同步 enhance/bridge/smoke/retire 模块、vendor 3.3.34、增强面板与 PRD 文档, Hub 接入 hub-routes-prd,含安装/经验/模块说明与 co-integration 运维脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
150 lines
5.9 KiB
JavaScript
150 lines
5.9 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* pchat 补丁桥接:无 CO 卡密,直调 vendor co-chat 扩展补丁逻辑(headless)。
|
||
*/
|
||
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 mode = process.argv[2] || 'apply';
|
||
const vendorArg = process.argv.indexOf('--vendor');
|
||
const vendorRce = vendorArg >= 0 ? process.argv[vendorArg + 1] : 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 EXT_CANDIDATES = [
|
||
vendorRce ? path.join(path.dirname(vendorRce), '..', 'dist/extension.js') : null,
|
||
path.join(process.env.HOME || '', '.persistent-chat-local/lib/../vendor/cochat-engine/3.3.34/dist/extension.js'),
|
||
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'),
|
||
].filter(Boolean).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 installVscodeMock(extPath) {
|
||
const mockPath = path.join('/tmp', 'pchat-vscode-mock.cjs');
|
||
const content = `
|
||
const path = require('path');
|
||
const fs = require('fs');
|
||
const extPath = ${JSON.stringify(extPath)};
|
||
const subs = [];
|
||
module.exports = {
|
||
ExtensionContext: function() {
|
||
this.subscriptions = [];
|
||
this.globalState = { get: () => undefined, update: async () => {}, keys: () => [] };
|
||
this.secrets = { get: async () => undefined, store: async () => {}, delete: async () => {} };
|
||
this.extensionPath = path.dirname(path.dirname(extPath));
|
||
this.extensionUri = { fsPath: this.extensionPath };
|
||
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) => {
|
||
const map = { 'kc.noQuotaMcp': true, 'kc.corePatchBundle': true, 'kc.endlessRetries': true };
|
||
return map[k] !== undefined ? map[k] : d;
|
||
},
|
||
update: async () => {},
|
||
has: () => true,
|
||
}),
|
||
workspaceFolders: [],
|
||
fs: {},
|
||
},
|
||
window: {
|
||
showInformationMessage: async (m) => { console.error('[info]', m); return undefined; },
|
||
showErrorMessage: async (m) => { console.error('[err]', m); return undefined; },
|
||
showWarningMessage: async () => undefined,
|
||
},
|
||
extensions: {
|
||
all: [],
|
||
getExtension: (id) => id && id.includes('co-chat') ? { exports: {}, extensionPath: path.dirname(path.dirname(extPath)), packageJSON: { version: '3.3.34' } } : undefined,
|
||
},
|
||
env: { appRoot: ${JSON.stringify(path.join(CURSOR_APP, 'Contents/Resources/app'))}, uriScheme: 'vscode' },
|
||
commands: { registerCommand: () => ({ dispose: () => {} }) },
|
||
Uri: { file: (f) => ({ fsPath: f }) },
|
||
EventEmitter: class { constructor() { this.event = (cb) => { subs.push(cb); return { dispose: () => {} }; }; } fire(v) { subs.forEach((f) => f(v)); } },
|
||
};
|
||
`;
|
||
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);
|
||
};
|
||
}
|
||
|
||
async function tryHeadlessPatch(extPath) {
|
||
installVscodeMock(extPath);
|
||
process.env.CO_SKIP_LICENSE = '1';
|
||
process.env.PCHAT_NO_LICENSE = '1';
|
||
const extUrl = pathToFileURL(extPath).href;
|
||
const mod = await import(extUrl);
|
||
const activate = mod.activate || mod.default?.activate;
|
||
if (typeof activate !== 'function') {
|
||
throw new Error('extension.js 无 activate 导出');
|
||
}
|
||
const vscode = require('vscode');
|
||
const ctx = new vscode.ExtensionContext();
|
||
await activate(ctx);
|
||
await new Promise((r) => setTimeout(r, 5000));
|
||
return { activated: true };
|
||
}
|
||
|
||
function verifyMarkers() {
|
||
const wb = fs.existsSync(WORKBENCH) ? fs.readFileSync(WORKBENCH, 'utf8') : '';
|
||
const eh = fs.existsSync(EXTHOST) ? fs.readFileSync(EXTHOST, 'utf8') : '';
|
||
const markers = [
|
||
{ 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 = markers.filter((m) => m.src.includes(m.start)).map((m) => m.id);
|
||
return { patchOk: found.length >= 3, found, total: markers.length };
|
||
}
|
||
|
||
async function main() {
|
||
if (mode === 'verify') {
|
||
emit({ ok: true, ...verifyMarkers() });
|
||
return;
|
||
}
|
||
const extPath = resolveExtensionPath();
|
||
if (!extPath) {
|
||
emit({ ok: false, error: '未找到 vendor/extension.js', candidates: EXT_CANDIDATES });
|
||
process.exitCode = 1;
|
||
return;
|
||
}
|
||
try {
|
||
await tryHeadlessPatch(extPath);
|
||
} catch (e) {
|
||
emit({ ok: false, error: 'headless activate 失败: ' + (e.message || e), extPath, stack: String(e.stack || '').slice(0, 500) });
|
||
process.exitCode = 1;
|
||
return;
|
||
}
|
||
const v = verifyMarkers();
|
||
emit({ ok: v.patchOk, ...v, extPath, hint: v.patchOk ? 'W8 OK' : '补丁未注入:请 Cmd+Q 退出 Cursor 后重试 W7' });
|
||
}
|
||
|
||
main().catch((e) => {
|
||
emit({ ok: false, error: String(e.stack || e.message || e) });
|
||
process.exitCode = 1;
|
||
});
|