'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 强杀 Cursor(SIGTERM 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 = 'W2:workbench 目录不可写,请先修复权限'; saveState(state); return { ok: false, step: 'W2', error: state.lastError, perm: gates.perm, fixScript: PERM_FIX_SCRIPT }; } if (!state.cursorPrefOk && !opts.force) { state.lastError = 'W6:Cursor 前置未满足(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 }, };