Files
persistent-chat-plugin/lib/hub-routes-prd.js
卡若AI ba786b3700 feat: Persistent Chat v1.1 CO-Chat 整合上传 Gitea
同步 enhance/bridge/smoke/retire 模块、vendor 3.3.34、增强面板与 PRD 文档,
Hub 接入 hub-routes-prd,含安装/经验/模块说明与 co-integration 运维脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 17:42:16 +08:00

209 lines
7.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
const enhance = require('./enhance');
const bridge = require('./bridge');
const modeLib = require('./mode');
const templatesLib = require('./templates');
const smokeLib = require('./smoke');
const retireLib = require('./retire');
const HUB_URL = `http://127.0.0.1:${process.env.PCHAT_HTTP_PORT || '13458'}`;
function defaultWorkspace(sessions) {
if (process.env.PCHAT_WORKSPACE) return process.env.PCHAT_WORKSPACE;
for (const s of sessions.values()) {
if (s.workspace && s.workspace !== 'none') return s.workspace;
}
return '/Users/karuo/Documents/个人';
}
async function handlePrdRoutes(ctx) {
const { u, req, res, sessions, pendingWaits, saveSessions, nowMs, json, readBody } = ctx;
const WORKSPACE = defaultWorkspace(sessions);
if (u.pathname === '/api/prd/progress' && req.method === 'GET') {
json(res, 200, enhance.getPrdProgress());
return true;
}
if (u.pathname === '/api/mode/check' && req.method === 'GET') {
const report = modeLib.detectRulesConflict(WORKSPACE);
if (templatesLib.templatesReady(WORKSPACE)) enhance.markPrdItem('phase2-templates', true);
json(res, 200, { ok: true, workspace: WORKSPACE, ...report });
return true;
}
if (u.pathname === '/api/mode/retire-cochat-rule' && req.method === 'POST') {
const fs = require('fs');
const path = require('path');
const rulesDir = path.join(WORKSPACE, '.cursor', 'rules');
const retiredDir = path.join(rulesDir, '_retired');
const src = path.join(rulesDir, 'co-chat.mdc');
let moved = false;
try {
if (fs.existsSync(src)) {
fs.mkdirSync(retiredDir, { recursive: true });
const dest = path.join(retiredDir, 'co-chat.mdc');
fs.renameSync(src, dest);
moved = true;
}
} catch (e) {
json(res, 500, { ok: false, error: String(e.message || e) });
return true;
}
const report = modeLib.detectRulesConflict(WORKSPACE);
json(res, 200, { ok: true, moved, conflict: report.conflict, message: report.message });
return true;
}
if (u.pathname === '/api/templates' && req.method === 'GET') {
const data = templatesLib.getTemplates(WORKSPACE);
if (templatesLib.templatesReady(WORKSPACE)) enhance.markPrdItem('phase2-templates', true);
json(res, 200, data);
return true;
}
if (u.pathname === '/api/retire/check' && req.method === 'GET') {
const state = enhance.getStatus();
const report = retireLib.checkRetirePreconditions(state);
if (report.script) enhance.markPrdItem('phase3-retire', true);
json(res, 200, report);
return true;
}
if (u.pathname === '/api/smoke/run' && req.method === 'POST') {
const result = await smokeLib.runSmoke(HUB_URL);
if (result.ok) enhance.markPrdItem('phase3-smoke', true);
json(res, 200, result);
return true;
}
if (u.pathname === '/api/bridge/sync' && req.method === 'POST') {
const body = JSON.parse(await readBody(req) || '{}');
const result = bridge.syncToSession({ token: body.token, content: body.content, source: body.source }, sessions, pendingWaits, nowMs, saveSessions);
if (result.ok) {
enhance.markPrdItem('phase2-bridge', true);
const s = sessions.get(body.token);
if (s) enhance.markPrdItem('mode-a-wait', pendingWaits.has(body.token) || (s.messages || []).length > 0);
}
json(res, result.ok ? 200 : 400, result);
return true;
}
if (u.pathname === '/api/session/mode' && req.method === 'POST') {
const body = JSON.parse(await readBody(req) || '{}');
const s = sessions.get(body.token);
if (!s) { json(res, 404, { ok: false, error: 'unknown token' }); return true; }
if (!modeLib.setSessionMode(s, body.mode)) { json(res, 400, { ok: false, error: 'invalid mode' }); return true; }
saveSessions();
json(res, 200, { ok: true, mode: body.mode, badge: modeLib.sessionBadge(s) });
return true;
}
if (u.pathname === '/api/enhance/status' && req.method === 'GET') {
const state = enhance.getStatus();
enhance.markPrdItem('phase1-hub-progress', true);
enhance.markPrdItem('phase1-wizard-ui', true);
json(res, 200, { ok: true, state });
return true;
}
if (u.pathname === '/api/enhance/diagnose' && req.method === 'GET') {
const report = enhance.diagnose();
enhance.markPrdItem('phase1-w2-w8', enhance.w8GatesPass(report.state));
if (enhance.modeBGatesPass(report.state)) enhance.markPrdItem('mode-b-opus', true);
json(res, 200, { ok: true, report });
return true;
}
if (u.pathname === '/api/enhance/apply' && req.method === 'POST') {
const result = await enhance.applyEnhance();
enhance.markPrdItem('phase1-enhance-api', true);
enhance.markPrdItem('phase1-patch-bridge', !!result.ok);
json(res, 200, result);
return true;
}
if (u.pathname === '/api/enhance/repair' && req.method === 'POST') {
const result = await enhance.repairEnhance();
json(res, 200, result);
return true;
}
if (u.pathname === '/api/enhance/toggle' && req.method === 'POST') {
const body = JSON.parse(await readBody(req) || '{}');
const result = enhance.setEnhanceEnabled(!!body.enabled);
json(res, result.ok ? 200 : 400, result);
return true;
}
if (u.pathname === '/api/enhance/settings' && req.method === 'POST') {
const body = JSON.parse(await readBody(req) || '{}');
const state = enhance.updateNoQuotaSettings(body);
json(res, 200, { ok: true, state });
return true;
}
if (u.pathname === '/api/enhance/fix-perm' && req.method === 'POST') {
const result = enhance.runPermFixOsascript();
const state = enhance.loadState();
enhance.syncGatesToState(state);
enhance.saveState(state);
if (enhance.w8GatesPass(state)) enhance.markPrdItem('phase1-w2-w8', true);
json(res, 200, { ok: state.permOk, result, state });
return true;
}
if (u.pathname === '/api/enhance/fix-prefs' && req.method === 'POST') {
const result = enhance.fixCursorPrefs();
const state = enhance.loadState();
enhance.syncGatesToState(state);
enhance.saveState(state);
json(res, 200, { ok: result.cursorPrefOk, prefs: result, state });
return true;
}
if (u.pathname === '/api/enhance/open-w2-terminal' && req.method === 'POST') {
const { spawn } = require('child_process');
const cmd = 'sudo bash ~/.cursor/cochat_fix_write_permission.sh';
spawn('osascript', ['-e', `tell application "Terminal" to do script "${cmd}"`], { detached: true, stdio: 'ignore' }).unref();
json(res, 200, { ok: true, message: '已在 Terminal 打开 W2 命令,输入密码后 Cmd+Q 重启 Cursor' });
return true;
}
if (u.pathname === '/api/enhance/opus-probe' && req.method === 'GET') {
const state = enhance.getStatus();
const markers = enhance.verifyPatchMarkers();
const ready = enhance.modeBGatesPass(state);
if (ready) enhance.markPrdItem('mode-b-opus', true);
json(res, 200, {
ok: ready,
gates: {
permOk: state.permOk,
patchOk: state.patchOk,
patchNativeOk: !!state.patchNativeOk,
cursorPrefOk: state.cursorPrefOk,
},
markers: markers.details,
message: ready
? (state.patchOk ? '三门禁全绿workbench 补丁)' : 'pchat 原生模式就绪Opus 探测可用')
: '请先 W2 + W6并开启增强包',
});
return true;
}
if (u.pathname === '/enhance' && req.method === 'GET') {
const fs = require('fs');
const path = require('path');
const p = path.join(__dirname, '..', 'panel-enhance.html');
if (!fs.existsSync(p)) return false;
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(fs.readFileSync(p, 'utf8'));
return true;
}
return false;
}
module.exports = { handlePrdRoutes, defaultWorkspace };