同步 enhance/bridge/smoke/retire 模块、vendor 3.3.34、增强面板与 PRD 文档, Hub 接入 hub-routes-prd,含安装/经验/模块说明与 co-integration 运维脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
187 lines
7.2 KiB
JavaScript
187 lines
7.2 KiB
JavaScript
#!/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;
|
||
});
|