Files
persistent-chat-plugin/lib/patch-via-cochat.mjs
卡若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

219 lines
6.7 KiB
JavaScript
Raw Permalink 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.

#!/usr/bin/env node
/**
* W7 补丁编排:恢复 co-chat storage → Cursor 未运行时 headless 打补丁;
* Cursor 运行中则排队不强杀Hub watcher 在退出后自动续跑。
*/
import fs from 'node:fs';
import path from 'node:path';
import { spawn, spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const HOME = process.env.HOME || '';
const PCHAT_ROOT = path.join(HOME, '.persistent-chat-local');
const PENDING_FILE = path.join(PCHAT_ROOT, 'pending-w7.json');
const COCHAT_STORAGE = path.join(
HOME,
'Library/Application Support/Cursor/User/globalStorage/co-chat.co-chat-panel/storage.json',
);
const CURSOR_APP = '/Applications/Cursor.app';
const WORKBENCH = path.join(CURSOR_APP, 'Contents/Resources/app/out/vs/workbench/workbench.desktop.main.js');
const EXTHOST = path.join(CURSOR_APP, 'Contents/Resources/app/out/vs/workbench/api/node/extensionHostProcess.js');
const PATCH_ACTIVATE = path.join(__dirname, 'patch-activate.mjs');
const PATCH_FLAGS = {
'kc.corePatchBundle': true,
'kc.agentSortPerfPatch': true,
'kc.seamlessPatch': true,
'kc.nameTabHook': true,
'kc.noQuotaMcp': true,
'kc.extendStallTimeout': true,
'kc.mcpSelfHealing': true,
'kc.agentRetries': true,
'kc.endlessRetries': true,
'kc.extensionProtect': true,
'kc.stripeOverride': true,
'kc.disableTelemetry': true,
'kc.cursorCleanAuto': true,
'kc.bgKeepalive': true,
'kc.loopReminderHook': true,
'kc.noQuotaTogglePort': true,
'kc.disableCursorUpdate': true,
};
function emit(obj) {
console.log(JSON.stringify(obj));
}
function readJson(p, fb = {}) {
try {
return JSON.parse(fs.readFileSync(p, 'utf8'));
} catch {
return fb;
}
}
function writeJson(p, obj) {
fs.mkdirSync(path.dirname(p), { recursive: true });
fs.writeFileSync(p, JSON.stringify(obj, null, 2));
}
function verifyMarkers() {
const wb = fs.existsSync(WORKBENCH) ? fs.readFileSync(WORKBENCH, 'utf8') : '';
const eh = fs.existsSync(EXTHOST) ? fs.readFileSync(EXTHOST, 'utf8') : '';
const checks = [
{ id: 'noQuotaMcp', start: 'CO_NO_QUOTA_MCP_V1_START', src: wb },
{ id: 'composerBridge', start: 'CO_COMPOSER_BRIDGE_START', src: wb },
{ id: 'exthost', start: 'CO_NO_QUOTA_EXTHOST_V1_START', src: eh },
];
const found = checks.filter((m) => m.src.includes(m.start)).map((m) => m.id);
return {
patchOk: found.length >= 3,
found,
total: checks.length,
details: checks.map((m) => ({ id: m.id, start: m.start, found: m.src.includes(m.start) })),
};
}
function isCursorRunning() {
const r = spawnSync('ps', ['aux'], { encoding: 'utf8', maxBuffer: 4 * 1024 * 1024 });
return /\/Applications\/Cursor\.app\/Contents\/MacOS\/Cursor(\s|$)/.test(r.stdout || '');
}
function restoreCoChatStorage() {
const now = new Date().toISOString();
const cur = readJson(COCHAT_STORAGE, {});
const merged = {
...cur,
...PATCH_FLAGS,
'coMcp.aclSetupComplete': true,
'coMcp.aclGrantedPath': WORKBENCH,
'kc.noQuotaMcp.lastMode': cur['kc.noQuotaMcp.lastMode'] || 'extremely_low',
'kc.noQuotaMcp.lastEnabled': true,
'kc.noQuotaMcp.lastExtVersion': cur['kc.noQuotaMcp.lastExtVersion'] || '3.3.34',
'kc.noQuotaMcp.reloadStamps': now,
'kc.noQuotaMcp.reloadReasonTs': now,
'kc.corePatchBundle.reapplyPending': now,
};
writeJson(COCHAT_STORAGE, merged);
return { storagePath: COCHAT_STORAGE, keys: Object.keys(PATCH_FLAGS).length };
}
function queuePending(reason) {
writeJson(PENDING_FILE, { queuedAt: new Date().toISOString(), reason });
}
function clearPending() {
try {
fs.unlinkSync(PENDING_FILE);
} catch {}
}
function runPatchActivate(timeoutMs = 28000) {
return new Promise((resolve) => {
if (!fs.existsSync(PATCH_ACTIVATE)) {
resolve({ ok: false, error: 'patch-activate.mjs 未找到' });
return;
}
const child = spawn(process.execPath, [PATCH_ACTIVATE, 'apply'], {
env: { ...process.env, CO_SKIP_LICENSE: '1', PCHAT_NO_LICENSE: '1' },
});
let out = '';
let err = '';
const timer = setTimeout(() => {
try {
child.kill('SIGTERM');
} catch {}
}, timeoutMs);
child.stdout.on('data', (c) => {
out += c;
});
child.stderr.on('data', (c) => {
err += c;
});
child.on('close', () => {
clearTimeout(timer);
let parsed = null;
try {
parsed = JSON.parse(out.trim().split('\n').filter(Boolean).pop());
} catch {}
if (parsed) return resolve(parsed);
resolve({ ok: false, stdout: out.slice(-2000), stderr: err.slice(-2000) });
});
});
}
async function applyPatchFlow() {
const before = verifyMarkers();
if (before.patchOk) {
clearPending();
return { ok: true, ...before, method: 'already', hint: 'W8 已通过' };
}
const storage = restoreCoChatStorage();
if (isCursorRunning()) {
queuePending('cursor_running');
return {
ok: false,
queued: true,
needsQuit: true,
needsCoChatToggle: true,
storage,
...before,
method: 'queued',
hint:
'已写入 co-chat 补丁配置。请在 Cursor 内co-chat 面板 → 设置 → 关闭再打开「核心增强包」→ 再 Cmd+Q 完全退出 → 重开 Cursor → Hub 点「诊断」。Hub 不会强杀 Cursor。',
};
}
const result = await runPatchActivate();
const after = verifyMarkers();
const ok = after.patchOk || result.patchOk;
if (ok) clearPending();
else queuePending('patch_activate_failed');
return {
ok,
queued: !ok,
storage,
patchResult: result,
...after,
method: ok ? 'patch-activate' : 'patch-activate-failed',
hint: ok
? 'W8 OK — 请重新打开 Cursor 使补丁生效'
: result.hint ||
'补丁未写入:请 Cmd+Q 退出 Cursor 后 Hub 将自动重试,或重开 Cursor 让 co-chat 扩展激活时打补丁',
};
}
async function tryPendingPatch() {
if (!fs.existsSync(PENDING_FILE)) return null;
if (isCursorRunning()) return { waiting: true, reason: 'cursor_still_running' };
restoreCoChatStorage();
const result = await runPatchActivate();
const after = verifyMarkers();
if (after.patchOk) {
clearPending();
return { ok: true, applied: true, ...after, patchResult: result };
}
return { ok: false, applied: false, ...after, patchResult: result };
}
async function main() {
const mode = process.argv[2] || 'apply';
if (mode === 'verify') {
emit({ ok: true, ...verifyMarkers(), method: 'verify' });
return;
}
if (mode === 'pending') {
emit({ ...(await tryPendingPatch()), method: 'pending' });
return;
}
emit(await applyPatchFlow());
}
main().catch((e) => {
emit({ ok: false, error: String(e.stack || e.message || e) });
process.exitCode = 1;
});