Files
persistent-chat-plugin/lib/patch-worker.mjs
卡若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

150 lines
5.9 KiB
JavaScript
Raw 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.

#!/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;
});