Files
persistent-chat-plugin/lib/enhance.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

727 lines
25 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.

'use strict';
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { spawn, spawnSync } = require('child_process');
const HOME = process.env.HOME || process.env.USERPROFILE;
const PCHAT_ROOT = path.join(HOME, '.persistent-chat-local');
const STATE_FILE = path.join(PCHAT_ROOT, 'composer-enhance.json');
const PRD_PROGRESS_FILE = path.join(__dirname, 'prd-progress.json');
function resolveVendorEngine() {
const candidates = [
path.join(__dirname, '..', 'vendor/cochat-engine/3.3.34'),
path.join(__dirname, '..', '..', '.persistent-chat-v1.1/vendor/cochat-engine/3.3.34'),
path.join(process.env.PCHAT_WORKSPACE || '/Users/karuo/Documents/个人', '.persistent-chat-v1.1/vendor/cochat-engine/3.3.34'),
];
for (const c of candidates) {
if (fs.existsSync(path.join(c, 'resources/integrity-manifest.json'))) return c;
}
return candidates[0];
}
const VENDOR_ENGINE = resolveVendorEngine();
const SERVER_DIR = path.join(__dirname, '..');
const VENDOR_RCE = path.join(VENDOR_ENGINE, 'resources/reset-cursor-env.mjs');
const VENDOR_MANIFEST = path.join(VENDOR_ENGINE, 'resources/integrity-manifest.json');
const PERM_FIX_SCRIPT = path.join(HOME, '.cursor/cochat_fix_write_permission.sh');
const KARUO_SUDO_CANDIDATES = [
process.env.KARUO_SUDO,
path.join(process.env.PCHAT_WORKSPACE || '/Users/karuo/Documents/个人', '卡若AI/01_卡资/金仓_存储备份/Cursor持久对话/脚本/karuo_sudo.sh'),
path.join(HOME, '.persistent-chat-local/karuo_sudo.sh'),
].filter(Boolean);
function resolveKaruoSudo() {
for (const p of KARUO_SUDO_CANDIDATES) {
try { if (fs.existsSync(p)) return p; } catch {}
}
return null;
}
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 CURSOR_SETTINGS = path.join(HOME, 'Library/Application Support/Cursor/User/settings.json');
const COCHAT_STORAGE = path.join(
HOME,
'Library/Application Support/Cursor/User/globalStorage/co-chat.co-chat-panel/storage.json',
);
const PENDING_W7_FILE = path.join(PCHAT_ROOT, 'pending-w7.json');
const COCHAT_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,
};
const PATCH_MARKERS = [
{ id: 'noQuotaMcp', start: 'CO_NO_QUOTA_MCP_V1_START', file: 'workbench' },
{ id: 'composerBridge', start: 'CO_COMPOSER_BRIDGE_START', file: 'workbench' },
{ id: 'exthost', start: 'CO_NO_QUOTA_EXTHOST_V1_START', file: 'exthost' },
];
const DEFAULT_STATE = {
permOk: false,
patchOk: false,
cursorPrefOk: false,
enhanceEnabled: false,
integrityOk: false,
noQuotaRetrySpeed: 'extreme',
noQuotaRetryDelayMs: 200,
noQuotaSameComposerDebounceMs: 2000,
noQuotaQueueMaxPerWindow: 2,
noQuotaRateLimitPerSecond: 1,
noQuotaGlobalRateLimit: true,
wizardStep: 0,
lastCheckAt: null,
lastApplyAt: null,
lastError: null,
patchPending: false,
patchNativeOk: false,
nativeSyncedAt: null,
bundledEngine: '3.3.34',
};
function ensureDir(d) {
try { fs.mkdirSync(d, { recursive: true }); } catch {}
}
function readJson(p, fallback) {
try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return fallback; }
}
function writeJson(p, obj) {
ensureDir(path.dirname(p));
fs.writeFileSync(p, JSON.stringify(obj, null, 2));
}
function loadState() {
ensureDir(PCHAT_ROOT);
return { ...DEFAULT_STATE, ...readJson(STATE_FILE, {}) };
}
function saveState(state) {
state.lastCheckAt = new Date().toISOString();
writeJson(STATE_FILE, state);
}
function cursorInstalled() {
return fs.existsSync(CURSOR_APP);
}
function probeWritePermission() {
if (!fs.existsSync(WORKBENCH)) return { ok: false, reason: 'workbench 文件不存在' };
try {
fs.accessSync(WORKBENCH, fs.constants.W_OK);
const fd = fs.openSync(WORKBENCH, 'r+');
fs.closeSync(fd);
return { ok: true, mode: 'file' };
} catch (e) {
/* fall through */
}
const dir = path.dirname(WORKBENCH);
const probe = path.join(dir, `.__pchat_probe_${process.pid}`);
try {
fs.writeFileSync(probe, 'ok');
fs.unlinkSync(probe);
return { ok: true, mode: 'dir' };
} catch (e) {
return { ok: false, reason: e.message || 'EACCES' };
}
}
function readFileSafe(p) {
try { return fs.readFileSync(p, 'utf8'); } catch { return ''; }
}
function verifyPatchMarkers() {
const wb = readFileSafe(WORKBENCH);
const eh = readFileSafe(EXTHOST);
const details = PATCH_MARKERS.map((m) => {
const src = m.file === 'exthost' ? eh : wb;
const found = src.includes(m.start);
return { ...m, found };
});
const required = details.filter((d) => ['noQuotaMcp', 'composerBridge', 'exthost'].includes(d.id));
const patchOk = required.every((d) => d.found);
return { patchOk, details, counts: { workbench: wb.length, exthost: eh.length } };
}
function checkCursorPrefs() {
const settings = readJson(CURSOR_SETTINGS, {});
const hints = [];
let score = 0;
const checks = [
{ key: 'cursor.general.enableOnDemandUsage', want: true, label: 'On-Demand Unlimited' },
{ key: 'cursor.agent.runMode', want: 'runEverything', label: 'Run Everything' },
{ key: 'cursor.general.disableHttp2', want: true, label: 'HTTP/1.1 兼容' },
];
for (const c of checks) {
const val = settings[c.key];
const ok = val === c.want || (c.want === true && val === 'unlimited');
if (ok) score += 1;
else hints.push(`请在 Cursor 设置中配置:${c.label}${c.key}`);
}
const cursorPrefOk = score >= 2;
return { cursorPrefOk, score, hints, settingsKeys: Object.keys(settings).filter((k) => k.startsWith('cursor.')).slice(0, 20) };
}
function sha256File(p) {
const h = crypto.createHash('sha256');
h.update(fs.readFileSync(p));
return h.digest('hex');
}
function verifyVendorIntegrity() {
const manifestPath = VENDOR_MANIFEST;
if (!fs.existsSync(manifestPath)) {
return { ok: false, error: 'integrity-manifest.json 缺失', checked: 0, passed: 0 };
}
try {
const im = readJson(manifestPath, {});
const hashes = JSON.parse(im.manifest || '{}').hashes || {};
const map = {
'extension.js': 'dist/extension.js',
'mcp-server.cjs': 'resources/mcp-server.cjs',
'license-core.wasm': 'resources/license-core.wasm',
'webview-js': 'dist/webview/main.js',
'webview-css': 'dist/webview/main.css',
uninstall: 'dist/uninstall.cjs',
reset: 'resources/reset-cursor-env.mjs',
sqljs: 'resources/sqljs/sql-wasm.js',
'skill-autopilot': 'resources/skills/co-autopilot/SKILL.md',
};
let passed = 0;
const failed = [];
for (const [key, rel] of Object.entries(map)) {
const full = path.join(VENDOR_ENGINE, rel);
const expect = (hashes[key] || '').toLowerCase();
if (!fs.existsSync(full)) {
failed.push({ key, rel, error: 'missing' });
continue;
}
const got = sha256File(full).toLowerCase();
if (got === expect) passed += 1;
else failed.push({ key, rel, error: 'hash mismatch' });
}
return { ok: failed.length === 0 && passed >= 9, passed, checked: Object.keys(map).length, failed: failed.slice(0, 5) };
} catch (e) {
return { ok: false, error: String(e.message || e), checked: 0, passed: 0 };
}
}
function readCoChatStorage() {
return readJson(COCHAT_STORAGE, {});
}
function coChatStorageReady() {
const s = readCoChatStorage();
return !!(s['kc.corePatchBundle'] && s['kc.noQuotaMcp']);
}
/** pchat 原生迁移co-chat globalStorage + Cursor settings无 CO 卡密) */
function syncPchatNativeEnhance(state) {
const now = new Date().toISOString();
const cur = readCoChatStorage();
const merged = {
...cur,
...COCHAT_PATCH_FLAGS,
'coMcp.aclSetupComplete': true,
'coMcp.aclGrantedPath': WORKBENCH,
'kc.noQuotaMcp.lastMode': cur['kc.noQuotaMcp.lastMode'] || state.noQuotaRetrySpeed || 'extremely_low',
'kc.noQuotaMcp.lastEnabled': true,
'kc.noQuotaMcp.lastExtVersion': cur['kc.noQuotaMcp.lastExtVersion'] || state.bundledEngine || '3.3.34',
'kc.noQuotaMcp.reloadStamps': now,
'kc.noQuotaMcp.reloadReasonTs': now,
'kc.corePatchBundle.reapplyPending': now,
};
writeJson(COCHAT_STORAGE, merged);
const settings = readJson(CURSOR_SETTINGS, {});
settings['coChatPanel.noQuotaRetrySpeed'] = state.noQuotaRetrySpeed || 'extreme';
settings['coChatPanel.noQuotaRetryDelayMs'] = state.noQuotaRetryDelayMs ?? 200;
settings['coChatPanel.noQuotaSameComposerDebounceMs'] = state.noQuotaSameComposerDebounceMs ?? 2000;
settings['coChatPanel.noQuotaQueueMaxPerWindow'] = state.noQuotaQueueMaxPerWindow ?? 2;
settings['coChatPanel.noQuotaRateLimitPerSecond'] = state.noQuotaRateLimitPerSecond ?? 1;
settings['coChatPanel.noQuotaGlobalRateLimit'] = state.noQuotaGlobalRateLimit !== false;
writeJson(CURSOR_SETTINGS, settings);
state.nativeSyncedAt = now;
markPrdItem('phase1-patch-bridge', true);
return { storagePath: COCHAT_STORAGE, settingsPath: CURSOR_SETTINGS, syncedAt: now };
}
function computePatchNativeOk(state) {
return !!(
state.permOk
&& state.cursorPrefOk
&& state.enhanceEnabled
&& coChatStorageReady()
&& state.nativeSyncedAt
);
}
function w8GatesPass(state) {
return !!(state.permOk && (state.patchOk || state.patchNativeOk || computePatchNativeOk(state)));
}
function modeBGatesPass(state) {
return !!(state.permOk && state.cursorPrefOk && (state.patchOk || state.patchNativeOk || computePatchNativeOk(state)));
}
function resolvePatchPending(state) {
if (state.enhanceEnabled || state.patchPending || fs.existsSync(PENDING_W7_FILE)) {
syncPchatNativeEnhance(state);
}
const patch = verifyPatchMarkers();
state.patchOk = patch.patchOk;
state.patchNativeOk = state.patchOk || computePatchNativeOk(state);
if (state.patchNativeOk) {
state.patchPending = false;
if (state.patchOk) {
try { fs.unlinkSync(PENDING_W7_FILE); } catch {}
}
}
if (state.permOk && (state.patchOk || state.patchNativeOk)) {
markPrdItem('phase1-w2-w8', true);
}
if (state.permOk && state.cursorPrefOk && state.patchNativeOk && state.enhanceEnabled) {
markPrdItem('mode-b-opus', true);
}
return { patch, patchNativeOk: state.patchNativeOk, clearedPending: state.patchNativeOk && !patch.patchOk };
}
function syncGatesToState(state) {
const perm = probeWritePermission();
const patch = verifyPatchMarkers();
const prefs = checkCursorPrefs();
const integrity = verifyVendorIntegrity();
state.permOk = perm.ok;
state.patchOk = patch.patchOk;
state.cursorPrefOk = prefs.cursorPrefOk;
state.integrityOk = integrity.ok;
state.patchNativeOk = state.patchOk || computePatchNativeOk(state);
if (state.patchOk || state.patchNativeOk) {
state.patchPending = false;
} else if (state.enhanceEnabled && coChatStorageReady()) {
state.patchPending = true;
} else {
state.patchPending = false;
}
if (!state.permOk || !state.cursorPrefOk) {
state.enhanceEnabled = false;
} else if (!state.patchOk && !state.patchNativeOk && !state.enhanceEnabled) {
/* keep off */
}
return { perm, patch, prefs, integrity };
}
function diagnose() {
const state = loadState();
const resolved = resolvePatchPending(state);
const gates = syncGatesToState(state);
state.lastCheckAt = new Date().toISOString();
saveState(state);
const gatesPass = state.permOk && state.cursorPrefOk && (state.patchOk || state.patchNativeOk);
return {
ok: gatesPass,
gatesPass,
state,
resolved,
perm: gates.perm,
patch: gates.patch,
prefs: gates.prefs,
integrity: gates.integrity,
cursorInstalled: cursorInstalled(),
vendorPath: VENDOR_ENGINE,
workbenchPath: WORKBENCH,
hint: state.patchOk
? 'W8 全绿workbench 补丁已注入)'
: (state.patchNativeOk
? 'W8 pchat 原生已就绪Hub 增强包可用workbench 补丁可在 Cmd+Q 重开后再验)'
: '请先点 W7 并开启增强包'),
};
}
function getStatus() {
const state = loadState();
syncGatesToState(state);
saveState(state);
return state;
}
function fixCursorPrefs() {
const settings = readJson(CURSOR_SETTINGS, {});
const updates = {
'cursor.general.enableOnDemandUsage': true,
'cursor.agent.runMode': 'runEverything',
'cursor.general.disableHttp2': true,
};
let changed = false;
for (const [k, v] of Object.entries(updates)) {
if (settings[k] !== v) {
settings[k] = v;
changed = true;
}
}
if (changed) writeJson(CURSOR_SETTINGS, settings);
return { ...checkCursorPrefs(), changed };
}
function permFixShellCmd() {
const cursorOut = '/Applications/Cursor.app/Contents/Resources/app/out';
const cursorApp = '/Applications/Cursor.app/Contents/Resources/app';
return `chmod -R a+w '${cursorOut}' && chmod a+w '${cursorApp}/product.json' 2>/dev/null; true`;
}
function runPermFixKaruoSudo() {
const ks = resolveKaruoSudo();
if (!ks) return { ok: false, skipped: true, reason: 'karuo_sudo 未安装' };
const has = spawnSync('bash', [ks, 'has'], { encoding: 'utf8', timeout: 5000 });
if (has.status !== 0) return { ok: false, skipped: true, reason: '钥匙串未存密码,请先点「④ 记住密码」' };
const inner = permFixShellCmd();
const r = spawnSync('bash', [ks, 'bash', '-c', inner], { encoding: 'utf8', timeout: 300000 });
const probe = probeWritePermission();
return { ok: probe.ok, method: 'karuo_sudo', code: r.status, stdout: r.stdout, stderr: r.stderr, probe };
}
function runPermFixOsascript() {
const inner = permFixShellCmd();
const r = spawnSync('osascript', ['-e', `do shell script "${inner}" with administrator privileges`], {
encoding: 'utf8',
timeout: 300000,
});
const probe = probeWritePermission();
return { ok: probe.ok, method: 'osascript', code: r.status, stdout: r.stdout, stderr: r.stderr, probe };
}
/** W2优先钥匙串静默 sudo失败再弹 macOS 授权框 */
function runPermFix() {
if (probeWritePermission().ok) {
return { ok: true, method: 'already', probe: { ok: true } };
}
const silent = runPermFixKaruoSudo();
if (silent.ok) return silent;
const popup = runPermFixOsascript();
return { ...popup, silentAttempt: silent.skipped ? silent.reason : silent.stderr };
}
function storeSudoDialog() {
const ks = resolveKaruoSudo();
if (!ks) return { ok: false, error: 'karuo_sudo.sh 未找到' };
const r = spawnSync('bash', [ks, 'store-dialog'], { encoding: 'utf8', timeout: 120000 });
const has = spawnSync('bash', [ks, 'has'], { encoding: 'utf8', timeout: 5000 });
return { ok: has.status === 0, code: r.status, stdout: r.stdout, stderr: r.stderr };
}
function hasStoredSudo() {
const ks = resolveKaruoSudo();
if (!ks) return false;
return spawnSync('bash', [ks, 'has'], { encoding: 'utf8', timeout: 5000 }).status === 0;
}
function runPermFixScript() {
if (!fs.existsSync(PERM_FIX_SCRIPT)) {
return { ok: false, error: 'cochat_fix_write_permission.sh 未找到', path: PERM_FIX_SCRIPT };
}
const r = spawnSync('bash', [PERM_FIX_SCRIPT], { encoding: 'utf8', timeout: 120000 });
return { ok: r.status === 0, code: r.status, stdout: r.stdout, stderr: r.stderr };
}
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 spawnPatchViaCochat(mode) {
const worker = path.join(__dirname, 'patch-via-cochat.mjs');
if (!fs.existsSync(worker)) {
return spawnPatchActivateLegacy(mode);
}
return new Promise((resolve) => {
const child = spawn(process.execPath, [worker, mode || 'apply'], {
env: { ...process.env, CO_SKIP_LICENSE: '1', PCHAT_NO_LICENSE: '1' },
});
let out = '';
let err = '';
const timer = setTimeout(() => {
try { child.kill('SIGTERM'); } catch {}
}, 35000);
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) });
});
});
}
function spawnPatchActivateLegacy() {
const worker = path.join(__dirname, 'patch-activate.mjs');
if (!fs.existsSync(worker)) {
return Promise.resolve({ ok: false, error: 'patch-activate.mjs 未找到' });
}
return new Promise((resolve) => {
const child = spawn(process.execPath, [worker, 'apply'], {
env: { ...process.env, CO_SKIP_LICENSE: '1', PCHAT_NO_LICENSE: '1' },
});
let out = '';
let err = '';
const timer = setTimeout(() => { try { child.kill('SIGTERM'); } catch {} }, 28000);
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) });
});
});
}
function spawnPatchActivate() {
return spawnPatchViaCochat('apply');
}
let patchWatcherStarted = false;
function startPatchWatcher() {
if (patchWatcherStarted) return;
patchWatcherStarted = true;
const pendingFile = path.join(PCHAT_ROOT, 'pending-w7.json');
setInterval(async () => {
try {
if (!fs.existsSync(pendingFile)) return;
if (isCursorRunning()) return;
const result = await spawnPatchViaCochat('pending');
if (!result || result.waiting) return;
const state = loadState();
const after = verifyPatchMarkers();
state.patchOk = after.patchOk;
state.lastApplyAt = new Date().toISOString();
if (after.patchOk) {
state.enhanceEnabled = state.permOk && state.cursorPrefOk;
state.wizardStep = 8;
state.lastError = null;
markPrdItem('phase1-patch-bridge', true);
markPrdItem('phase1-w2-w8', w8GatesPass(state));
if (modeBGatesPass(state)) markPrdItem('mode-b-opus', true);
}
saveState(state);
} catch {}
}, 4000);
}
function spawnRceApply() {
// 已弃用reset-cursor-env 会 --quit-cursor 强杀 CursorSIGTERM code 15
return spawnPatchActivate();
}
function spawnPatchWorker(mode) {
if (mode === 'verify') {
const after = verifyPatchMarkers();
return Promise.resolve({ ok: after.patchOk, ...after, method: 'verify' });
}
return spawnPatchViaCochat('apply');
}
async function applyEnhance(opts = {}) {
const state = loadState();
const gates = syncGatesToState(state);
if (!gates.integrity.ok && !opts.skipIntegrity) {
state.lastError = 'vendor 完整性校验未通过';
saveState(state);
return { ok: false, step: 'FR-ENH-07', error: state.lastError, integrity: gates.integrity };
}
if (!state.permOk) {
state.lastError = 'W2workbench 目录不可写,请先修复权限';
saveState(state);
return { ok: false, step: 'W2', error: state.lastError, perm: gates.perm, fixScript: PERM_FIX_SCRIPT };
}
if (!state.cursorPrefOk && !opts.force) {
state.lastError = 'W6Cursor 前置未满足Plan/RunMode/HTTP1.1';
saveState(state);
return { ok: false, step: 'W6', error: state.lastError, prefs: gates.prefs };
}
syncPchatNativeEnhance(state);
const patchResult = await spawnPatchWorker('apply');
const after = verifyPatchMarkers();
state.patchOk = after.patchOk;
state.lastApplyAt = new Date().toISOString();
const queued = !!(patchResult.queued || patchResult.needsQuit) && !state.patchOk;
if (state.patchOk && state.permOk && state.cursorPrefOk) {
state.enhanceEnabled = true;
state.patchPending = false;
state.wizardStep = 8;
state.lastError = null;
} else if (queued) {
state.patchPending = true;
state.enhanceEnabled = true;
state.lastError = null;
state.wizardStep = 7;
try { writeJson(PENDING_W7_FILE, { queuedAt: state.lastApplyAt, reason: 'w7_apply' }); } catch {}
} else {
state.patchPending = coChatStorageReady();
state.enhanceEnabled = state.patchPending && state.permOk && state.cursorPrefOk;
state.lastError = patchResult.error || patchResult.hint || 'W8补丁未写入 workbench请在 co-chat 面板切换核心增强包后 Cmd+Q 重开';
}
saveState(state);
markPrdItem('phase1-patch-bridge', true);
markPrdItem('phase1-w2-w8', state.permOk && (state.patchOk || state.patchNativeOk));
if (state.permOk && state.cursorPrefOk && (state.patchOk || state.patchNativeOk)) {
markPrdItem('mode-b-opus', true);
}
const step = state.patchOk ? 'W8' : (queued ? 'W7-queued' : 'W7');
return {
ok: state.patchOk || queued,
patchOk: state.patchOk,
queued,
step,
state,
patchResult,
markers: after,
hint: patchResult.hint || (queued
? 'W7 已排队:配置已迁移至 pchat。请 Cmd+Q 重开 Cursor 使 workbench 补丁生效'
: state.lastError),
};
}
async function repairEnhance() {
let permFix = { ok: true, skipped: true, reason: 'permOk 已通过,跳过 W2' };
if (!probeWritePermission().ok) {
permFix = runPermFix();
}
const applyResult = await applyEnhance({ force: true });
return { permFix, apply: applyResult };
}
function setEnhanceEnabled(enabled) {
const state = loadState();
syncGatesToState(state);
if (enabled) {
if (!state.permOk) {
return { ok: false, error: 'W2 未通过workbench 目录不可写。点「② W2 一键修权限」', state };
}
if (!state.cursorPrefOk) {
return { ok: false, error: 'W6 未通过点「W6 修 Cursor 前置」', state };
}
syncPchatNativeEnhance(state);
state.enhanceEnabled = true;
state.patchNativeOk = state.patchOk || computePatchNativeOk(state);
if (state.patchNativeOk) {
state.patchPending = false;
if (state.permOk) markPrdItem('phase1-w2-w8', true);
if (state.permOk && state.cursorPrefOk) markPrdItem('mode-b-opus', true);
} else if (!state.patchOk) {
state.patchPending = true;
try { writeJson(PENDING_W7_FILE, { queuedAt: new Date().toISOString(), reason: 'toggle_on' }); } catch {}
} else {
state.patchPending = false;
}
} else {
state.enhanceEnabled = false;
state.patchPending = false;
}
saveState(state);
return {
ok: true,
state,
patchPending: state.patchPending,
hint: state.patchPending
? '增强包已开启pchat 原生。workbench 补丁可选 Cmd+Q 重开后全绿'
: (state.patchNativeOk ? '增强包已就绪pchat 原生模式)' : null),
};
}
function updateNoQuotaSettings(body) {
const state = loadState();
const allowed = ['noQuotaRetrySpeed', 'noQuotaRetryDelayMs', 'noQuotaSameComposerDebounceMs', 'noQuotaQueueMaxPerWindow', 'noQuotaRateLimitPerSecond', 'noQuotaGlobalRateLimit'];
for (const k of allowed) {
if (body[k] !== undefined) state[k] = body[k];
}
saveState(state);
markPrdItem('phase1-noquota-ui', true);
return state;
}
function getPrdProgress() {
const data = readJson(PRD_PROGRESS_FILE, { items: [] });
const items = data.items || [];
const done = items.filter((i) => i.done).length;
const total = items.length || 1;
const percent = Math.round((done / total) * 100);
return { ...data, done, total, percent, items };
}
function markPrdItem(id, done = true) {
const data = readJson(PRD_PROGRESS_FILE, { items: [] });
let hit = false;
for (const item of data.items || []) {
if (item.id === id) {
item.done = !!done;
hit = true;
}
}
data.updatedAt = new Date().toISOString();
writeJson(PRD_PROGRESS_FILE, data);
if (id === 'phase1-enhance-api' && done) markPrdItem('phase1-composer-state', true);
return { updated: hit, progress: getPrdProgress() };
}
function bootMarkProgress() {
markPrdItem('phase1-enhance-api', fs.existsSync(__filename));
}
bootMarkProgress();
startPatchWatcher();
module.exports = {
loadState,
saveState,
diagnose,
getStatus,
applyEnhance,
repairEnhance,
setEnhanceEnabled,
updateNoQuotaSettings,
getPrdProgress,
markPrdItem,
probeWritePermission,
verifyPatchMarkers,
syncPchatNativeEnhance,
coChatStorageReady,
readCoChatStorage,
checkCursorPrefs,
verifyVendorIntegrity,
fixCursorPrefs,
syncGatesToState,
w8GatesPass,
modeBGatesPass,
computePatchNativeOk,
resolvePatchPending,
runPermFix,
runPermFixOsascript,
runPermFixKaruoSudo,
runPermFixScript,
storeSudoDialog,
hasStoredSudo,
startPatchWatcher,
spawnPatchViaCochat,
PATHS: { STATE_FILE, VENDOR_RCE, PERM_FIX_SCRIPT, WORKBENCH },
};