From 3ba267b38a27e47a0451c8330bc582d8b7e01e67 Mon Sep 17 00:00:00 2001 From: yangxiangyuan Date: Tue, 18 Aug 2026 13:23:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(server,=20thought-lab):=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=99=BE=E7=82=BCAPI=E9=85=8D=E7=BD=AE=E5=B9=B6?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=9C=BA=E5=99=A8=E4=BA=BA=E8=B1=A1=E6=A3=8B?= =?UTF-8?q?=E5=AE=9E=E9=AA=8C=E5=AE=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 优化服务端百炼API配置,将硬编码的API地址改为通过环境变量读取,支持自定义部署端点 - 新增机器人象棋思想实验室,包含完整对战逻辑、管理接口与前端页面,配套测试用例 - 优化思想实验室路由注册,新增公开路由绑定能力支持非浏览器客户端调用 --- .../integration/test_bot_chess_api_smoke.js | 140 +++++ .../unit/test_bot_chess_referee.js | 203 +++++++ .../thought_lab/labs/bot_chess/index.html | 289 ++++++++++ src/server/calendar_voice.js | 5 +- src/server/global_news.js | 2 +- src/server/plant_home.js | 2 +- src/server/security_calendar.js | 6 +- src/server/style_check.js | 4 +- src/server/thought_lab/index.js | 23 + .../thought_lab/labs/bot_chess/bot_chess.db | Bin 0 -> 53248 bytes .../labs/bot_chess/bot_chess.db-shm | Bin 0 -> 32768 bytes .../labs/bot_chess/bot_chess.db-wal | Bin 0 -> 4120032 bytes .../thought_lab/labs/bot_chess/config.json | 49 ++ src/server/thought_lab/labs/bot_chess/db.js | 279 +++++++++ .../thought_lab/labs/bot_chess/index.js | 261 +++++++++ .../labs/bot_chess/orchestrator.js | 544 ++++++++++++++++++ .../thought_lab/labs/bot_chess/referee.js | 473 +++++++++++++++ .../thought_lab/labs/bot_chess/service.js | 365 ++++++++++++ src/server/weekly_integrations.js | 3 +- src/server/yuanzhupai.js | 2 +- 20 files changed, 2639 insertions(+), 11 deletions(-) create mode 100644 dev_test_scripts/integration/test_bot_chess_api_smoke.js create mode 100644 dev_test_scripts/unit/test_bot_chess_referee.js create mode 100644 public/tools/thought_lab/labs/bot_chess/index.html create mode 100644 src/server/thought_lab/labs/bot_chess/bot_chess.db create mode 100644 src/server/thought_lab/labs/bot_chess/bot_chess.db-shm create mode 100644 src/server/thought_lab/labs/bot_chess/bot_chess.db-wal create mode 100644 src/server/thought_lab/labs/bot_chess/config.json create mode 100644 src/server/thought_lab/labs/bot_chess/db.js create mode 100644 src/server/thought_lab/labs/bot_chess/index.js create mode 100644 src/server/thought_lab/labs/bot_chess/orchestrator.js create mode 100644 src/server/thought_lab/labs/bot_chess/referee.js create mode 100644 src/server/thought_lab/labs/bot_chess/service.js diff --git a/dev_test_scripts/integration/test_bot_chess_api_smoke.js b/dev_test_scripts/integration/test_bot_chess_api_smoke.js new file mode 100644 index 0000000..ca03bed --- /dev/null +++ b/dev_test_scripts/integration/test_bot_chess_api_smoke.js @@ -0,0 +1,140 @@ +// ============================================================ +// test_bot_chess_api_smoke.js —— bot_chess HTTP 冒烟集成测试 +// 运行:node dev_test_scripts/integration/test_bot_chess_api_smoke.js +// +// 临时起一个 express 实例,真实绑定 thought_lab 全部路由(含 bot_chess +// bindPublicRoutes 公开路由),验证:鉴权穿透、建赛、state、走子、 +// 非法走子错误码、轮次保护、MD 日志、删除。全程不碰 8976/8977 端口。 +// ============================================================ +const express = require('express') +const http = require('http') +const thoughtLab = require('../../src/server/thought_lab') + +const COOKIE = 'nav_gate=1' +let passed = 0, failed = 0 + +const app = express() +app.use(express.json()) +thoughtLab.bindRoutes(app) + +const server = http.createServer(app) + +async function req(method, path, body) { + const opts = { method, headers: { Cookie: COOKIE } } + if (body !== undefined) { + opts.headers['Content-Type'] = 'application/json' + opts.body = JSON.stringify(body) + } + const resp = await fetch(`http://127.0.0.1:${server.address().port}${path}`, opts) + let data = null + try { data = await resp.json() } catch (e) { data = null } + return { status: resp.status, data } +} + +const ok = (name) => { passed++; console.log(' ✔ ' + name) } +const bad = (name, e) => { failed++; console.log(' ✘ ' + name + ' —— ' + (e || 'fail')) } +const chk = (cond, msg) => { if (!cond) throw new Error(msg) } + +const main = async () => { + await new Promise(res => server.listen(0, res)) + + // 1) lab 列表包含 bot_chess(首页页签渲染) + { + const r = await req('GET', '/api/thought_lab/labs') + chk(r.data && r.data.ok, 'labs 接口不 ok') + const lab = (r.data.labs || []).find(l => l.id === 'bot_chess') + chk(!!lab, '列表无 bot_chess') + ok('lab 列表包含 bot_chess(' + lab.name + ')') + } + + // 2) 创建比赛 + let matchId = 0, redToken = '', blackToken = '' + { + const r = await req('POST', '/api/thought_lab/bot_chess/matches', { + title: '冒烟测试', red_bot: '红Bot', black_bot: '黑Bot', best_of: 1 + }) + chk(r.data && r.data.ok, '创建比赛失败:' + JSON.stringify(r.data)) + chk(r.data.match_id > 0, '无 match_id') + chk(r.data.red_token && r.data.black_token, '未返回 token') + matchId = r.data.match_id + redToken = r.data.red_token + blackToken = r.data.black_token + ok(`创建比赛 #${matchId}(红/黑 token 已签发)`) + } + + // 3) 未开局时走子 → MATCH_NOT_STARTED + { + const r = await req('POST', `/api/thought_lab/bot_chess/matches/${matchId}/moves`, { move: 'h2-e2', side: 'red' }) + chk(r.status === 400 && r.data.code === 'MATCH_NOT_STARTED', '未开局应拒绝走子:' + JSON.stringify(r.data)) + ok('未开局走子被拒(MATCH_NOT_STARTED)') + } + + // 4) 开局 + { + const r = await req('POST', `/api/thought_lab/bot_chess/matches/${matchId}/start`) + chk(r.data && r.data.ok, '开局失败') + ok('开局成功') + } + + // 5) 红方合法走子 h2-e2 + { + const r = await req('POST', `/api/thought_lab/bot_chess/matches/${matchId}/moves`, { move: 'h2e2', side: 'red' }) + chk(r.data && r.data.ok && r.data.accepted, '红方 h2e2 应被接受:' + JSON.stringify(r.data)) + ok('红方 h2e2 合法走子被接受') + } + + // 6) 轮次保护:红方再次走子 → NOT_YOUR_TURN + { + const r = await req('POST', `/api/thought_lab/bot_chess/matches/${matchId}/moves`, { move: 'b0c2', side: 'red' }) + chk(r.data && r.data.code === 'NOT_YOUR_TURN', '轮次保护失效:' + JSON.stringify(r.data)) + ok('红方连走被拒(NOT_YOUR_TURN)') + } + + // 7) 非法走子:黑马斜走 b9c8(马走日,斜线非法)→ 400 + 错误码 + { + const r = await req('POST', `/api/thought_lab/bot_chess/matches/${matchId}/moves`, { move: 'b9c8', side: 'black' }) + chk(r.status === 400 && r.data.code === 'ILLEGAL_MOVE', '非法走子应 400+code:' + JSON.stringify(r.data)) + ok('非法走子返回 400 + ILLEGAL_MOVE') + } + + // 8) bot token 鉴权:裸 token 走子/查状态 + { + const r = await req('GET', `/api/thought_lab/bot_chess/matches/${matchId}/state`) + chk(r.data && r.data.ok, 'state 失败') + chk(r.data.board && r.data.board.fen.includes(' '), 'state 无 FEN') + chk(r.data.game && (r.data.game.side_to_move === 'red' || r.data.game.side_to_move === 'black'), 'state 无 side_to_move') + ok('state 接口返回局面(FEN/棋盘/turn/side_to_move)') + } + + // 9) 黑方合法走子 + 状态推进 + { + const r = await req('POST', `/api/thought_lab/bot_chess/matches/${matchId}/moves`, { move: 'h9g7', side: 'black' }) + chk(r.data && r.data.ok, '黑方 h9g7 应接受:' + JSON.stringify(r.data)) + ok('黑方 h9g7 合法走子被接受') + } + + // 10) MD 日志可导出 + { + const resp = await fetch(`http://127.0.0.1:${server.address().port}/api/thought_lab/bot_chess/matches/${matchId}/log.md`, { headers: { Cookie: COOKIE } }) + const md = await resp.text() + chk(resp.status === 200 && md.includes('机器人象棋 比赛日志'), 'MD 日志异常') + chk(md.includes('h2'), 'MD 日志应包含着法 h2') + ok('MD 日志导出成功(含着法记录)') + } + + // 11) 清理 + { + const r = await req('DELETE', `/api/thought_lab/bot_chess/matches/${matchId}`) + chk(r.data && r.data.ok, '删除失败') + // token 不应再被识别 + const r2 = await req('POST', `/api/thought_lab/bot_chess/matches/${matchId}/moves`, { move: 'h2e2', side: 'red' }) + chk(r2.status === 404 || !r2.data.ok, '删除后仍可访问') + ok('比赛删除成功') + } + + server.close() + console.log(`\n结果:${passed} 通过 / ${failed} 失败`) + process.exit(failed ? 1 : 0) +} + +main().catch(e => { console.error('冒烟测试异常:', e); server && server.close(); process.exit(1) }) \ No newline at end of file diff --git a/dev_test_scripts/unit/test_bot_chess_referee.js b/dev_test_scripts/unit/test_bot_chess_referee.js new file mode 100644 index 0000000..00d65b3 --- /dev/null +++ b/dev_test_scripts/unit/test_bot_chess_referee.js @@ -0,0 +1,203 @@ +// ============================================================ +// test_bot_chess_referee.js —— bot_chess 规则引擎与对局核心单测 +// 运行:node dev_test_scripts/unit/test_bot_chess_referee.js +// +// 覆盖:FEN 解析/往返、走子校验(蹩马腿/塞象眼/炮架/将帅照面/送将)、 +// 飞将直取、子力判和、以及 service 层建赛-开局-走子-非法拒绝的端到端。 +// 测试使用真实 SQLite(bot_chess.db),跑完自动删除测试比赛。 +// ============================================================ +const assert = require('assert') +const r = require('../../src/server/thought_lab/labs/bot_chess/referee') +const service = require('../../src/server/thought_lab/labs/bot_chess/service') +const db = require('../../src/server/thought_lab/labs/bot_chess/db') + +let passed = 0, failed = 0 +const ok = (name) => { passed++; console.log(' ✔ ' + name) } +const bad = (name, e) => { failed++; console.log(' ✘ ' + name + ' —— ' + (e && e.message)) } + +const t = async (name, fn) => { + try { await fn(); ok(name) } catch (e) { bad(name, e) } +} + +const main = async () => { + + // ---------- 1. FEN 往返 ---------- + await t('FEN 解析 + 序列化往返一致', () => { + const { board, turn } = r.parseFen(r.START_FEN) + assert.strictEqual(turn, 'w') + assert.strictEqual(r.toFen(board, 'w').split(' ')[0], r.START_FEN.split(' ')[0]) + }) + + await t('开局双方各 16 子(红 16 / 黑 16)', () => { + const { board } = r.parseFen(r.START_FEN) + const list = r.boardToPieceList(board) + assert.strictEqual(list.red.length, 16) + assert.strictEqual(list.black.length, 16) + }) + + // ---------- 2. 走子合法性 ---------- + await t('标准开局:红炮二平五 h2-e2 合法', () => { + const { board } = r.parseFen(r.START_FEN) + const v = r.validateMove(board, 'red', { r: 2, c: 7 }, { r: 2, c: 4 }) + assert.strictEqual(v.ok, true) + }) + + await t('标准开局:黑马 b9-c7 合法(无蹩马腿)', () => { + const { board } = r.parseFen(r.START_FEN) + const v = r.validateMove(board, 'black', { r: 9, c: 1 }, { r: 7, c: 2 }) + assert.strictEqual(v.ok, true) + }) + + await t('蹩马腿:腿点被占 → BLOCKED_HORSE', () => { + const fen = '4k4/9/9/9/9/9/9/1N7/1p7/4K4 w - - 0 1' + const { board } = r.parseFen(fen) // 红马 b2 想跳 a0,腿点 b1 有黑卒 → 蹩马腿 + const v = r.validateMove(board, 'red', { r: 2, c: 1 }, { r: 0, c: 0 }) + assert.strictEqual(v.ok, false) + assert.strictEqual(v.code, r.E.BLOCKED_HORSE) + }) + + await t('马腿清空后同一着法合法', () => { + const fen = '3k5/9/9/9/9/9/9/1N7/9/4K4 w - - 0 1' + const { board } = r.parseFen(fen) + const v = r.validateMove(board, 'red', { r: 2, c: 1 }, { r: 0, c: 0 }) + assert.strictEqual(v.ok, true) + }) + + await t('炮吃子必须有且仅有一个炮架', () => { + // 红炮 d2,红车 f2 作架,黑卒 h2 → 隔一子吃,合法 + const withScreen = '3k5/9/9/9/9/9/9/3C1R1p1/9/4K4 w - - 0 1' + let { board } = r.parseFen(withScreen) + let v = r.validateMove(board, 'red', { r: 2, c: 3 }, { r: 2, c: 7 }) + assert.strictEqual(v.ok, true) + + // 无炮架直接吃 → CANNON_SCREEN + const noScreen = '3k5/9/9/9/9/9/9/3C3p1/9/4K4 w - - 0 1' + ;({ board } = r.parseFen(noScreen)) + v = r.validateMove(board, 'red', { r: 2, c: 3 }, { r: 2, c: 7 }) + assert.strictEqual(v.ok, false) + assert.strictEqual(v.code, r.E.CANNON_SCREEN) + }) + + await t('送将:移开自家遮挡车 → SELF_CHECK', () => { + // 黑车 e9 打红帅 e0,红车 e1 挡着;红车横移 e1-f1 让出直线 → 送将 + const fen = '4r4/9/9/9/9/9/9/9/4R4/4K4 w - - 0 1' + const { board } = r.parseFen(fen) + const v = r.validateMove(board, 'red', { r: 1, c: 4 }, { r: 1, c: 5 }) + assert.strictEqual(v.ok, false) + assert.strictEqual(v.code, r.E.SELF_CHECK) + }) + + await t('将帅照面:走完双方帅将同列无遮挡 → FLYING_GENERAL', () => { + const fen = '4k4/9/9/9/9/9/9/9/9/4K4 w - - 0 1' + const { board } = r.parseFen(fen) + const v = r.validateMove(board, 'red', { r: 0, c: 4 }, { r: 1, c: 4 }) + assert.strictEqual(v.ok, false) + assert.strictEqual(v.code, r.E.FLYING_GENERAL) + }) + + await t('飞将直取敌帅:合法且立即获胜(king_captured)', () => { + const fen = '4k4/9/9/9/9/9/9/9/9/4K4 w - - 0 1' + const { board } = r.parseFen(fen) + const v = r.validateMove(board, 'red', { r: 0, c: 4 }, { r: 9, c: 4 }) + assert.strictEqual(v.ok, true) + if (v.ok) { + const after = r.applyMove(board, { r: 0, c: 4 }, { r: 9, c: 4 }).board + const res = r.resolveAfterMove(after, 'red') + assert.strictEqual(res.ended, true) + assert.strictEqual(res.kind, 'king_captured') + assert.strictEqual(res.winner, 'red') + } + }) + + await t('不能吃自己棋子 → CAPTURE_OWN', () => { + const { board } = r.parseFen(r.START_FEN) + const v = r.validateMove(board, 'red', { r: 0, c: 0 }, { r: 0, c: 1 }) + assert.strictEqual(v.ok, false) + assert.strictEqual(v.code, r.E.CAPTURE_OWN) + }) + + // ---------- 3. 子力判和 ---------- + await t('子力不足:双方只剩帅士相 → 和', () => { + const fen = '4k4/9/9/9/9/9/9/9/4K4/9 w - - 0 1' + const { board } = r.parseFen(fen) + assert.strictEqual(r.insufficientMaterial(board), true) + }) + + await t('存在车/马/炮/兵任一即非子力和', () => { + const { board } = r.parseFen(r.START_FEN) + assert.strictEqual(r.insufficientMaterial(board), false) + }) + + // ---------- 4. service 端到端(建赛 → 开局 → 走子 → 非法拒绝)---------- + let cleanMatchId = 0 + await t('service:创建比赛 + 开局 + 合法走子 + 非法拒绝', async () => { + const snap1 = service.createMatch({ title: '单测比赛', red_bot: '测试红', black_bot: '测试黑', best_of: 1 }) + cleanMatchId = snap1.match.id + assert.strictEqual(snap1.match.status, 'created') + const started = service.startMatch(snap1.match.id) + assert.strictEqual(started.ok, true) + + let res = service.submitMove(snap1.match.id, 'red', 'h2e2') + assert.strictEqual(res.ok, true) + assert.strictEqual(res.game_finished, false) + + // 非法走子:黑方兵不能后退(b6-b5 不是黑方合理)→ 用明显非法:黑车 b9 斜走 + res = service.submitMove(snap1.match.id, 'black', 'b9c8') // 车斜走 → 非法 + assert.strictEqual(res.ok, false) + + // 非法走子后局面不动:依旧黑手 + const snap2 = service.buildSnapshot(snap1.match.id) + assert.strictEqual(snap2.game.turn, 'b') + + // 轮次保护:红方连走两次被拒 + res = service.submitMove(snap1.match.id, 'red', 'b0c2') + assert.strictEqual(res.ok, false) + assert.strictEqual(res.code, 'NOT_YOUR_TURN') + + // 黑方合法走子 + res = service.submitMove(snap1.match.id, 'black', 'h9g7') + assert.strictEqual(res.ok, true) + + // 日志可导出 + const md = service.buildMarkdownLog(snap1.match.id) + assert.ok(md.includes('h2-e2') || md.includes('红炮 h2-e2') || md.includes('比赛')) + }) + + await t('service:犯规累计到上限判负', async () => { + const snap = service.createMatch({ title: '犯规测试', red_bot: 'R', black_bot: 'B', best_of: 1 }) + service.startMatch(snap.match.id) + let r2 = service.submitMove(snap.match.id, 'red', 'h2e2') + assert.strictEqual(r2.ok, true) + // 黑方连续 3 次犯规(超时/非法由监督器调用 applyFoul) + service.applyFoul(snap.match.id, 'black', '测试犯规1') + service.applyFoul(snap.match.id, 'black', '测试犯规2') + const r3 = service.applyFoul(snap.match.id, 'black', '测试犯规3') + assert.strictEqual(r3.ok, true) + const snap2 = service.buildSnapshot(snap.match.id) + assert.strictEqual(snap2.game.status, 'finished') + assert.strictEqual(snap2.game.winner, 'red') + db.deleteMatch(snap.match.id) + }) + + await t('service:终局后自动换先开下一局 / 结算', async () => { + // best_of=3:第 1 局红胜 → 红黑对调开第 2 局 + const snap = service.createMatch({ title: '换先测试', red_bot: 'R', black_bot: 'B', best_of: 3 }) + service.startMatch(snap.match.id) + // 快速杀棋:飞将直取(构造后手动写入局面不好走 service API,这里用 applyFoul 制造判负) + service.forfeitSide(snap.match.id, 'black', '测试判负') + const snap2 = service.buildSnapshot(snap.match.id) + assert.strictEqual(snap2.game.game_no, 2) + assert.strictEqual(snap2.game.status, 'playing') + assert.strictEqual(snap2.game.turn, 'w') + // 第 2 局黑方(原黑 bot)执红 + assert.strictEqual(snap2.match.score_red, 1) + db.deleteMatch(snap.match.id) + }) + + if (cleanMatchId) db.deleteMatch(cleanMatchId) + + console.log(`\n结果:${passed} 通过 / ${failed} 失败`) + process.exit(failed ? 1 : 0) +} + +main().catch(e => { console.error('测试运行异常:', e); process.exit(1) }) \ No newline at end of file diff --git a/public/tools/thought_lab/labs/bot_chess/index.html b/public/tools/thought_lab/labs/bot_chess/index.html new file mode 100644 index 0000000..436e0ed --- /dev/null +++ b/public/tools/thought_lab/labs/bot_chess/index.html @@ -0,0 +1,289 @@ + + + + + +机器人象棋 · 裁判室 + + + +

♟️ 机器人象棋 · 裁判室 模式…

+ +
+ 对局: + + + + + +
+ +
+
+

当前局面

+
(无)
+
—
+
+
+

对话(只跟裁判说)

+
+
+ + +
+
+
+ +
+

对局流水 等待选局

+
—
+
+ + + + + + \ No newline at end of file diff --git a/src/server/calendar_voice.js b/src/server/calendar_voice.js index bf5f07d..67d895a 100644 --- a/src/server/calendar_voice.js +++ b/src/server/calendar_voice.js @@ -95,8 +95,9 @@ const bindRoutes = (app) => { if (!text) return res.status(400).json({ ok: false, error: 'No text provided' }) const apiKey = process.env.DASHSCOPE_API_KEY || '' - // 使用 OpenAI 兼容接口调用 DeepSeek-V3 - const url = 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions' + // 使用 OpenAI 兼容接口调用百炼大模型(前缀取环境变量,见 ~/Toolbox_local_creds.env.local) + const dashScopeHost = process.env.DASHSCOPE_API_HOST_FOR_OPENAI || '' + const url = dashScopeHost ? `${dashScopeHost.replace(/\/+$/, '')}/chat/completions` : '' const systemPrompt = `# Role 你是一名资深的时间管理专家和高效秘书,擅长从冗杂的口语录音中快速提炼核心日程主题。 diff --git a/src/server/global_news.js b/src/server/global_news.js index f46ee7f..d93c19a 100644 --- a/src/server/global_news.js +++ b/src/server/global_news.js @@ -192,7 +192,7 @@ const getAiConfig = () => { return { enabled: c.enabled !== false, apiKey: c.api_key || '', - apiBase: c.api_base || 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', + apiBase: c.api_base || (process.env.DASHSCOPE_API_HOST_FOR_OPENAI ? `${process.env.DASHSCOPE_API_HOST_FOR_OPENAI.replace(/\/+$/, '')}/chat/completions` : ''), model: c.model || 'qwen-plus-latest' } } catch { diff --git a/src/server/plant_home.js b/src/server/plant_home.js index 4b0df64..27734dc 100644 --- a/src/server/plant_home.js +++ b/src/server/plant_home.js @@ -15,7 +15,7 @@ const DEFAULT_IMAGE_DIR = path.join(DEFAULT_UPLOAD_DIR, 'images') const DEFAULT_ATTACHMENT_DIR = path.join(DEFAULT_UPLOAD_DIR, 'attachments') const DEFAULT_SEED_HOME_DIR = path.join(DEFAULT_UPLOAD_DIR, 'seed', 'home') const QW_BASE = 'https://devapi.qweather.com' -const DASHSCOPE_BASE = 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions' +const DASHSCOPE_BASE = process.env.DASHSCOPE_API_HOST_FOR_OPENAI ? `${process.env.DASHSCOPE_API_HOST_FOR_OPENAI.replace(/\/+$/, '')}/chat/completions` : '' const BJ_TZ = 'Asia/Shanghai' const weatherCache = { payload: null, diff --git a/src/server/security_calendar.js b/src/server/security_calendar.js index 9b3f2db..432e44c 100644 --- a/src/server/security_calendar.js +++ b/src/server/security_calendar.js @@ -11,7 +11,7 @@ if (!fs.existsSync(SECURITY_CALENDAR_AI_CFG_PATH)) { fs.writeFileSync(SECURITY_CALENDAR_AI_CFG_PATH, JSON.stringify({ enabled: false, api_key: '', - api_base: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', + api_base: process.env.DASHSCOPE_API_HOST_FOR_OPENAI ? `${process.env.DASHSCOPE_API_HOST_FOR_OPENAI.replace(/\/+$/, '')}/chat/completions` : '', model: 'qwen-plus-latest', window_days: 21, auto_verify_enabled: true, @@ -476,7 +476,7 @@ function loadSecurityCalendarAiConfig() { return { enabled: cfg && cfg.enabled !== false, apiKey: String((cfg && cfg.api_key) || '').trim(), - apiBase: String((cfg && cfg.api_base) || 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions').trim(), + apiBase: String((cfg && cfg.api_base) || (process.env.DASHSCOPE_API_HOST_FOR_OPENAI ? `${process.env.DASHSCOPE_API_HOST_FOR_OPENAI.replace(/\/+$/, '')}/chat/completions` : '')).trim(), model: String((cfg && cfg.model) || 'qwen-plus-latest').trim(), windowDays: Math.max(7, Math.min(21, parseInt(String((cfg && cfg.window_days) || '21'), 10) || 21)), autoVerifyEnabled: cfg && cfg.auto_verify_enabled !== false, @@ -488,7 +488,7 @@ function loadSecurityCalendarAiConfig() { return { enabled: false, apiKey: '', - apiBase: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', + apiBase: process.env.DASHSCOPE_API_HOST_FOR_OPENAI ? `${process.env.DASHSCOPE_API_HOST_FOR_OPENAI.replace(/\/+$/, '')}/chat/completions` : '', model: 'qwen-plus-latest', windowDays: 21, autoVerifyEnabled: false, diff --git a/src/server/style_check.js b/src/server/style_check.js index 1b259f1..d66b61c 100644 --- a/src/server/style_check.js +++ b/src/server/style_check.js @@ -27,7 +27,7 @@ if (!fs.existsSync(configPath)) { ai: { enabled: true, api_key: '', - api_base: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions', + api_base: process.env.DASHSCOPE_API_HOST_FOR_OPENAI ? `${process.env.DASHSCOPE_API_HOST_FOR_OPENAI.replace(/\/+$/, '')}/chat/completions` : '', vision_model: 'qwen-vl-plus', timeout_ms: 30000 } @@ -1495,7 +1495,7 @@ const getAiConfig = () => { return { enabled: ai.enabled !== false, apiKey: String(ai.api_key || envKey || '').trim(), - apiBase: String(ai.api_base || 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions').trim(), + apiBase: String(ai.api_base || (process.env.DASHSCOPE_API_HOST_FOR_OPENAI ? `${process.env.DASHSCOPE_API_HOST_FOR_OPENAI.replace(/\/+$/, '')}/chat/completions` : '')).trim(), visionModel: String(ai.vision_model || 'qwen-vl-plus').trim(), timeoutMs: Math.max(5000, Math.min(90000, Number(ai.timeout_ms || 30000))) } diff --git a/src/server/thought_lab/index.js b/src/server/thought_lab/index.js index e83a8b4..c739539 100644 --- a/src/server/thought_lab/index.js +++ b/src/server/thought_lab/index.js @@ -142,6 +142,11 @@ const bindRoutes = (app) => { } }) + // 4.5) 需要被「非浏览器客户端」直接访问的 lab 公开路由 + // (必须在导航鉴权守卫之前注册;使用方必须自行做 token/身份校验, + // 如 bot_chess 的红黑 bot token;普通 lab 无需实现 bindPublicRoutes) + bindLabPublicBackends(app) + // 4) 其余 /api/thought_lab 接口:默认导航鉴权 app.use('/api/thought_lab', navAuthApi) @@ -150,6 +155,24 @@ const bindRoutes = (app) => { bindLabBackends(app) } +const bindLabPublicBackends = (app) => { + try { + if (!fs.existsSync(LABS_DIR)) return + const dirs = fs.readdirSync(LABS_DIR, { withFileTypes: true }) + for (const d of dirs) { + if (!d.isDirectory()) continue + const entry = path.join(LABS_DIR, d.name, 'index.js') + if (!fs.existsSync(entry)) continue + try { + const mod = require(entry) + if (mod && typeof mod.bindPublicRoutes === 'function') mod.bindPublicRoutes(app) + } catch (e) { + try { console.error(`[thought_lab] 绑定 lab 公开路由失败: ${d.name} ${String(e.message || e)}`) } catch {} + } + } + } catch {} +} + const bindLabBackends = (app) => { try { if (!fs.existsSync(LABS_DIR)) return diff --git a/src/server/thought_lab/labs/bot_chess/bot_chess.db b/src/server/thought_lab/labs/bot_chess/bot_chess.db new file mode 100644 index 0000000000000000000000000000000000000000..e9ad24829c41c696c883c2f75334db965b0e42a2 GIT binary patch literal 53248 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

IoRo9`Y$ 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

3uO$Vobd#VYC4I|> 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­62h83GZc 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 m+|49&ayPR;?xvIT { + const parts = BJ_FMT.formatToParts(new Date()) + const get = t => (parts.find(p => p.type === t) || {}).value || '' + return `${get('year')}-${get('month')}-${get('day')} ${get('hour')}:${get('minute')}:${get('second')}` +} + +const randomToken = (len = 32) => crypto.randomBytes(len).toString('hex') + +const getDB = () => { + if (db) return db + db = new Database(DB_PATH) + db.pragma('journal_mode = WAL') + db.pragma('synchronous = NORMAL') + initSchema(db) + return db +} + +const initSchema = (database) => { + database.exec(` + CREATE TABLE IF NOT EXISTS bot_chess_matches ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL DEFAULT '', + red_bot TEXT NOT NULL DEFAULT '', + black_bot TEXT NOT NULL DEFAULT '', + red_model TEXT NOT NULL DEFAULT '', + black_model TEXT NOT NULL DEFAULT '', + best_of INTEGER NOT NULL DEFAULT 3, + status TEXT NOT NULL DEFAULT 'created', -- created | active | finished | aborted + score_red INTEGER NOT NULL DEFAULT 0, + score_black INTEGER NOT NULL DEFAULT 0, + winner TEXT NOT NULL DEFAULT '', -- red | black | ''(未定)| draw + red_token TEXT NOT NULL DEFAULT '', + black_token TEXT NOT NULL DEFAULT '', + current_game_id INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL, + started_at TEXT NOT NULL DEFAULT '', + finished_at TEXT NOT NULL DEFAULT '' + ) + `) + database.exec(` + CREATE TABLE IF NOT EXISTS bot_chess_games ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + match_id INTEGER NOT NULL, + game_no INTEGER NOT NULL, -- 第几局(1 起) + fen TEXT NOT NULL, -- 当前 FEN + turn TEXT NOT NULL DEFAULT 'w', -- 当前轮到 w=红 b=黑 + status TEXT NOT NULL DEFAULT 'playing', -- playing | finished | aborted + result TEXT NOT NULL DEFAULT '', -- red_win | black_win | draw | forfeit_red | forfeit_black + winner TEXT NOT NULL DEFAULT '', + move_count INTEGER NOT NULL DEFAULT 0, + red_fouls INTEGER NOT NULL DEFAULT 0, + black_fouls INTEGER NOT NULL DEFAULT 0, + no_capture_plies INTEGER NOT NULL DEFAULT 0, -- 自然限着(无吃子/无进兵)计数 + created_at TEXT NOT NULL, + finished_at TEXT NOT NULL DEFAULT '' + ) + `) + database.exec(` + CREATE TABLE IF NOT EXISTS bot_chess_moves ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + match_id INTEGER NOT NULL, + game_id INTEGER NOT NULL, + move_no INTEGER NOT NULL, + side TEXT NOT NULL, -- red | black + from_sq TEXT NOT NULL, + to_sq TEXT NOT NULL, + piece TEXT NOT NULL DEFAULT '', + captured TEXT NOT NULL DEFAULT '', + is_check INTEGER NOT NULL DEFAULT 0, + fen_before TEXT NOT NULL, + fen_after TEXT NOT NULL, + san TEXT NOT NULL DEFAULT '', + created_at TEXT NOT NULL + ) + `) + database.exec(` + CREATE TABLE IF NOT EXISTS bot_chess_events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + match_id INTEGER NOT NULL, + game_id INTEGER NOT NULL DEFAULT 0, + kind TEXT NOT NULL, -- match_created | start | game_started | move | foul | + -- game_finished | match_finished | abort | forfeit | chat | note + side TEXT NOT NULL DEFAULT '', + content TEXT NOT NULL DEFAULT '', + created_at TEXT NOT NULL + ) + `) + database.exec(`CREATE INDEX IF NOT EXISTS idx_bc_games_match ON bot_chess_games(match_id, game_no)`) + database.exec(`CREATE INDEX IF NOT EXISTS idx_bc_moves_game ON bot_chess_moves(game_id, move_no)`) + database.exec(`CREATE INDEX IF NOT EXISTS idx_bc_events_match ON bot_chess_events(match_id)`) +} + +// ---------------- matches ---------------- +const rowMatch = (row) => row || null +const createMatch = ({ title, red_bot, black_bot, red_model, black_model, best_of }) => { + const now = nowBeijing() + const r = getDB().prepare(` + INSERT INTO bot_chess_matches + (title, red_bot, black_bot, red_model, black_model, best_of, + status, red_token, black_token, created_at) + VALUES (?, ?, ?, ?, ?, ?, 'created', ?, ?, ?) + `).run( + String(title || '').trim() || '电脑对战', + red_bot, black_bot, red_model, black_model, + Math.max(1, Math.min(9, Number(best_of) || 3)), + randomToken(), randomToken(), now + ) + return getMatch(Number(r.lastInsertRowid)) +} +const getMatch = (id) => rowMatch(getDB().prepare(`SELECT * FROM bot_chess_matches WHERE id = ?`).get(id)) +const listMatches = (limit = 50) => getDB().prepare(` + SELECT * FROM bot_chess_matches ORDER BY id DESC LIMIT ? +`).all(limit).map(rowMatch) + +const updateMatchStatus = (id, { status, winner, score_red, score_black, finished_at, started_at, current_game_id }) => { + const sets = [] + const vals = [] + if (status !== undefined) { sets.push('status = ?'); vals.push(status) } + if (winner !== undefined) { sets.push('winner = ?'); vals.push(winner) } + if (score_red !== undefined) { sets.push('score_red = ?'); vals.push(score_red) } + if (score_black !== undefined) { sets.push('score_black = ?'); vals.push(score_black) } + if (finished_at !== undefined) { sets.push('finished_at = ?'); vals.push(finished_at) } + if (started_at !== undefined) { sets.push('started_at = ?'); vals.push(started_at) } + if (current_game_id !== undefined) { sets.push('current_game_id = ?'); vals.push(current_game_id) } + if (!sets.length) return getMatch(id) + vals.push(id) + getDB().prepare(`UPDATE bot_chess_matches SET ${sets.join(', ')} WHERE id = ?`).run(...vals) + return getMatch(id) +} + +const deleteMatch = (id) => { + const tx = getDB().transaction(() => { + getDB().prepare(`DELETE FROM bot_chess_moves WHERE match_id = ?`).run(id) + getDB().prepare(`DELETE FROM bot_chess_events WHERE match_id = ?`).run(id) + getDB().prepare(`DELETE FROM bot_chess_games WHERE match_id = ?`).run(id) + getDB().prepare(`DELETE FROM bot_chess_matches WHERE id = ?`).run(id) + }) + tx() + return true +} + +// 根据 bot token 找到它所属的比赛(用于 /state、/moves 接口鉴权与身份绑定) +const findMatchByToken = (token) => { + if (!token) return null + const row = getDB().prepare(` + SELECT * FROM bot_chess_matches WHERE red_token = ? OR black_token = ? + `).get(token, token) + if (!row) return null + const side = row.red_token === token ? 'red' : 'black' + return { match: rowMatch(row), side } +} + +// ---------------- games ---------------- +const rowGame = (row) => row || null +const createGame = (matchId, gameNo) => { + const now = nowBeijing() + const r = getDB().prepare(` + INSERT INTO bot_chess_games (match_id, game_no, fen, turn, status, created_at) + VALUES (?, ?, ?, 'w', 'playing', ?) + `).run(matchId, gameNo, require('./referee').START_FEN, now) + return getGame(Number(r.lastInsertRowid)) +} +const getGame = (id) => rowGame(getDB().prepare(`SELECT * FROM bot_chess_games WHERE id = ?`).get(id)) +const getGamesByMatch = (matchId) => getDB().prepare(` + SELECT * FROM bot_chess_games WHERE match_id = ? ORDER BY game_no ASC +`).all(matchId).map(rowGame) + +const updateGame = (id, patch) => { + const sets = [] + const vals = [] + const keys = ['fen', 'turn', 'status', 'result', 'winner', 'move_count', + 'red_fouls', 'black_fouls', 'no_capture_plies', 'finished_at'] + for (const k of keys) { + if (patch[k] !== undefined) { sets.push(`${k} = ?`); vals.push(patch[k]) } + } + if (!sets.length) return getGame(id) + vals.push(id) + getDB().prepare(`UPDATE bot_chess_games SET ${sets.join(', ')} WHERE id = ?`).run(...vals) + return getGame(id) +} +const addFoul = (gameId, side, reason) => { + const g = getGame(gameId) + if (!g) return null + const key = side === 'red' ? 'red_fouls' : 'black_fouls' + const n = (g[key] || 0) + 1 + return updateGame(gameId, { [key]: n }) +} +// ---------------- moves ---------------- +const getGameByCur = (matchId) => { + const m = getMatch(matchId) + if (!m || !m.current_game_id) return null + return getGame(m.current_game_id) +} + +const rowMove = (row) => row || null +const addMove = ({ match_id, game_id, move_no, side, from_sq, to_sq, piece, captured, is_check, fen_before, fen_after, san }) => { + const now = nowBeijing() + const r = getDB().prepare(` + INSERT INTO bot_chess_moves + (match_id, game_id, move_no, side, from_sq, to_sq, piece, captured, is_check, fen_before, fen_after, san, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `).run(match_id, game_id, move_no, side, from_sq, to_sq, piece, captured, is_check ? 1 : 0, fen_before, fen_after, san, now) + return rowMove(getDB().prepare(`SELECT * FROM bot_chess_moves WHERE id = ?`).get(Number(r.lastInsertRowid))) +} +const listMoves = (gameId, limit = 1000) => getDB().prepare(` + SELECT * FROM bot_chess_moves WHERE game_id = ? ORDER BY move_no ASC LIMIT ? +`).all(gameId, limit).map(rowMove) +// 某局的最后一次走子时间(超时监督用) +const lastMoveTime = (gameId) => { + const row = getDB().prepare(` + SELECT created_at FROM bot_chess_moves WHERE game_id = ? ORDER BY id DESC LIMIT 1 + `).get(gameId) + return row ? row.created_at : '' +} + +// ---------------- events ---------------- +const rowEvent = (row) => row || null +const addEvent = ({ match_id, game_id = 0, kind, side = '', content = '' }) => { + const now = nowBeijing() + const r = getDB().prepare(` + INSERT INTO bot_chess_events (match_id, game_id, kind, side, content, created_at) + VALUES (?, ?, ?, ?, ?, ?) + `).run(match_id, game_id, kind, side, content, now) + return rowEvent(getDB().prepare(`SELECT * FROM bot_chess_events WHERE id = ?`).get(Number(r.lastInsertRowid))) +} +const listEvents = (matchId, gameId = 0, limit = 500) => { + if (gameId) { + return getDB().prepare(` + SELECT * FROM bot_chess_events WHERE match_id = ? AND (game_id = ? OR kind IN ('match_created','start','match_finished','abort')) + ORDER BY id ASC LIMIT ? + `).all(matchId, gameId, limit).map(rowEvent) + } + return getDB().prepare(`SELECT * FROM bot_chess_events WHERE match_id = ? ORDER BY id ASC LIMIT ?`) + .all(matchId, limit).map(rowEvent) +} + +// 增量拉取事件流(前端“滚动日志”轮询用):只返回 id > after 的事件 +const listEventsAfter = (matchId, afterId = 0, limit = 200) => getDB().prepare(` + SELECT id, match_id, game_id, kind, side, content, created_at + FROM bot_chess_events + WHERE match_id = ? AND id > ? + ORDER BY id ASC LIMIT ? +`).all(matchId, Number(afterId) || 0, limit).map(rowEvent) + +module.exports = { + nowBeijing, randomToken, + createMatch, getMatch, listMatches, updateMatchStatus, deleteMatch, findMatchByToken, + createGame, getGame, getGamesByMatch, updateGame, addFoul, getGameByCur, + addMove, listMoves, lastMoveTime, + addEvent, listEvents, listEventsAfter +} \ No newline at end of file diff --git a/src/server/thought_lab/labs/bot_chess/index.js b/src/server/thought_lab/labs/bot_chess/index.js new file mode 100644 index 0000000..bacf816 --- /dev/null +++ b/src/server/thought_lab/labs/bot_chess/index.js @@ -0,0 +1,261 @@ +// ============================================================ +// bot_chess/index.js —— HTTP API 路由(核心对局 API + 裁判聊天 API) +// +// 鉴权设计(本 lab 需要被“非浏览器”的 bot 客户端访问,因此通过 +// thought_lab 预留的 bindPublicRoutes 在导航鉴权守卫之前注册): +// - 全局导航鉴权关闭时:全部放行(与容器行为一致) +// - 浏览器会话(nav_gate cookie 或同源 Referer):放行(聊天页用) +// - Bearer token:匹配 referee_token(管理/聊天)或比赛红黑 token(bot) +// 非法走子一律返回结构化错误码,局面不动;犯规判罚由监督器/编排器执行, +// 本层只做“可靠裁判”,不做惩罚。 +// ============================================================ +const fs = require('fs') +const path = require('path') +const express = require('express') +const service = require('./service') +const db = require('./db') +const r = require('./referee') + +const B = '/api/thought_lab/bot_chess' +const CONFIG_PATH = path.join(__dirname, 'config.json') +const FLAGS_PATH = path.join(process.cwd(), 'config', 'flags.json') +const NAV_AUTH_COOKIE = 'nav_gate' + +const readConfig = () => { + try { + if (fs.existsSync(CONFIG_PATH)) return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')) + } catch {} + return {} +} +const isNavAuthEnabled = () => { + try { + const f = JSON.parse(fs.readFileSync(FLAGS_PATH, 'utf-8')) + return (f && f.navAuth && f.navAuth.enable_auth) !== false + } catch { return true } +} +const parseCookie = (raw) => { + const out = {} + try { + String(raw || '').split(';').forEach(item => { + const idx = item.indexOf('=') + if (idx < 1) return + out[item.slice(0, idx).trim()] = item.slice(idx + 1).trim() + }) + } catch {} + return out +} +const refereeToken = () => String(process.env.BOT_CHESS_REFEREE_TOKEN || readConfig().referee_token || '') +const bearer = (req) => { + const h = String(req.headers.authorization || '') + if (/^Bearer\s+/i.test(h)) return h.replace(/^Bearer\s+/i, '').trim() + return '' +} +const sameOrigin = (req) => { + try { + const referer = String(req.headers.referer || '') + const host = String(req.headers.host || '') + return !!referer && !!host && new URL(referer).host === host + } catch { return false } +} +const isBrowser = (req) => parseCookie(req.headers.cookie || '')[NAV_AUTH_COOKIE] === '1' || sameOrigin(req) + +// 管理接口鉴权:浏览器 / referee token +const adminAuth = (req, res, next) => { + if (!isNavAuthEnabled() || isBrowser(req)) return next() + if (refereeToken() && bearer(req) === refereeToken()) return next() + return res.status(401).json({ ok: false, error: 'unauthorized' }) +} + +// bot 接口鉴权:浏览器(调试)/ 比赛 token(绑定红/黑身份) +const botAuth = (req, res, next) => { + if (!isNavAuthEnabled() || isBrowser(req)) { req.bot = { side: String(req.query.side || req.body.side || '') }; return next() } + const cfg = readConfig() + if (cfg.enable_bot_auth === false) { req.bot = { side: String(req.query.side || '') }; return next() } + const t = bearer(req) + const found = t ? db.findMatchByToken(t) : null + if (!found) return res.status(401).json({ ok: false, error: 'unauthorized' }) + req.bot = { matchId: found.match.id, side: found.side, token: t } + req.bot.forceSide = String(req.query.side || '') || found.side + next() +} + +const ok = (res, data) => res.json(Object.assign({ ok: true }, data)) +const fail = (res, status, code, error) => res.status(status).json({ ok: false, code, error }) + +// ---------------- 路由注册(守卫之前,lab 公开路由)---------------- +const bindPublicRoutes = (app) => { + const router = express.Router() + service.ensureSupervisor() // 超时判罚监督器(进程级,仅启动一次) + + // 元信息(前端弹窗双模式依据) + router.get('/meta', (req, res) => ok(res, { ui_mode: String(readConfig().ui_mode || 'debug') })) + + // ---- 管理接口 ---- + router.get('/matches', adminAuth, (req, res) => { + try { + const list = db.listMatches(60).map(m => ({ + id: m.id, title: m.title, red_bot: m.red_bot, black_bot: m.black_bot, + best_of: m.best_of, status: m.status, score_red: m.score_red, score_black: m.score_black, + winner: m.winner, created_at: m.created_at, current_game_no: (service.buildSnapshot(m.id) || {}).match ? service.buildSnapshot(m.id).match.current_game_no : 0 + })) + return ok(res, { matches: list }) + } catch (e) { return fail(res, 500, 'INTERNAL', String(e.message || e)) } + }) + + // 创建比赛(返回快照 + 红黑 token——token 只在本响应出现,后续不再暴露) + router.post('/matches', adminAuth, (req, res) => { + try { + const body = req.body || {} + if (!String(body.title || '').trim() && !String(body.red_bot || '').trim() && !String(body.black_bot || '').trim()) { + return fail(res, 400, 'BAD_REQUEST', 'title / red_bot / black_bot 至少提供一项') + } + const snap = service.createMatch(body) + const m = db.getMatch(snap.match.id) + return ok(res, { + match_id: snap.match.id, + red_token: m.red_token, + black_token: m.black_token, + snapshot: snap + }) + } catch (e) { return fail(res, 500, 'INTERNAL', String(e.message || e)) } + }) + + router.get('/matches/:id', adminAuth, (req, res) => { + try { + const snap = service.buildSnapshot(Number(req.params.id)) + if (!snap) return fail(res, 404, 'NOT_FOUND', '比赛不存在') + return ok(res, snap) + } catch (e) { return fail(res, 500, 'INTERNAL', String(e.message || e)) } + }) + + router.get('/matches/:id/state', botAuth, (req, res) => { + try { + const mid = req.bot && req.bot.matchId ? Number(req.bot.matchId) : Number(req.params.id) + const snap = service.buildSnapshot(mid) + if (!snap) return fail(res, 404, 'NOT_FOUND', '比赛不存在') + const curSide = snap.board && snap.board.side_to_move + let legal = [] + if (curSide && snap.game && snap.game.status === 'playing') { + const fen = r.parseFen(snap.board.fen) + legal = r.legalMoves(fen.board, curSide) + .map(m => `${'abcdefghi'[m.from.c]}${m.from.r}-${'abcdefghi'[m.to.c]}${m.to.r}`) + .slice(0, 120) + } + // 显式给出 bot 需要的轮次字段(game.side_to_move),与内存兜底行为一致 + const game = snap.game ? Object.assign({}, snap.game, { side_to_move: curSide }) : null + return ok(res, Object.assign({}, snap, { game, your_side: req.bot && req.bot.side, legal_moves_of_side_to_move: legal })) + } catch (e) { return fail(res, 500, 'INTERNAL', String(e.message || e)) } + }) + + // 提交走子(bot 主接口;非法走子返回 400 + 错误码,局面不动) + router.post('/matches/:id/moves', botAuth, (req, res) => { + try { + const matchId = req.bot && req.bot.matchId ? Number(req.bot.matchId) : Number(req.params.id) + // 身份:优先角色 token 绑定的 side;浏览器调试允许 body.side 指定 + const side = String((req.bot && (req.bot.side || req.bot.forceSide)) || (req.body && req.body.side) || '').trim() || '' + if (side !== 'red' && side !== 'black') return fail(res, 400, 'BAD_SIDE', 'side 必须为 red 或 black(通过 token 或 body.side 指定)') + const move = String((req.body && req.body.move) || '').trim() + if (!move) return fail(res, 400, 'BAD_MOVE_FORMAT', '缺少 move 字段') + const result = service.submitMove(matchId, side, move) + if (!result.ok) { + const status = result.code === 'GAME_OVER' || result.code === 'MATCH_NOT_FOUND' ? 409 : 400 + return fail(res, status, result.code, result.error || result.reason) + } + // 去掉冗余快照,返回精简结果给 bot + return ok(res, { + accepted: true, side, move, + fen_after: result.fen_after, + check: result.check || '', + game_finished: !!result.game_finished, + result: result.result || '', winner: result.winner || '', + state: result.snapshot + }) + } catch (e) { return fail(res, 500, 'INTERNAL', String(e.message || e)) } + }) + + router.post('/matches/:id/start', adminAuth, (req, res) => { + try { + const r2 = service.startMatch(Number(req.params.id)) + if (!r2.ok) return fail(res, 400, r2.code || 'BAD_REQUEST', r2.error) + return ok(res, { snapshot: r2.snapshot }) + } catch (e) { return fail(res, 500, 'INTERNAL', String(e.message || e)) } + }) + + router.post('/matches/:id/foul', adminAuth, (req, res) => { + try { + const body = req.body || {} + const r2 = service.applyFoul(Number(req.params.id), body.side, body.reason || '违规') + if (!r2.ok) return fail(res, 400, 'BAD_REQUEST', r2.error) + return ok(res, { snapshot: r2.snapshot }) + } catch (e) { return fail(res, 500, 'INTERNAL', String(e.message || e)) } + }) + + router.post('/matches/:id/forfeit', adminAuth, (req, res) => { + try { + const body = req.body || {} + const r2 = service.forfeitSide(Number(req.params.id), body.side, body.reason || '判负') + if (!r2.ok) return fail(res, 400, 'BAD_REQUEST', r2.error) + return ok(res, { snapshot: r2.snapshot }) + } catch (e) { return fail(res, 500, 'INTERNAL', String(e.message || e)) } + }) + + router.post('/matches/:id/abort', adminAuth, (req, res) => { + try { + const body = req.body || {} + service.abortMatch(Number(req.params.id), body.reason || '手动中止') + return ok(res, {}) + } catch (e) { return fail(res, 500, 'INTERNAL', String(e.message || e)) } + }) + + router.delete('/matches/:id', adminAuth, (req, res) => { + try { + db.deleteMatch(Number(req.params.id)) + return ok(res, {}) + } catch (e) { return fail(res, 500, 'INTERNAL', String(e.message || e)) } + }) + + // 事件流水(滚动日志轮询:?after= 增量;?all=1 全量) + router.get('/matches/:id/events', adminAuth, (req, res) => { + try { + const mid = Number(req.params.id) + if (!db.getMatch(mid)) return fail(res, 404, 'NOT_FOUND', '比赛不存在') + const after = req.query.all ? 0 : Number(req.query.after || 0) + const events = db.listEventsAfter(mid, after) + return ok(res, { events }) + } catch (e) { return fail(res, 500, 'INTERNAL', String(e.message || e)) } + }) + + // 导出 MD 日志(可下载) + router.get('/matches/:id/log.md', adminAuth, (req, res) => { + try { + const md = service.buildMarkdownLog(Number(req.params.id)) + if (!md) return fail(res, 404, 'NOT_FOUND', '比赛不存在') + res.setHeader('Content-Type', 'text/markdown; charset=utf-8') + if (String(req.query.download) === '1') { + res.setHeader('Content-Disposition', `attachment; filename="bot_chess_match_${req.params.id}.md"`) + } + return res.send(md) + } catch (e) { return fail(res, 500, 'INTERNAL', String(e.message || e)) } + }) + + // ---- 裁判聊天接口 ---- + router.get('/chat/history', adminAuth, (req, res) => { + const orchestrator = require('./orchestrator') + return ok(res, { messages: orchestrator.chatHistory() }) + }) + + router.post('/chat', adminAuth, async (req, res) => { + try { + const orchestrator = require('./orchestrator') + const message = String((req.body && req.body.message) || '').trim() + if (!message) return fail(res, 400, 'BAD_REQUEST', 'message 不能为空') + const out = await orchestrator.processUserMessage(message) + return ok(res, out) + } catch (e) { return fail(res, 500, 'INTERNAL', String(e.message || e)) } + }) + + app.use(B, router) + // 注意:无 bindRoutes —— 本 lab 全部接口都在守卫之前注册并由自身鉴权接管 +} + +module.exports = { bindPublicRoutes } \ No newline at end of file diff --git a/src/server/thought_lab/labs/bot_chess/orchestrator.js b/src/server/thought_lab/labs/bot_chess/orchestrator.js new file mode 100644 index 0000000..50df91b --- /dev/null +++ b/src/server/thought_lab/labs/bot_chess/orchestrator.js @@ -0,0 +1,544 @@ +// ============================================================ +// bot_chess/orchestrator.js —— 三角色编排层 +// 1) 裁判 LLM:人只跟裁判对话。裁判通过“工具调用”(tool calling)操作棋台: +// check_bots(检测两个 bot)/ new_match / match_status / start_match / +// adjudicate(判罚/中止)/ match_log / recent_events +// 2) 两个 bot 会话(红/黑):完全隔离的对话记忆,通过 API(Bearer token) +// 拿局面(get_status) + 提交走子(submit_move),非法走子错误回喂后用工具重试。 +// 3) 比赛运行完全由 service.js 机械引擎裁决,本模块只做“组织与叙事”, +// 不再做任何合法性判断(规则只认 referee.js)。 +// 说明:裁判会话、bot 会话均保存在内存(重启后聊天上下文丢失,但对局数据在 SQLite)。 +// ============================================================ +const fs = require('fs') +const path = require('path') +const axios = require('axios') +const service = require('./service') +const db = require('./db') + +const CONFIG_PATH = path.join(__dirname, 'config.json') +const readConfig = () => { + try { + if (fs.existsSync(CONFIG_PATH)) return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')) + } catch {} + return {} +} + +const DEFAULT_AI_BASE = process.env.DASHSCOPE_API_HOST_FOR_OPENAI ? `${process.env.DASHSCOPE_API_HOST_FOR_OPENAI.replace(/\/+$/, '')}/chat/completions` : '' +const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) +const safeParse = (s, fb) => { try { return JSON.parse(s) } catch { return fb } } + +// ---------------- 百炼客户端(OpenAI 兼容 + 工具调用)---------------- +const aiInfo = () => { + const cfg = readConfig() + return { + apiBase: String(cfg.api_base || DEFAULT_AI_BASE), + apiKey: String(process.env.DASHSCOPE_API_KEY || cfg.api_key || ''), + refereeModel: String(cfg.referee_model || 'qwen-plus-latest'), + redModel: String(cfg.red_model || 'qwen-plus-latest'), + blackModel: String(cfg.black_model || 'qwen-plus-latest') + } +} + +// 单轮聊天(支持 tools;返回 message 原文以便处理 tool_calls) +const chatOnce = async ({ messages, model, tools, temperature = 0.7, maxTokens = 4096 }) => { + const ai = aiInfo() + if (!ai.apiKey) throw new Error('dashscope_api_key_missing') + const timeoutMs = Math.max(60000, Number(readConfig().llm_timeout_ms) || 240000) + const payload = { model: model || ai.refereeModel, messages, temperature } + if (tools && tools.length) payload.tools = tools + if (maxTokens) payload.max_tokens = maxTokens + const resp = await axios.post(ai.apiBase, payload, { + timeout: timeoutMs, + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${ai.apiKey}` } + }) + const msg = (resp && resp.data && resp.data.choices && resp.data.choices[0] && resp.data.choices[0].message) || {} + return { content: String(msg.content || ''), tool_calls: msg.tool_calls || [] } +} + +// 工具调用主循环:模型要工具就执行、回注结果,再要就再执行,直到给出最终文本 +const chatWithTools = async ({ messages, model, tools, runTool, maxRounds = 8 }) => { + const hist = (messages || []).map(m => m) + const toolEvents = [] + for (let i = 0; i < maxRounds; i++) { + const msg = await chatOnce({ messages: hist, model, tools }) + if (msg.tool_calls && msg.tool_calls.length) { + hist.push({ + role: 'assistant', + content: msg.content || null, + tool_calls: msg.tool_calls.map(tc => ({ + id: tc.id, type: 'function', + function: { name: tc.function && tc.function.name, arguments: tc.function && tc.function.arguments } + })) + }) + for (const tc of msg.tool_calls) { + let result + try { + const args = safeParse(tc.function.arguments, {}) + result = await runTool(tc.function.name, args) + } catch (e) { + result = { ok: false, error: String(e.message || e) } + } + toolEvents.push({ name: tc.function.name, ok: !!result.ok, summary: String(result.summary || result.error || 'ok') }) + hist.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify(result) }) + } + continue + } + return { reply: msg.content || '', toolEvents } + } + return { reply: '(裁判工具执行轮次过多,请重试或换一种说法)', toolEvents } +} + +// ---------------- 公共工具定义(裁判 LLM 用)---------------- +const refereeTools = () => [ + { + type: 'function', + function: { + name: 'check_bots', description: '检测红/黑两个 bot 模型是否可用(会各问一句话,看能否应答)。用于开局前就绪检查。', + parameters: { type: 'object', properties: {} } + } + }, + { + type: 'function', + function: { + name: 'new_match', description: '创建一场比赛(只创建,不开局)。返回比赛 ID、红黑 bot 与赛制。', + parameters: { + type: 'object', + properties: { + title: { type: 'string', description: '比赛名称' }, + red_bot: { type: 'string', description: '红方 bot 名称' }, + black_bot: { type: 'string', description: '黑方 bot 名称' }, + best_of: { type: 'integer', description: '局数,默认 3(三局两胜)' } + }, + required: [] + } + } + }, + { + type: 'function', + function: { + name: 'match_status', description: '查看当前比赛的比分、当前局局面(FEN)、轮到谁、犯规计数与最近着法。', + parameters: { type: 'object', properties: { match_id: { type: 'integer', description: '比赛ID,缺省用最近创建的比赛' } } } + } + }, + { + type: 'function', + function: { + name: 'start_match', description: '让比赛开始:两个 bot 自动对弈(先红后黑,引擎逐手裁决)。开局前请先确认 bot 已就绪且用户同意。', + parameters: { type: 'object', properties: { match_id: { type: 'integer', description: '比赛ID' } }, required: [] } + } + }, + { + type: 'function', + function: { + name: 'adjudicate', description: '裁定:判某方负(forfeit,如无限犯规/失联)或中止整场(abort)。', + parameters: { + type: 'object', + properties: { + action: { type: 'string', enum: ['forfeit', 'abort'] }, + side: { type: 'string', enum: ['red', 'black'], description: '被判负的一方(forfeit 时必填)' }, + match_id: { type: 'integer' }, + reason: { type: 'string', description: '裁定原因' } + }, + required: ['action'] + } + } + }, + { + type: 'function', + function: { + name: 'match_log', description: '导出比赛日志(Markdown),用于复盘总结。', + parameters: { type: 'object', properties: { match_id: { type: 'integer' } } } + } + }, + { + type: 'function', + function: { + name: 'recent_events', description: '查看最近事件流水(建赛/开局/走子/犯规/终局),用于向用户播报局势。', + parameters: { type: 'object', properties: { match_id: { type: 'integer' } } } + } + } +] + +// ---------------- 主持人状态(内存)---------------- +const judge = { + activeMatchId: 0, + session: [], // 裁判会话消息(含 system) + startedMatches: new Set() // 已启动过 driver 的比赛(防重复) +} + +// 裁判系统提示词(协议讲解 + 行为约束) +const JUDGE_SYSTEM = () => { + const cfg = readConfig() + return [ + `你是「机器人象棋」比赛主持人兼裁判(TRAE-Toolbox bot_chess lab)。`, + `背景:红黑两个大模型 bot 在同一个机制下自动对弈;你的职责是调度、播报与裁定,最终合法性一律由代码引擎把关,你不用也不能代替引擎判规则。`, + `规则要点(引擎已实现,你只需了解):`, + `- 中国象棋;坐标:列 a-i(左到右),行 0-9(0=红方底线,9=黑方底线);着法格式如 h2-e2(炮二平五)。棋子:红 R车N马B相A士K帅C炮P兵;黑小写。`, + `- 赛制:默认三局两胜,每局红黑对调(第 1 局红方 bot 执红先行)。`, + `- 胜负与和:将死/困毙/飞将直取判胜;连续 120 手无吃子无进兵、三次重复局面、子力不足判和。`, + `- 犯规:超时(单步 ${Number(cfg.move_timeout_seconds) || 180} 秒)、非法走子、失联都会累计犯规,${Number(cfg.foul_limit) || 3} 次判负本局。`, + `行为约束:`, + `1. 开局流程:用户说“检测/开始检测”时,先调用 check_bots 检查两个 bot;把检测结果汇报给用户,并说明比赛设计(谁执红谁执黑、几局几胜);必须征得用户明确同意后再调用 new_match + start_match。`, + `2. 比赛自动进行时不要打扰用户;只有比分变化、犯规、终局等关键事件才播报。`, + `3. 用 adjudicate 做裁定前先向用户说明原因。`, + `4. 语言:简洁中文,像专业裁判主持。` + ].join('\n') +} + +// ---------------- 工具执行器(裁判用)---------------- +const pickMatchId = (args, fallback) => Number(args && args.match_id) || judge.activeMatchId || fallback || 0 + +// terse:给 LLM 看的摘要(不暴露 token) +const terse = (snap) => { + if (!snap) return { ok: false, error: 'snapshot_empty' } + const m = snap.match + return { + ok: true, summary: `比赛#${m.id}《${m.title}》:${m.red_bot}(红) vs ${m.black_bot}(黑)`, + match_id: m.id, status: m.status, score: `红 ${m.score_red} : ${m.score_black} 黑`, + winner: m.winner || '未定', + game: snap.game ? { game_no: snap.game.game_no, status: snap.game.status, result: snap.game.result || '进行中', side_to_move: snap.game.turn === 'b' ? 'black' : 'red', move_count: snap.game.move_count, fouls: `红 ${snap.game.red_fouls} / 黑 ${snap.game.black_fouls}` } : null, + last_moves: snap.last_moves && snap.last_moves.slice(0, 8), + fen: snap.board ? snap.board.fen : null + } +} + +const judgeExecutor = async (name, args) => { + const cfg = readConfig() + switch (name) { + case 'check_bots': { + const ai = aiInfo() + const probe = async (model, sideName) => { + if (!model) return { ok: false, error: '未配置模型' } + try { + const r = await chatOnce({ + model, maxTokens: 120, temperature: 0.3, + messages: [ + { role: 'system', content: `你是${sideName}方中国象棋 bot,用一句话自报家门即可。` }, + { role: 'user', content: '检测连接:你的名字?你准备好了吗?' } + ] + }) + return { ok: true, reply: (r.content || '').slice(0, 120) } + } catch (e) { + return { ok: false, error: String(e.message || e) } + } + } + const redModel = cfg.red_model || ai.redModel + const blackModel = cfg.black_model || ai.blackModel + const red = await probe(redModel, '红') + const black = await probe(blackModel, '黑') + return { + ok: red.ok && black.ok, summary: `红(${redModel}):${red.ok ? '就绪' : '失败 ' + red.error};黑(${blackModel}):${black.ok ? '就绪' : '失败 ' + black.error}`, + red: { model: redModel, ready: red.ok, reply: red.reply, error: red.error }, + black: { model: blackModel, ready: black.ok, reply: black.reply, error: black.error } + } + } + case 'new_match': { + const snap = service.createMatch({ + title: args.title, red_bot: args.red_bot, black_bot: args.black_bot, + best_of: args.best_of || cfg.best_of + }) + judge.activeMatchId = snap.match.id + return terse(snap) + } + case 'match_status': { + const id = pickMatchId(args) + const snap = id ? service.buildSnapshot(id) : null + return snap ? terse(snap) : { ok: false, error: '没有可用的比赛,请先 new_match' } + } + case 'start_match': { + const id = pickMatchId(args) + if (!id) return { ok: false, error: '没有比赛可开始,请先 new_match' } + const started = service.startMatch(id) + if (!started.ok) return started + spawnBots(id) + return { ok: true, summary: `比赛#${id} 已开始,两 bot 自动对弈中`, match_id: id, snapshot: terse(service.buildSnapshot(id)) } + } + case 'adjudicate': { + const id = pickMatchId(args) + if (args.action === 'forfeit') { + if (!args.side) return { ok: false, error: 'forfeit 需要指定 side' } + const r = service.forfeitSide(id, args.side, args.reason || '裁判裁定') + return { ok: r.ok, summary: `${args.side} 判负`, snapshot: r.ok ? terse(r.snapshot) : undefined, error: r.error } + } + if (args.action === 'abort') { + const r = service.abortMatch(id, args.reason || '裁判裁定中止') + return { ok: r.ok, summary: '比赛已中止', error: r.error } + } + return { ok: false, error: '未知裁定动作' } + } + case 'match_log': { + const id = pickMatchId(args) + const md = id ? service.buildMarkdownLog(id) : '' + if (!md) return { ok: false, error: '无日志' } + return { ok: true, summary: `日志 ${md.length} 字符;URL: ${cfg.chat_base_url || 'http://localhost:8976'}/api/thought_lab/bot_chess/matches/${id}/log.md`, md: md.slice(0, 6000) } + } + case 'recent_events': { + const id = pickMatchId(args) + const evs = id ? db.listEvents(id) : [] + return { ok: true, summary: `共 ${evs.length} 条事件`, events: evs.slice(-30).map(e => `[${e.created_at}] ${e.side ? '[' + e.side + '] ' : ''}${e.content}`) } + } + default: + return { ok: false, error: `未知工具 ${name}` } + } +} + +// ---------------- 对外:裁判对话接口 ---------------- +const processUserMessage = async (text) => { + const msg = String(text || '').trim() + if (!msg) return { reply: '' } + if (!judge.session.length) judge.session.push({ role: 'system', content: JUDGE_SYSTEM() }) + judge.session.push({ role: 'user', content: msg }) + try { + const { reply, toolEvents } = await chatWithTools({ + messages: judge.session, + model: aiInfo().refereeModel, + tools: refereeTools(), + runTool: judgeExecutor, + maxRounds: 10 + }) + judge.session.push({ role: 'assistant', content: reply }) + // 内存裁剪:只保留最近 40 条 + if (judge.session.length > 44) { + judge.session = [judge.session[0], ...judge.session.slice(judge.session.length - 40)] + } + return { reply, tool_events: toolEvents, match_id: judge.activeMatchId || 0 } + } catch (e) { + const errText = String(e.message || e) + const friendly = errText.includes('dashscope_api_key_missing') + ? '尚未配置百炼 API Key(请设置环境变量 DASHSCOPE_API_KEY)' + : `调用裁判模型失败:${errText}` + return { reply: friendly, tool_events: [], error: errText } + } +} + +const chatHistory = () => judge.session + .filter(m => m.role !== 'system') + .map(m => ({ role: m.role, content: m.content })) + .slice(-40) + +// ---------------- 两个 bot 会话(互相隔离)---------------- +const BOT_SYSTEM = (side) => [ + `你是中国象棋机器人,本场执${side === 'red' ? '红' : '黑'}。你的对手是另一个大模型,由机械裁判裁决,规则不会由你或我说了算。`, + `坐标:列 a~i(左到右,红方视角);行 0~9(0=红方底线在下,9=黑方底线在上)。`, + `着法格式严格为 -,如 h2-e2(红方代表炮二平五)。`, + `须知:必须走合法着法(别蹩马腿、别送将、别让将帅照面);如果裁判返回“着法非法+错误码”,仔细看原因后换一步,绝不重复同一手、绝不编造已落子。`, + `策略:开局可考虑中炮/屏风马等常规布局;先保护将帅,别把帅/将暴露在空列上(会被对方飞将直取)。`, + `只输出针对当前局面的着法;不在你的回合时输出“等待”。不要输出长解释。` +].join('\n') + +const botStateForSide = async (matchId, token, side) => { + // HTTP 优先(与外部 bot 同一协议);失败自动兜底为进程内快照,避免 + // chat_base_url 端口对不上时 driver 空转到超时判负 + const cfg = readConfig() + try { + const r = await axios.get(`${cfg.chat_base_url || 'http://localhost:8976'}/api/thought_lab/bot_chess/matches/${matchId}/state?side=${side}`, { + timeout: 15000, + headers: { Authorization: `Bearer ${token}` } + }) + if (r.data && r.data.ok) return r.data + } catch (e) { /* 走兜底 */ } + const snap = service.buildSnapshot(matchId) + if (!snap) return { ok: false, error: 'empty' } + const game = snap.game + return { + ok: true, http: false, your_side: side, + match: snap.match, + game: game ? { + id: game.id, game_no: game.game_no, status: game.status, result: game.result || '', + winner: game.winner || '', move_count: game.move_count, turn: game.turn, + red_fouls: game.red_fouls, black_fouls: game.black_fouls, side_to_move: game.turn === 'b' ? 'black' : 'red' + } : null, + board: snap.board, + last_moves: snap.last_moves + } +} + +const botSubmitFallback = (matchId, side, move) => { + // 进程内兜底走子(返回结构与 HTTP 版对齐) + const res = service.submitMove(matchId, side, move) + if (res.ok && res.accepted) { + return { ok: true, accepted: true, fen_after: res.fen_after, check: res.check || '', game_finished: !!res.game_finished, result: res.result || '', winner: res.winner || '' } + } + if (res.ok) return { ok: true, accepted: false, code: res.code || 'N/A', reason: res.reason || res.error } + return { ok: false, code: res.code, reason: res.reason || res.error, fen: res.fen, turn: res.turn } +} + +const botSubmit = async (matchId, token, side, move) => { + const cfg = readConfig() + try { + const r = await axios.post(`${cfg.chat_base_url || 'http://localhost:8976'}/api/thought_lab/bot_chess/matches/${matchId}/moves`, { move }, { + timeout: 15000, + headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` } + }) + if (r.data) return r.data + } catch (e) { /* 兜底 */ } + return botSubmitFallback(matchId, side, move) +} + +const botFoul = (matchId, side, reason) => { + // 犯规是编排层的赛道判断,直接走进程内机械层,不绕 HTTP(避免依赖 referee token 配置) + return service.applyFoul(matchId, side, reason) +} + +// 单边 bot 对弈循环 +const driveBotSession = async (matchId, side) => { + const ai = aiInfo() + const match = db.getMatch(matchId) + if (!match) return + const token = side === 'red' ? match.red_token : match.black_token + const model = side === 'red' ? (match.red_model || ai.redModel) : (match.black_model || ai.blackModel) + const msgs = [{ role: 'system', content: BOT_SYSTEM(side) }] + let illegalStreak = 0 + const cfg = readConfig() + const retryLimit = Math.max(1, Number(cfg.illegal_retry) || 2) + addJudgeEvent(matchId, `[driver] ${side} 启动:模型=${model},API=${cfg.chat_base_url || 'http://localhost:8976'}(HTTP 失败自动内存兜底)`) + + while (true) { + // 整场级退出条件 + const st = await botStateForSide(matchId, token, side) + if (!st.ok) { await sleep(3000); continue } + const m = st.match, g = st.game + if (!m || m.status !== 'active' || !g || g.status !== 'playing') { + if (m && m.status === 'finished') { addJudgeEvent(matchId, `[driver] ${side} 检测到比赛结束,退出`); break } + await sleep(2000); continue + } + // 轮到谁:统一用 turn 字段判断(HTTP 与内存兜底都提供),不依赖 side_to_move 可选字段 + const turnSide = g.turn === 'b' ? 'black' : 'red' + if (turnSide !== side) { await sleep(1200); continue } // 未轮到 + + // 组装 bot 提示:局面给结构化信息(不给 bot 解析 FEN 的机会) + const summary = describeForBot(st, side, { ascii: cfg.bot_prompt_ascii === true }) + const myLast = st.last_moves && st.last_moves.length ? st.last_moves[st.last_moves.length - 1] : '' + const moveNo = (Number(g.move_count) || 0) + 1 + const useTools = cfg.bot_use_tools === true + let accepted = false + + if (!useTools) { + // ---------- 文本着法模式(快/稳,推荐):局面进提示词,模型只回一行着法 ---------- + const ask = `轮到你了:第 ${moveNo} 手,执${side === 'red' ? '红' : '黑'}。\n${summary}${myLast ? '\n上一步对方着法:' + myLast : ''}\n\n现在请输出你的着法。格式要求:严格一行 起点-终点(坐标),例如 h2-e2 / b0c2。禁止解释、禁止写中文记谱(如“炮二平五”)、禁止提棋盘、禁止其他格式。若确实没轮到你,只输出“等待”。` + msgs.push({ role: 'user', content: ask }) + let mv = '' + let rawReply = '' + try { + // 放宽输出的 token 上限,避免模型长篇分析被截断导致着法丢失 + const once = await chatOnce({ messages: msgs, model, maxTokens: 1024, temperature: 0.4 }) + rawReply = String(once.content || '').trim() + mv = extractMove(rawReply) + } catch (e) { + addJudgeEvent(matchId, `[driver] ${side} 调模型失败:${String(e.message || e).slice(0, 120)}(将重试)`) + await sleep(1500) + } + if (mv) { + const res = await botSubmit(matchId, token, side, mv) + if (res && res.ok && res.accepted) { + accepted = true + // 己方着法回注,让模型在下一轮能"记得"自己上一步(保持记忆链) + msgs.push({ role: 'assistant', content: mv }) + } else { + // 非法着法:回喂错误让模型重想 + msgs.push({ role: 'user', content: `你刚才提交的 ${mv} 非法:${res && res.reason || res && res.error || ''}(错误码 ${res && res.code || '?'})。请重新给一行合法着法,格式 起点-终点(坐标)。` }) + addJudgeEvent(matchId, `[driver] ${side} 着法被拒:${mv}(${res && res.code || '?'})`) + } + } else if (rawReply) { + // 模型回复了内容但没坐标:回喂原文并纠正格式,给它一次自我修正机会 + msgs.push({ role: 'user', content: `你刚才的回复是:「${rawReply.slice(0, 120)}」——这不是标准着法(可能误用了中文记谱或分析)。请只输出一行坐标着法,格式:起点-终点(如 h2-e2)。` }) + addJudgeEvent(matchId, `[driver] ${side} 回复无坐标着法,已回喂纠错:${rawReply.slice(0, 60)}`) + } else { + addJudgeEvent(matchId, `[driver] ${side} 未得到着法,本手作废重试`) + } + } else { + // ---------- 工具调用模式(还原外部 bot 协议,更慢)---------- + msgs.push({ role: 'user', content: `(第 ${moveNo} 手,轮到你)${summary}${myLast ? '\n上一步对方着法:' + myLast : ''}\n请使用 get_status 确认后,用 submit_move 提交你的着法。` }) + const toolScope = { accepted: false } + const botExecutor = async (name, args) => { + if (name === 'get_status') return botStateForSide(matchId, token, side) + if (name === 'submit_move') { + const mv = String(args.move || '').trim() + const res = await botSubmit(matchId, token, side, mv) + if (res && res.ok && res.accepted) { + toolScope.accepted = true + return { ok: true, summary: '着法已接受', fen_after: res.fen_after, game_finished: !!res.game_finished, result: res.result || '', check: res.check || '' } + } + return { ok: false, summary: '着法未通过', code: res && res.code, reason: res && res.reason || res && res.error, fen: res && res.fen } + } + return { ok: false, error: `未知工具 ${name}` } + } + const botTools = [ + { type: 'function', function: { name: 'get_status', description: '获取当前局面(FEN/棋盘/是否轮到你)。', parameters: { type: 'object', properties: {} } } }, + { type: 'function', function: { name: 'submit_move', description: '提交你的着法(格式如 h2-e2)。', parameters: { type: 'object', properties: { move: { type: 'string' } }, required: ['move'] } } } + ] + const botTurn = await chatWithTools({ messages: msgs, model, tools: botTools, runTool: botExecutor, maxRounds: 8 }) + .catch(e => { + addJudgeEvent(matchId, `[driver] ${side} 调模型失败:${String(e.message || e).slice(0, 120)}(将重试)`) + return { reply: '', toolEvents: [], error: String(e.message || e) } + }) + const lastMsg = String(botTurn.reply || '') + if (lastMsg) msgs.push({ role: 'assistant', content: lastMsg }) + if (!toolScope.accepted) { + const mv = extractMove(lastMsg) + if (mv) { + const res = await botSubmit(matchId, token, side, mv) + accepted = !!(res && res.ok && res.accepted) + if (!accepted) msgs.push({ role: 'user', content: `你提交的 ${mv} 非法,请重新给一步合法着法。` }) + } else { + msgs.push({ role: 'user', content: '你还需要提交一步合法着法(使用 submit_move 工具)。' }) + } + } else { + accepted = true + } + } + + if (accepted) { + illegalStreak = 0 + // 本手结束:直接从下一轮循环读最新状态(含可能的新局) + await sleep(300) + continue + } + // 没提交成功 → 非法重试累积(超限记犯规,由机械层判) + illegalStreak++ + if (illegalStreak > retryLimit) { + await botFoul(matchId, side, `连续 ${illegalStreak} 次未提交合法着法`) + addJudgeEvent(matchId, `${side} 连续非法走子超限,已记犯规`) + illegalStreak = 0 + await sleep(1500) + } + // 裁剪 bot 会话避免上下文膨胀拖慢模型(保留 system + 最近 8 条,局面每轮完整重发) + if (msgs.length > 12) msgs.splice(1, msgs.length - 10) + } +} + +const extractMove = (text) => { + const m = String(text || '').match(/([a-i][0-9])[-:]([a-i][0-9])/) + if (m) return `${m[1]}-${m[2]}` + const m2 = String(text || '').match(/([a-i][0-9])([a-i][0-9])/) + return m2 ? `${m2[1]}-${m2[2]}` : '' +} + +const addJudgeEvent = (matchId, content) => { + try { if (matchId) db.addEvent({ match_id: matchId, kind: 'note', content }) } catch {} +} + +// 启动两个 bot 会话(幂等:每场只启动一次) +const spawnBots = (matchId) => { + if (judge.startedMatches.has(matchId)) return + judge.startedMatches.add(matchId) + driveBotSession(matchId, 'red') + driveBotSession(matchId, 'black') + addJudgeEvent(matchId, '两个 bot driver 已启动') +} + +// 便于 index.js 在服务内预热 +const describeForBot = (st, side, opts) => { + if (!st || !st.board) return '(当前无局面)' + const lines = [] + if (opts && opts.ascii && st.board.ascii) lines.push(st.board.ascii) + const redList = (st.board.pieces.red || []).map(p => `${p.cn || p.piece}@${p.pos}`).join(', ') + const blackList = (st.board.pieces.black || []).map(p => `${p.cn || p.piece}@${p.pos}`).join(', ') + lines.push(`FEN: ${st.board.fen}`) + lines.push(`红方子力:${redList}`) + lines.push(`黑方子力:${blackList}`) + lines.push(`当前轮到:${st.board.side_to_move === 'red' ? '红' : '黑'};你执${side === 'red' ? '红' : '黑'}。`) + return lines.join('\n') +} + +module.exports = { processUserMessage, chatHistory, spawnBots, chatWithTools, chatOnce } \ No newline at end of file diff --git a/src/server/thought_lab/labs/bot_chess/referee.js b/src/server/thought_lab/labs/bot_chess/referee.js new file mode 100644 index 0000000..3f0918e --- /dev/null +++ b/src/server/thought_lab/labs/bot_chess/referee.js @@ -0,0 +1,473 @@ +// ============================================================ +// bot_chess/referee.js —— 中国象棋规则引擎(纯函数,无 IO 依赖) +// +// 职责:FEN 解析 / 合法着法生成 / 走子校验(蹩马腿、塞象眼、 +// 炮架、将帅照面、送将)/ 将军 / 将死 / 困毙 / 胜负判定。 +// 本模块只做“规则”,不做数据库、不做 HTTP,保证可单测、可复用。 +// +// 坐标约定(与已知参考实现 xiangqi-model-match 保持同套协议,红方视角): +// 列:a~i,从左到右(红方视角) +// 行:0~9,从下到上(0 = 红方底线,9 = 黑方底线) +// 着法格式:- 或 ,例如 h2-e2 / h2e2(炮二平五) +// +// FEN 约定(国际通用中国象棋 FEN,如 pychess / xqf 格式): +// 第 1 段从上到下共 10 行,每行 9 格; +// 大写 = 红子,小写 = 黑子;/ 分行;数字 = 连续空格数; +// r=车 n=马 b=象 a=士 k=帅/将 c=炮 p=兵/卒(标准 FEN:马=n、象=b) +// 第 1 行是棋盘顶部(黑方底线),即 FEN 第 i 行 ↔ 内部坐标第 (9-i) 行 +// 第 2 段:w/b —— w=红方走,b=黑方走 +// 例:rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1 +// ============================================================ + +const START_FEN = 'rnbakabnr/9/1c5c1/p1p1p1p1p/9/9/P1P1P1P1P/1C5C1/9/RNBAKABNR w - - 0 1' + +const ROWS = 10 // 行数 0..9 +const COLS = 9 // 列数 0..8 +const FILES = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'] + +// 阵营:内部统一用 'red' 红 / 'black' 黑;FEN turn 用 'w'(红走)/ 'b'(黑走) +const IN = { red: 'red', black: 'black' } +const TURN_SIDE = { w: 'red', b: 'black' } // FEN turn -> side +const SIDE_TURN = { red: 'w', black: 'b' } // side -> FEN turn + +// 错误码(提交走子非法时返回给调用方 / bot 可读) +const E = { + OUT_OF_BOARD: 'OUT_OF_BOARD', // 起/终点越界 + NO_PIECE: 'NO_PIECE', // 起点没有我方棋子 + WRONG_SIDE: 'WRONG_SIDE', // 起点是对手棋子 + CAPTURE_OWN: 'CAPTURE_OWN', // 落点是我方棋子 + ILLEGAL_MOVE: 'ILLEGAL_MOVE', // 不符合该子走法(通用) + BLOCKED_HORSE: 'BLOCKED_HORSE', // 蹩马腿 + BLOCKED_ELEPHANT: 'BLOCKED_ELEPHANT', // 塞象眼 + CANNON_SCREEN: 'CANNON_SCREEN', // 炮吃子需且仅需隔一个炮架 + FLYING_GENERAL: 'FLYING_GENERAL', // 将帅照面(走完两将同线无遮挡) + SELF_CHECK: 'SELF_CHECK', // 送将(走完自己被将军) +} + +// ---------- 基础工具 ---------- +const inBoard = (r, c) => r >= 0 && r < ROWS && c >= 0 && c < COLS +const colToFile = c => FILES[c] || '' +const fileToCol = f => FILES.indexOf(f) +const sq = (r, c) => colToFile(c) + String(r) // 坐标串,如 h2 +const parseSquare = s => { // 'h2' -> {r,c} + const m = String(s || '').trim().match(/^([a-i])([0-9])$/) + if (!m) return null + return { r: Number(m[2]), c: fileToCol(m[1]) } +} +// 着法串:'h2-e2' 或 'h2e2' -> {from:{r,c}, to:{r,c}},非法返回 null +const parseMove = s => { + const m = String(s || '').trim().match(/^([a-i])([0-9])[-:]?([a-i])([0-9])$/) + if (!m) return null + return { + from: { r: Number(m[2]), c: fileToCol(m[1]) }, + to: { r: Number(m[4]), c: fileToCol(m[3]) } + } +} + +// ---------- 棋盘表示:board[r][c],r=0 红底线,r=9 黑底线 ---------- +// 棋盘格子存单字符:红 'KA BNRCP'(K帅 A士 B相 N马 R车 C炮 P兵)黑同小写;空='' + +// 解析 FEN 为棋盘(board 为 10x9 二维数组) +const parseFen = (fen) => { + const parts = String(fen || START_FEN).trim().split(/\s+/) + const rows = (parts[0] || '').split('/') + if (rows.length !== ROWS) throw new Error('fen_bad_rows') + const board = [] + for (let i = 0; i < ROWS; i++) { // i=0 是 FEN 第 1 行(顶部=黑底线) + const row = rows[i] + const ourRow = ROWS - 1 - i // 顶部行 ↔ 内部第 9 行 + const line = [] + let c = 0 + for (const ch of row) { + if (ch >= '1' && ch <= '9') { c += Number(ch); continue } + line[boardFillIndex(c)] = ch + c++ + } + // line 为稀疏数组,补齐为 9 格 + const full = [] + for (let x = 0; x < COLS; x++) full[x] = line[x] || '' + board[ourRow] = full + } + const turn = String(parts[1] || 'w').toLowerCase() === 'b' ? 'b' : 'w' + return { board, turn } +} + +// 辅助:把字符写进稀疏行数组的补位逻辑(等价于 line.push,但显式按列索引) +const boardFillIndex = (c) => c + +// 棋盘序列化为 FEN +const toFen = (board, turn) => { + const lines = [] + for (let ourRow = ROWS - 1; ourRow >= 0; ourRow--) { // 从第 9 行(顶) 反向输出 + let line = '' + let empty = 0 + for (let c = 0; c < COLS; c++) { + const ch = board[ourRow][c] + if (!ch) { empty++; continue } + if (empty) { line += empty; empty = 0 } + line += ch + } + if (empty) line += empty + if (line === '') line = '9' // FEN 空行 = 9 个空位 + lines.push(line) + } + return `${lines.join('/')} ${turn || 'w'} - - 0 1` +} + +// 位置去重复用的“局面指纹”(只含摆子,不含轮次计数) +const positionKey = (board) => { + const rows = [] + for (let r = 0; r < ROWS; r++) rows.push(board[r].join('')) + return rows.join('/') +} + +// ---------- 棋子信息 ---------- +const pieceInfo = (ch) => { + if (!ch) return null + const isRed = ch === ch.toUpperCase() + return { side: isRed ? 'red' : 'black', type: ch.toUpperCase() } +} +const findKing = (board, side) => { + for (let r = 0; r < ROWS; r++) { + for (let c = 0; c < COLS; c++) { + const p = pieceInfo(board[r][c]) + if (p && p.side === side && p.type === 'K') return { r, c } + } + } + return null +} +const isEnemy = (board, r, c, side) => { + const p = pieceInfo(board[r][c]) + return !!p && p.side !== side +} +const isOwn = (board, r, c, side) => { + const p = pieceInfo(board[r][c]) + return !!p && p.side === side +} + +// 宫格(帅/士活动区):红 0..2 行,黑 7..9 行,列 3..5 +const inPalace = (r, c, side) => { + if (c < 3 || c > 5) return false + return side === 'red' ? (r >= 0 && r <= 2) : (r >= 7 && r <= 9) +} + +// ---------- 伪合法着法(单子合法走法,未过滤“走完被将军/将帅照面”)---------- +// 返回 { tc, tr, code } 数组;code 为 null 表示合法 +const pseudoTargets = (board, r, c) => { + const p = pieceInfo(board[r][c]) + const out = [] + if (!p) return out + const { side, type } = p + const add = (tr, tc, code) => { if (!inBoard(tr, tc)) return; if (isOwn(board, tr, tc, side)) return; out.push({ tr, tc, code }) } + const pathClear = (tr, tc) => { + const dr = Math.sign(tr - r), dc = Math.sign(tc - c) + let cr = r + dr, cc = c + dc + while (cr !== tr || cc !== tc) { + if (board[cr][cc]) return false + cr += dr; cc += dc + } + return true + } + + if (type === 'R') { // 车:横竖走,路径无子 + for (const [dr, dc] of [[1,0],[-1,0],[0,1],[0,-1]]) { + let tr = r + dr, tc = c + dc + while (inBoard(tr, tc)) { + add(tr, tc, isEnemy(board, tr, tc, side) || !board[tr][tc] ? null : 'CAPTURE_OWN') + if (board[tr][tc]) break + tr += dr; tc += dc + } + } + } else if (type === 'N') { // 马:日字,蹩马腿 + const steps = [[1,2],[2,1],[2,-1],[1,-2],[-1,-2],[-2,-1],[-2,1],[-1,2]] + for (const [dr, dc] of steps) { + const tr = r + dr, tc = c + dc + if (!inBoard(tr, tc)) continue + const legR = r + (Math.abs(dr) === 2 ? dr / 2 : 0) + const legC = c + (Math.abs(dc) === 2 ? dc / 2 : 0) + add(tr, tc, board[legR][legC] ? 'BLOCKED_HORSE' : null) + } + } else if (type === 'B') { // 象:田字,塞象眼,不过河 + if ((side === 'red' && r <= 4) || (side === 'black' && r >= 5)) { + for (const [dr, dc] of [[2,2],[2,-2],[-2,2],[-2,-2]]) { + const tr = r + dr, tc = c + dc + if (!inBoard(tr, tc)) continue + const eyeR = r + dr / 2, eyeC = c + dc / 2 + add(tr, tc, board[eyeR][eyeC] ? 'BLOCKED_ELEPHANT' : null) + } + } + } else if (type === 'A') { // 士:仕宫斜一步 + if (inPalace(r, c, side)) { + for (const [dr, dc] of [[1,1],[1,-1],[-1,1],[-1,-1]]) { + add(r + dr, c + dc, null) + } + } + } else if (type === 'K') { // 帅/将:宫中斜一步(直线),外加“将帅照面直吃” + if (inPalace(r, c, side)) { + for (const [dr, dc] of [[1,0],[-1,0],[0,1],[0,-1]]) add(r + dr, c + dc, null) + } + // 飞将直吃:同列前方若直通敌将则允许吃 + const dr = side === 'red' ? 1 : -1 + let tr = r + dr + while (inBoard(tr, c) && !board[tr][c]) tr += dr + if (inBoard(tr, c)) { + const tp = pieceInfo(board[tr][c]) + if (tp && tp.side !== side && tp.type === 'K') add(tr, c, null) + } + } else if (type === 'C') { // 炮:走子如车(落点为空),吃子需隔一个炮架 + for (const [dr, dc] of [[1,0],[-1,0],[0,1],[0,-1]]) { + let tr = r + dr, tc = c + dc + // 非吃:空行走到空位 + while (inBoard(tr, tc) && !board[tr][tc]) { add(tr, tc, null); tr += dr; tc += dc } + // 已碰到第一个子:找第二个子当目标 + if (!inBoard(tr, tc)) continue + tr += dr; tc += dc + while (inBoard(tr, tc) && !board[tr][tc]) { tr += dr; tc += dc } + if (inBoard(tr, tc)) add(tr, tc, isEnemy(board, tr, tc, side) ? null : 'CAPTURE_OWN') + } + } else if (type === 'P') { // 兵/卒:未过河只能前进,过河后可横走(不能后退) + const fwd = side === 'red' ? 1 : -1 + const crossed = side === 'red' ? r >= 5 : r <= 4 // 已过河 + add(r + fwd, c, null) + if (crossed) { add(r, c + 1, null); add(r, c - 1, null) } + } + return out +} + +// 某方所有伪合法落点(用于快速判定“是否被攻击”) +const attackedBy = (board, side, pos) => { + for (let r = 0; r < ROWS; r++) { + for (let c = 0; c < COLS; c++) { + const p = pieceInfo(board[r][c]) + if (!p || p.side !== side) continue + const targets = pseudoTargets(board, r, c) + for (const t of targets) { + if (!t.code && t.tr === pos.r && t.tc === pos.c) return true + } + } + } + return false +} + +// 是否被将军 +const inCheck = (board, side) => { + const king = findKing(board, side) + if (!king) return false + const enemy = side === 'red' ? 'black' : 'red' + return attackedBy(board, enemy, king) +} + +// 落子(返回新棋盘 + 被吃的子) +const applyMove = (board, from, to) => { + const nb = board.map(row => row.slice()) + const moving = nb[from.r][from.c] + nb[to.r][to.c] = moving + nb[from.r][from.c] = '' + return { board: nb, captured: board[to.r][to.c] || '' } +} + +// 将帅是否照面(两将同列且中间无子) +const kingsFace = (board) => { + const rk = findKing(board, 'red'), bk = findKing(board, 'black') + if (!rk || !bk || rk.c !== bk.c) return false + const lo = Math.min(rk.r, bk.r), hi = Math.max(rk.r, bk.r) + for (let r = lo + 1; r < hi; r++) if (board[r][rk.c]) return false + return true +} + +// ---------- 着法合法性校验(对外主入口)---------- +// 返回 { ok:true } 或 { ok:false, code, reason } +const validateMove = (board, side, from, to) => { + if (!inBoard(from.r, from.c) || !inBoard(to.r, to.c)) return { ok: false, code: E.OUT_OF_BOARD, reason: '越界' } + const p = pieceInfo(board[from.r][from.c]) + if (!p) return { ok: false, code: E.NO_PIECE, reason: '起点无棋子' } + if (p.side !== side) return { ok: false, code: E.WRONG_SIDE, reason: '不能走对方棋子' } + if (isOwn(board, to.r, to.c, side)) return { ok: false, code: E.CAPTURE_OWN, reason: '落点是自己棋子' } + + // 1) 单子走法(含蹩马腿/塞象眼/炮架) + let pseudo = false, issue = null + for (const t of pseudoTargets(board, from.r, from.c)) { + if (t.tr === to.r && t.tc === to.c) { pseudo = true; issue = t.code || ''; break } + } + if (!pseudo) { + // 给 bot 更友好的错误细分(注意:from/to 为 {r,c} 结构) + const dr = Math.abs(to.r - from.r), dc = Math.abs(to.c - from.c) + if (p.type === 'N' && ((dr === 1 && dc === 2) || (dr === 2 && dc === 1))) { + const legR = from.r + (dr === 2 ? (to.r - from.r) / 2 : 0) + const legC = from.c + (dc === 2 ? (to.c - from.c) / 2 : 0) + if (board[legR] && board[legR][legC]) return { ok: false, code: E.BLOCKED_HORSE, reason: '蹩马腿' } + } + if (p.type === 'B' && dr === 2 && dc === 2) { + const eyeR = from.r + (to.r - from.r) / 2, eyeC = from.c + (to.c - from.c) / 2 + if (board[eyeR] && board[eyeR][eyeC]) return { ok: false, code: E.BLOCKED_ELEPHANT, reason: '塞象眼' } + } + if (p.type === 'C' && (dr === 0 || dc === 0)) { + // 同线但拦截数不对 → 炮架问题 + const blocked = countBetween(board, from, to) + if (blocked !== 1) { + return { ok: false, code: E.CANNON_SCREEN, reason: `炮吃/走子拦截数不对(需隔 1 个炮架),当前 ${blocked} 个` } + } + } + return { ok: false, code: E.ILLEGAL_MOVE, reason: '不符合该子走法' } + } + if (issue === 'BLOCKED_HORSE') return { ok: false, code: E.BLOCKED_HORSE, reason: '蹩马腿' } + if (issue === 'BLOCKED_ELEPHANT') return { ok: false, code: E.BLOCKED_ELEPHANT, reason: '塞象眼' } + if (issue === 'CAPTURE_OWN') return { ok: false, code: E.CAPTURE_OWN, reason: '落点是自己棋子' } + + // 2) 模拟落子后的局面合法性 + const nb = applyMove(board, from, to).board + if (kingsFace(nb)) { + return { ok: false, code: E.FLYING_GENERAL, reason: '将帅照面' } + } + if (inCheck(nb, side)) { + return { ok: false, code: E.SELF_CHECK, reason: '送将(走完己方被将军)' } + } + return { ok: true } +} + +// 两点间棋子数(同线时有效) +const countBetween = (board, from, to) => { + if (from.r === to.r) { + let n = 0 + const lo = Math.min(from.c, to.c) + 1, hi = Math.max(from.c, to.c) + for (let c = lo; c < hi; c++) if (board[from.r][c]) n++ + return n + } + if (from.c === to.c) { + let n = 0 + const lo = Math.min(from.r, to.r) + 1, hi = Math.max(from.r, to.r) + for (let r = lo; r < hi; r++) if (board[r][from.c]) n++ + return n + } + return -1 +} + +// ---------- 合法着法生成(过滤自将/照面的完整合法移动)---------- +// 返回 [{ from:{r,c}, to:{r,c}, code:null }] +const legalMoves = (board, side) => { + const out = [] + for (let r = 0; r < ROWS; r++) { + for (let c = 0; c < COLS; c++) { + const p = pieceInfo(board[r][c]) + if (!p || p.side !== side) continue + for (const t of pseudoTargets(board, r, c)) { + if (t.code) continue + const nb = applyMove(board, { r, c }, { r: t.tr, c: t.tc }).board + if (kingsFace(nb)) continue + if (inCheck(nb, side)) continue + out.push({ from: { r, c }, to: { r: t.tr, c: t.tc } }) + } + } + } + return out +} + +const hasAnyLegalMove = (board, side) => legalMoves(board, side).length > 0 + +// ---------- 走完一步后的终局判定 ---------- +// 传入:走子后的棋盘 + 刚走的一方。返回: +// { ended, result, winner, kind, check } +// result: red_win | black_win | draw | ''(未结束) +// kind: king_captured | checkmate | stalemate | '' +// check: 是否将军对方 + 被将军方('red'|'black'|'') +const resolveAfterMove = (board, movedSide) => { + const enemy = movedSide === 'red' ? 'black' : 'red' + const enemyKing = findKing(board, enemy) + if (!enemyKing) { + return { ended: true, result: `${movedSide}_win`, winner: movedSide, kind: 'king_captured', check: '' } + } + const enemyCheck = inCheck(board, enemy) + const enemyMoves = legalMoves(board, enemy) + if (enemyCheck && enemyMoves.length === 0) { + return { ended: true, result: `${movedSide}_win`, winner: movedSide, kind: 'checkmate', check: enemy } + } + if (!enemyCheck && enemyMoves.length === 0) { + // 中国象棋规则:无棋可动(困毙)判负 + return { ended: true, result: `${movedSide}_win`, winner: movedSide, kind: 'stalemate', check: '' } + } + return { ended: false, result: '', winner: '', kind: '', check: enemyCheck ? enemy : '' } +} + +// 和棋辅助判定(由调用方在掌握历史后调用) +// 1) 子力不足:场上无车/马/炮/兵(只剩帅±士相)→ 理论上无法杀棋 → 和 +const insufficientMaterial = (board) => { + let attack = false + for (let r = 0; r < ROWS; r++) { + for (let c = 0; c < COLS; c++) { + const p = pieceInfo(board[r][c]) + if (!p) continue + if (p.type === 'R' || p.type === 'N' || p.type === 'C' || p.type === 'P') { attack = true; break } + } + if (attack) break + } + return !attack +} + +// ---------- 展示与对照 ---------- +// ASCII 棋盘(日志/聊天用),行 9=黑方底线在顶部,红方视角 +const boardToAscii = (board, turn) => { + const SYM = { K: '帅', A: '士', B: '相', N: '马', R: '车', C: '炮', P: '兵' } + const lines = [] + lines.push(' a b c d e f g h i') + lines.push(' ┌─────────────────────────────┐') + for (let r = ROWS - 1; r >= 0; r--) { + const cells = [] + for (let c = 0; c < COLS; c++) { + const ch = board[r][c] + if (!ch) cells.push('·') + else { + const p = pieceInfo(ch) + let name = SYM[p.type] || p.type + cells.push(p.side === 'red' ? `红${name}` : `黑${name}`) + } + } + const cellStr = cells.join(' ') + // 行号统一宽度 + lines.push(` ${String(r).padStart(3)} │ ${cellStr} │`) + } + lines.push(' └─────────────────────────────┘') + const cur = turn === 'b' ? '黑方' : '红方' + lines.push(`当前轮:${cur}(${turn})`) + return lines.join('\n') +} + +// 双方棋子清单(给 bot 的结构化描述,避免 bot 解析 FEN 出错) +const boardToPieceList = (board) => { + const SYM = { K: '帅', A: '士', B: '相', N: '马', R: '车', C: '炮', P: '兵' } + const red = [], black = [] + for (let r = 0; r < ROWS; r++) { + for (let c = 0; c < COLS; c++) { + const ch = board[r][c] + if (!ch) continue + const p = pieceInfo(ch) + const item = { pos: sq(r, c), piece: p.type, cn: SYM[p.type] } + if (p.side === 'red') red.push(item); else black.push(item) + } + } + return { red, black } +} + +module.exports = { + START_FEN, + E, + parseFen, + toFen, + positionKey, + parseMove, + parseSquare, + pieceInfo, + findKing, + validateMove, + legalMoves, + hasAnyLegalMove, + applyMove, + inCheck, + kingsFace, + pseudoTargets, + resolveAfterMove, + insufficientMaterial, + boardToAscii, + boardToPieceList, + ROWS, + COLS +} \ No newline at end of file diff --git a/src/server/thought_lab/labs/bot_chess/service.js b/src/server/thought_lab/labs/bot_chess/service.js new file mode 100644 index 0000000..757683c --- /dev/null +++ b/src/server/thought_lab/labs/bot_chess/service.js @@ -0,0 +1,365 @@ +// ============================================================ +// bot_chess/service.js —— 对局核心业务(建赛/换先/走子/终局/犯规/结算/日志) +// 被 index.js(HTTP API)与 orchestrator.js(裁判编排)共用,保证“机械裁判” +// 只有这一处实现,规则意见一律出自 referee.js 引擎,不信任任何 LLM 叙述。 +// ============================================================ +const fs = require('fs') +const path = require('path') +const r = require('./referee') +const db = require('./db') + +const CONFIG_PATH = path.join(__dirname, 'config.json') + +const readConfig = () => { + try { + if (fs.existsSync(CONFIG_PATH)) return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8')) + } catch {} + return {} +} + +const SIDE_OTHER = { red: 'black', black: 'red' } +const sideOfTurn = t => (t === 'b' ? 'black' : 'red') +const turnOfSide = s => (s === 'red' ? 'w' : 'b') + +const CN_R = { 'K': '帅', 'A': '士', 'B': '相', 'N': '马', 'R': '车', 'C': '炮', 'P': '兵' } +const CN_B = { 'K': '将', 'A': '仕', 'B': '象', 'N': '馬', 'R': '車', 'C': '砲', 'P': '卒' } +const safeStr = v => String(v || '') + +// 某局中某方对应的 bot 名(偶局红黑对调) +const sideBotForGame = (match, gameNo, side) => { + const swap = (Number(gameNo) || 0) % 2 === 0 + if (side === 'red') return swap ? safeStr(match.black_bot) : safeStr(match.red_bot) + return swap ? safeStr(match.red_bot) : safeStr(match.black_bot) +} + +// ---------------- 创建 / 开局 ---------------- +// 创建比赛并生成第一局(返回 match 快照;token 仅此处/建赛接口返回) +const createMatch = (opts) => { + const cfg = readConfig() + const bestOf = Math.max(1, Math.min(9, Number(opts.best_of) || Number(cfg.best_of) || 3)) + const match = db.createMatch({ + title: opts.title, + red_bot: opts.red_bot || '红方Bot', + black_bot: opts.black_bot || '黑方Bot', + red_model: opts.red_model || cfg.red_model || 'qwen-plus-latest', + black_model: opts.black_model || cfg.black_model || 'qwen-plus-latest', + best_of: bestOf + }) + const game = db.createGame(match.id, 1) + db.updateMatchStatus(match.id, { current_game_id: game.id }) + db.addEvent({ match_id: match.id, game_id: game.id, kind: 'match_created', content: `比赛“${match.title}”创建:${match.red_bot} vs ${match.black_bot},${bestOf}局${bestOf === 1 ? '' : '(胜出需赢' + Math.ceil(bestOf / 2) + '局)'}` }) + db.addEvent({ match_id: match.id, game_id: game.id, kind: 'game_started', content: `第 1 局开始:${sideBotForGame(match, 1, 'red')} 执红先行` }) + return buildSnapshot(match.id) +} + +// 开局(created -> active),并触发监督器接管超时判罚 +const startMatch = (matchId) => { + const match = db.getMatch(matchId) + if (!match) return { ok: false, error: 'match_not_found' } + if (match.status !== 'created') return { ok: false, error: 'match_not_created', code: 'MATCH_NOT_CREATED' } + db.updateMatchStatus(matchId, { status: 'active', started_at: db.nowBeijing() }) + db.addEvent({ match_id: matchId, kind: 'start', content: '比赛开始' }) + return { ok: true, snapshot: buildSnapshot(matchId) } +} + +// ---------------- 走子(机械裁判主入口)---------------- +const submitMove = (matchId, side, moveStr, opts = {}) => { + const cfg = readConfig() + const match = db.getMatch(matchId) + if (!match) return { ok: false, code: 'MATCH_NOT_FOUND', error: '比赛不存在' } + if (match.status === 'finished' || match.status === 'aborted') { + return { ok: false, code: 'GAME_OVER', error: `比赛已${match.status === 'finished' ? '结束' : '终止'}` } + } + if (match.status !== 'active') return { ok: false, code: 'MATCH_NOT_STARTED', error: '比赛尚未开始' } + const game = db.getGameByCur(matchId) + if (!game || game.status !== 'playing') return { ok: false, code: 'GAME_OVER', error: '本局已结束,等待下一局' } + if (side !== sideOfTurn(game.turn)) { + return { ok: false, code: 'NOT_YOUR_TURN', error: `还没轮到你(${side}),当前轮到 ${sideOfTurn(game.turn)}` } + } + + const mv = r.parseMove(moveStr) + if (!mv) return { ok: false, code: 'BAD_MOVE_FORMAT', error: '着法格式应为 h2-e2 或 h2e2' } + + const fen = r.parseFen(game.fen) + const v = r.validateMove(fen.board, side, mv.from, mv.to) + if (!v.ok) { + // 非法走子:局面不动,仅返回可读错误(不自动判罚;判罚由监督器/编排器决定) + return { ok: false, code: v.code, reason: v.reason, error: `非法走子:${v.reason}`, + fen: game.fen, turn: game.turn, side_to_move: sideOfTurn(game.turn), status: game.status } + } + + // 模拟落子 + const applied = r.applyMove(fen.board, mv.from, mv.to) + const piece = fen.board[mv.from.r][mv.from.c] + const pInfo = r.pieceInfo(piece) + const enemy = SIDE_OTHER[side] + const isCheck = r.inCheck(applied.board, enemy) + const res = r.resolveAfterMove(applied.board, side) + + // 和棋辅助判定(仅当引擎未直接决出胜负时) + let draw = '', drawKind = '' + if (!res.ended) { + if (r.insufficientMaterial(applied.board)) { draw = 'draw'; drawKind = 'no_material' } + else { + const isCapturePawn = !!applied.captured || (pInfo && pInfo.type === 'P') + const np = isCapturePawn ? 0 : (Number(game.no_capture_plies) || 0) + 1 + const limit = Number(cfg.draw_ply_limit) || 120 + if (np >= limit) { draw = 'draw'; drawKind = 'no_advance_limit' } + else { + const key = r.positionKey(applied.board) + const history = db.listMoves(game.id).map(m => r.positionKey(r.parseFen(m.fen_after).board)) + let cnt = 1 + for (const k of history) if (k === key) cnt++ + const need = Number(cfg.repetition_required) || 3 + if (cnt >= need) { draw = 'draw'; drawKind = 'repetition' } + } + } + } + + const moveNo = (Number(game.move_count) || 0) + 1 + const newTurn = SIDE_OTHER[side] + const newFen = r.toFen(applied.board, turnOfSide(newTurn)) + const cn = (side === 'red' ? CN_R : CN_B)[pInfo.type] || pInfo.type + const san = `${side === 'red' ? '红' : '黑'}${cn} ${fmtSq(mv.from)}-${fmtSq(mv.to)}` + const mvRow = db.addMove({ + match_id: matchId, game_id: game.id, move_no: moveNo, side, + from_sq: fmtSq(mv.from), to_sq: fmtSq(mv.to), + piece: pInfo.type, captured: applied.captured ? (applied.captured.toUpperCase()) : '', + is_check: isCheck ? 1 : 0, fen_before: game.fen, fen_after: newFen, san + }) + + if (res.ended || draw) { + const result = draw ? 'draw' : res.result + const winner = draw ? '' : res.winner + const kind = draw ? drawKind : res.kind + const content = draw + ? `判和:${drawKind === 'no_material' ? '子力不足' : drawKind === 'repetition' ? '三次重复局面' : '自然限着(连续无吃子/进兵超限)'}` + : `${winner === 'red' ? '红方' : '黑方'}胜:${kind === 'checkmate' ? '将死' : kind === 'stalemate' ? '困毙(无棋可动)' : '飞将直取'}` + finishGame(matchId, game.id, { result, winner, kind, content, lastMoveRow: mvRow }) + return { ok: true, accepted: true, fen_after: newFen, check: res.check, game_finished: true, + result, winner, kind, snapshot: buildSnapshot(matchId) } + } + + db.updateGame(game.id, { + fen: newFen, turn: turnOfSide(newTurn), move_count: moveNo, + no_capture_plies: applied.captured || (pInfo && pInfo.type === 'P') ? 0 : (Number(game.no_capture_plies) || 0) + 1 + }) + db.addEvent({ + match_id: matchId, game_id: game.id, kind: 'move', + side, content: `${san}${applied.captured ? ` 吃${(side === 'red' ? CN_R : CN_B)[applied.captured.toUpperCase()] || applied.captured}` : ''}${isCheck ? ' 将军' : ''} (${moveNo})` + }) + return { ok: true, accepted: true, fen_after: newFen, check: res.check, game_finished: false, snapshot: buildSnapshot(matchId) } +} + +// 坐标转字符串(parseMove 返回的对象含 r/c) +const fmtSq = p => (p === undefined || p === null) ? '' : ('abcdefghi'[p.c] + String(p.r)) + +// ---------------- 终局 / 换先 / 整场结算 ---------------- +const finishGame = (matchId, gameId, { result, winner, kind, content, lastMoveRow }) => { + const match = db.getMatch(matchId) + const game = db.getGame(gameId) + if (!match || !game || game.status !== 'playing') return + db.updateGame(gameId, { status: 'finished', result, winner, finished_at: db.nowBeijing() }) + + let gw = winner + if (String(result).startsWith('forfeit_')) gw = result === 'forfeit_red' ? 'black' : 'red' + if (String(result).startsWith('abort_')) { gw = ''; content = content || '对局中止' } + + const addScore = (side) => { + const m2 = db.getMatch(matchId) + if (side === 'red') db.updateMatchStatus(matchId, { score_red: (m2.score_red || 0) + 1 }) + else db.updateMatchStatus(matchId, { score_black: (m2.score_black || 0) + 1 }) + } + if (gw === 'red') addScore('red') + else if (gw === 'black') addScore('black') + else if (!winner && !gw) { /* 平局不加分 */ } + + db.addEvent({ match_id: matchId, game_id: gameId, kind: 'game_finished', side: gw, content: `${game.game_no} 局结束:${content || result}` }) + + // 判断整场是否需继续下一局 + const m2 = db.getMatch(matchId) + const winNeed = Math.ceil(Number(m2.best_of) / 2) + const nextNo = (Number(game.game_no) || 0) + 1 + const scored = Math.max(m2.score_red || 0, m2.score_black || 0) + if (scored >= winNeed) { + const champ = (m2.score_red || 0) >= winNeed ? 'red' : (m2.score_black || 0) >= winNeed ? 'black' : '' + const champBot = champ === 'red' ? m2.red_bot : m2.black_bot + db.updateMatchStatus(matchId, { status: 'finished', winner: champ, finished_at: db.nowBeijing() }) + db.addEvent({ match_id: matchId, kind: 'match_finished', side: champ, content: `比赛结束:${champBot} 夺冠 ${m2.score_red}:${m2.score_black}` }) + } else if (nextNo > Number(m2.best_of)) { + const champ = m2.score_red > m2.score_black ? 'red' : m2.score_black > m2.score_red ? 'black' : 'draw' + db.updateMatchStatus(matchId, { status: 'finished', winner: champ, finished_at: db.nowBeijing() }) + db.addEvent({ match_id: matchId, kind: 'match_finished', side: champ, content: `比赛结束(满${m2.best_of}局):${champ === 'draw' ? '平局' : (champ === 'red' ? m2.red_bot : m2.black_bot) + ' 胜'}` }) + } else { + // 开下一局,红黑对调 + const ng = db.createGame(matchId, nextNo) + db.updateMatchStatus(matchId, { current_game_id: ng.id }) + const nm = db.getMatch(matchId) + db.addEvent({ match_id: matchId, game_id: ng.id, kind: 'game_started', content: `第 ${nextNo} 局开始:${sideBotForGame(nm, nextNo, 'red')} 执红先行(红黑对调)` }) + } +} + +// 认输 / 判负 / 中止(编排器或管理员调用;机械层负责落库) +const forfeitSide = (matchId, side, reason) => { + const match = db.getMatch(matchId) + const game = db.getGameByCur(matchId) + if (!match || !game || game.status !== 'playing') return { ok: false, error: '不是对局中' } + const result = side === 'red' ? 'forfeit_red' : 'forfeit_black' + finishGame(matchId, game.id, { + result, winner: SIDE_OTHER[side], + content: `${side === 'red' ? '红方' : '黑方'}判负:${reason || '判负'}` + }) + return { ok: true, snapshot: buildSnapshot(matchId) } +} + +const abortMatch = (matchId, reason) => { + const match = db.getMatch(matchId) + if (!match) return { ok: false, error: 'match_not_found' } + const game = db.getGameByCur(matchId) + if (game && game.status === 'playing') { + db.updateGame(game.id, { status: 'aborted', result: 'aborted', finished_at: db.nowBeijing() }) + } + db.updateMatchStatus(matchId, { status: 'aborted', winner: '', finished_at: db.nowBeijing() }) + db.addEvent({ match_id: matchId, game_id: game ? game.id : 0, kind: 'abort', content: `比赛中止:${reason || '手动中止'}` }) + return { ok: true } +} + +// 犯规累计判罚(超时/非法走子超限/失联均由监督器或编排器调用) +const applyFoul = (matchId, side, reason) => { + const cfg = readConfig() + const match = db.getMatch(matchId) + if (!match || match.status !== 'active') return { ok: false, error: '比赛不活动中' } + const game = db.getGameByCur(matchId) + if (!game || game.status !== 'playing') return { ok: false, error: '本局已结束' } + const curSide = sideOfTurn(game.turn) + if (side !== curSide) return { ok: false, error: '犯规方与当前行棋方不一致' } + db.addFoul(game.id, side, reason) + db.addEvent({ match_id: matchId, game_id: game.id, kind: 'foul', side, content: `${side === 'red' ? '红方' : '黑方'}犯规:${reason}` }) + const limit = Number(cfg.foul_limit) || 3 + const g2 = db.getGame(game.id) + const cnt = side === 'red' ? g2.red_fouls : g2.black_fouls + if (cnt >= limit) { + const result = side === 'red' ? 'forfeit_red' : 'forfeit_black' + finishGame(matchId, game.id, { result, winner: SIDE_OTHER[side], content: `${side === 'red' ? '红方' : '黑方'}犯规达 ${limit} 次判负` }) + } + return { ok: true, snapshot: buildSnapshot(matchId) } +} + +// ---------------- 快照与日志 ---------------- +const buildSnapshot = (matchId) => { + const match = db.getMatch(matchId) + if (!match) return null + const game = match.current_game_id ? db.getGame(match.current_game_id) : null + const games = db.getGamesByMatch(matchId).map(g => ({ + game_no: g.game_no, status: g.status, result: g.result, winner: g.winner, + move_count: g.move_count, red_fouls: g.red_fouls, black_fouls: g.black_fouls, + fen: g.fen, turn: g.turn, created_at: g.created_at, finished_at: g.finished_at + })) + let board = null + if (game) { + const fen = r.parseFen(game.fen) + board = { + fen: game.fen, + ascii: r.boardToAscii(fen.board, game.turn), + pieces: r.boardToPieceList(fen.board), + side_to_move: sideOfTurn(game.turn) + } + } + const lastMoves = game ? db.listMoves(game.id, 10).reverse() : [] + return { + match: { + id: match.id, title: match.title, red_bot: match.red_bot, black_bot: match.black_bot, + red_model: match.red_model, black_model: match.black_model, best_of: match.best_of, + status: match.status, score_red: match.score_red, score_black: match.score_black, + winner: match.winner, created_at: match.created_at, started_at: match.started_at, + finished_at: match.finished_at, current_game_no: game ? game.game_no : 0 + }, + game: game ? { + id: game.id, game_no: game.game_no, status: game.status, result: game.result, + winner: game.winner, move_count: game.move_count, turn: game.turn, + red_fouls: game.red_fouls, black_fouls: game.black_fouls, created_at: game.created_at, finished_at: game.finished_at + } : null, + board, + games, + last_moves: lastMoves.map(m => m.san || `${m.side} ${m.from_sq}-${m.to_sq}`) + } +} + +// Markdown 比赛日志(导出用) +const buildMarkdownLog = (matchId) => { + const match = db.getMatch(matchId) + if (!match) return '' + const games = db.getGamesByMatch(matchId) + const events = db.listEvents(matchId) + const L = [] + L.push(`# 机器人象棋 比赛日志`) + L.push('') + L.push(`- 比赛:${match.title}`) + L.push(`- 红方:${match.red_bot}(${match.red_model}) / 黑方:${match.black_bot}(${match.black_model})`) + L.push(`- 赛制:${match.best_of} 局${match.best_of === 1 ? '' : `(两胜制,胜出需 ${Math.ceil(match.best_of / 2)} 局)`}`) + L.push(`- 状态:${match.status === 'finished' ? '已结束' : match.status === 'aborted' ? '已中止' : match.status === 'active' ? '进行中' : '已创建'}`) + L.push(`- 比分:红 ${match.score_red} : ${match.score_black} 黑`) + L.push(`- 结果:${match.winner === 'red' ? '红方胜' : match.winner === 'black' ? '黑方胜' : match.winner === 'draw' ? '平局' : '未定'}`) + L.push(`- 创建:${match.created_at}${match.started_at ? '' : ''}${match.finished_at ? ` / 结束:${match.finished_at}` : ''}`) + L.push('') + L.push(`## 事件流水`) + L.push('') + for (const ev of events) { + L.push(`- [${ev.created_at}] ${ev.kind === 'move' ? `手 ${ev.content}` : ev.content}`) + } + L.push('') + for (const g of games) { + L.push(`## 第 ${g.game_no} 局`) + L.push('') + const color = (Number(g.game_no) % 2 === 1) + ? `红:${match.red_bot} / 黑:${match.black_bot}` + : `红:${match.black_bot} / 黑:${match.red_bot}(换先)` + L.push(`- ${color}`) + L.push(`- 状态:${g.status === 'finished' ? '已结束' : g.status === 'aborted' ? '已中止' : '进行中'};结果:${g.result || '未定'}${g.winner ? `(${g.winner === 'red' ? '红胜' : '黑胜'})` : ''}`) + L.push(`- 犯规:红 ${g.red_fouls} 次 / 黑 ${g.black_fouls} 次${g.finished_at ? ` / 结束于 ${g.finished_at}` : ''}`) + L.push('') + L.push(`| # | 手 | 着法 | 吃子 | 将军 | 着后FEN |`) + L.push(`|---|-----|------|------|-------|---------|`) + const moves = db.listMoves(g.id) + for (const m of moves) { + L.push(`| ${m.move_no} | ${m.side === 'red' ? '红' : '黑'} | ${m.san || `${m.from_sq}-${m.to_sq}`} | ${m.captured ? `吃${m.captured}` : ''} | ${m.is_check ? '将军' : ''} | ${m.fen_after} |`) + } + L.push('') + } + L.push('---') + L.push('*由 TRAE-Toolbox bot_chess lab 机械裁判生成*') + return L.join('\n') +} + +// ---------------- 超时监督器(进程级,防 bot 失联卡死)---------------- +let supervisorStarted = false +const ensureSupervisor = () => { + if (supervisorStarted) return + supervisorStarted = true + setInterval(() => { + try { + const cfg = readConfig() + const timeout = Math.max(30, Number(cfg.move_timeout_seconds) || 180) * 1000 + const matches = db.listMatches(100) + for (const m of matches) { + if (m.status !== 'active') continue + const game = db.getGameByCur(m.id) + if (!game || game.status !== 'playing') continue + const last = db.lastMoveTime(game.id) || m.started_at + if (!last) continue + const lastMs = new Date(String(last).replace(' ', 'T') + '+08:00').getTime() + if (!Number.isFinite(lastMs)) continue + if (Date.now() - lastMs > timeout) { + const side = sideOfTurn(game.turn) + applyFoul(m.id, side, '超时未走子') + } + } + } catch (e) { /* 监督器容错,不打断主流程 */ } + }, 15000).unref() +} + +module.exports = { + readConfig, SIDE_OTHER, sideOfTurn, turnOfSide, sideBotForGame, + createMatch, startMatch, submitMove, finishGame, forfeitSide, abortMatch, applyFoul, + buildSnapshot, buildMarkdownLog, ensureSupervisor +} \ No newline at end of file diff --git a/src/server/weekly_integrations.js b/src/server/weekly_integrations.js index 641e9a4..b52572e 100644 --- a/src/server/weekly_integrations.js +++ b/src/server/weekly_integrations.js @@ -46,7 +46,8 @@ const summarizeText = async (text) => { // Using Aliyun DashScope (compatible with OpenAI) const apiKey = process.env.DASHSCOPE_API_KEY || ''; - const url = 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions'; + const dashScopeHost = process.env.DASHSCOPE_API_HOST_FOR_OPENAI || ''; + const url = dashScopeHost ? `${dashScopeHost.replace(/\/+$/, '')}/chat/completions` : ''; const systemPrompt = defaultWeeklySystemPrompt; diff --git a/src/server/yuanzhupai.js b/src/server/yuanzhupai.js index 3ff3af6..005d478 100644 --- a/src/server/yuanzhupai.js +++ b/src/server/yuanzhupai.js @@ -5,7 +5,7 @@ const axios = require('axios') const CONFIG_PATH = path.join(process.cwd(), 'config', 'yuanzhupai.json') const CONFIG_LOCAL_PATH = path.join(process.cwd(), 'config', 'yuanzhupai.local.json') const SCENARIOS_DIR = path.join(process.cwd(), 'config', 'scenarios') -const DASHSCOPE_BASE = 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions' +const DASHSCOPE_BASE = process.env.DASHSCOPE_API_HOST_FOR_OPENAI ? `${process.env.DASHSCOPE_API_HOST_FOR_OPENAI.replace(/\/+$/, '')}/chat/completions` : '' const DEFAULT_MODEL = 'qwen-plus-latest' const DEFAULT_TEMPERATURE = 0.2 const DEFAULT_MAX_TOKENS = 4096