#!/usr/bin/env node /** * W7 补丁:headless 激活 vendor co-chat extension(无 --quit-cursor,不杀 Cursor) */ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { createRequire } from 'node:module'; const require = createRequire(import.meta.url); const __dirname = path.dirname(fileURLToPath(import.meta.url)); 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 EXT_CANDIDATES = [ path.join(__dirname, '../vendor/cochat-engine/3.3.34/dist/extension.js'), path.join(process.env.PCHAT_WORKSPACE || '/Users/karuo/Documents/个人', '.persistent-chat-v1.1/vendor/cochat-engine/3.3.34/dist/extension.js'), path.join(process.env.HOME || '', '.cursor/extensions/co-chat.co-chat-panel-3.3.34/dist/extension.js'), ].map((p) => path.normalize(p)); function emit(obj) { console.log(JSON.stringify(obj)); } function resolveExtensionPath() { for (const p of EXT_CANDIDATES) { if (fs.existsSync(p)) return p; } return null; } function evt() { const subs = []; return { event: (cb) => { subs.push(cb); return { dispose: () => {} }; }, fire: (v) => subs.forEach((f) => f(v)), }; } function installVscodeMock(extPath) { const mockPath = path.join('/tmp', 'pchat-vscode-mock-v2.cjs'); const extRoot = path.dirname(path.dirname(extPath)); const cfgStore = { 'kc.noQuotaMcp': true, 'kc.corePatchBundle': true, 'kc.endlessRetries': true, 'kc.agentSortPerf': true, 'kc.mcpTimeoutGuard': true, 'kc.mcpSelfHeal': true, 'kc.agentStreamRetry': true, 'kc.disableCursorUpdate': true, }; const content = ` const path = require('path'); const fs = require('fs'); const extPath = ${JSON.stringify(extPath)}; const extRoot = ${JSON.stringify(extRoot)}; const cfgStore = ${JSON.stringify(cfgStore)}; const subs = []; function evt(){const s=[];return{event:(cb)=>{s.push(cb);return{dispose:()=>{}};},fire:(v)=>s.forEach(f=>f(v))};} module.exports = { ExtensionContext: function() { this.subscriptions = []; this.globalState = { get: (k,d)=>cfgStore[k]!==undefined?cfgStore[k]:d, update: async (k,v)=>{cfgStore[k]=v;}, keys: ()=>Object.keys(cfgStore), }; this.secrets = { get: async () => undefined, store: async () => {}, delete: async () => {} }; this.extensionPath = extRoot; this.extensionUri = { fsPath: extRoot }; this.storagePath = path.join(require('os').tmpdir(), 'pchat-ext-storage'); this.globalStoragePath = path.join(require('os').tmpdir(), 'pchat-global-storage'); this.logPath = path.join(require('os').tmpdir(), 'pchat-log'); for (const d of [this.storagePath, this.globalStoragePath, this.logPath]) { try { fs.mkdirSync(d, { recursive: true }); } catch {} } }, workspace: { getConfiguration: (section) => ({ get: (k, d) => cfgStore[k] !== undefined ? cfgStore[k] : d, update: async (k, v) => { cfgStore[k] = v; }, has: (k) => cfgStore[k] !== undefined, }), workspaceFolders: [{ uri: { fsPath: process.cwd() }, name: 'ws', index: 0 }], fs: {}, onDidChangeConfiguration: evt().event, onDidChangeWorkspaceFolders: evt().event, onDidOpenTextDocument: evt().event, onDidCloseTextDocument: evt().event, onDidSaveTextDocument: evt().event, onDidCreateFiles: evt().event, onDidDeleteFiles: evt().event, onDidRenameFiles: evt().event, textDocuments: [], getWorkspaceFolder: () => ({ uri: { fsPath: process.cwd() } }), }, window: { showInformationMessage: async () => undefined, showErrorMessage: async () => undefined, showWarningMessage: async () => undefined, createOutputChannel: () => ({ appendLine: () => {}, show: () => {}, dispose: () => {} }), createStatusBarItem: () => ({ show: () => {}, hide: () => {}, dispose: () => {}, text: '' }), }, extensions: { all: [{ id: 'co-chat.co-chat-panel', extensionPath: extRoot, packageJSON: { version: '3.3.34' } }], getExtension: (id) => id && String(id).includes('co-chat') ? { exports: {}, extensionPath: extRoot, packageJSON: { version: '3.3.34', name: 'co-chat-panel' } } : undefined, }, env: { appRoot: ${JSON.stringify(path.join(CURSOR_APP, 'Contents/Resources/app'))}, uriScheme: 'vscode', language: 'zh-cn' }, commands: { registerCommand: () => ({ dispose: () => {} }), executeCommand: async () => {} }, Uri: { file: (f) => ({ fsPath: f, scheme: 'file' }), parse: (u) => ({ fsPath: u, scheme: 'file' }) }, EventEmitter: class { constructor(){this._e=evt();this.event=this._e.event;} fire(v){this._e.fire(v);} }, StatusBarAlignment: { Left: 1, Right: 2 }, ViewColumn: { One: 1 }, ConfigurationTarget: { Global: 1, Workspace: 2 }, }; `; fs.writeFileSync(mockPath, content); const Module = require('module'); const orig = Module._resolveFilename; Module._resolveFilename = function (request, parent, isMain, options) { if (request === 'vscode') return mockPath; return orig.call(this, request, parent, isMain, options); }; } 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) })) }; } async function main() { const mode = process.argv[2] || 'apply'; if (mode === 'verify') { emit({ ok: true, ...verifyMarkers(), method: 'verify' }); return; } const extPath = resolveExtensionPath(); if (!extPath) { emit({ ok: false, error: '未找到 extension.js', candidates: EXT_CANDIDATES }); process.exitCode = 1; return; } process.env.CO_SKIP_LICENSE = '1'; process.env.PCHAT_NO_LICENSE = '1'; try { installVscodeMock(extPath); const mod = await import(pathToFileURL(extPath).href); const activate = mod.activate || mod.default?.activate; if (typeof activate !== 'function') throw new Error('无 activate'); const vscode = require('vscode'); const ctx = new vscode.ExtensionContext(); await activate(ctx); await new Promise((r) => setTimeout(r, 2500)); } catch (e) { emit({ ok: false, error: String(e.message || e), extPath, stack: String(e.stack || '').slice(0, 400) }); process.exitCode = 1; return; } const v = verifyMarkers(); emit({ ok: v.patchOk, ...v, extPath, method: 'patch-activate', hint: v.patchOk ? 'W8 OK — 请 Cmd+Q 重启 Cursor 使补丁生效' : '补丁未写入:请保存工作后 Cmd+Q 重启 Cursor,再点 W7', }); } main().catch((e) => { emit({ ok: false, error: String(e.stack || e.message || e) }); process.exitCode = 1; });