refactor(bridge): migrate gravity bridge to pure websocket gateway architecture, deleting legacy local file scanners and dependencies
This commit is contained in:
Binary file not shown.
@@ -120,15 +120,6 @@ function detectProjectName() {
|
||||
return 'default';
|
||||
}
|
||||
// ─── Bridge File I/O ───
|
||||
function ensureBridgeDir() {
|
||||
const dirs = ['', 'response', 'commands', 'chat_snapshots'];
|
||||
for (const d of dirs) {
|
||||
const p = path.join(bridgePath, d);
|
||||
if (!fs.existsSync(p)) {
|
||||
fs.mkdirSync(p, { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
// Module-level activeSessionId so writeChatSnapshot can register sessions lazily
|
||||
let activeSessionId = '';
|
||||
let activeTrajectoryId = '';
|
||||
@@ -136,39 +127,16 @@ let activeTrajectoryId = '';
|
||||
const recentDiscordSentTexts = new Map();
|
||||
function writeChatSnapshot(text) {
|
||||
try {
|
||||
// WS route (preferred) — skip file write to prevent duplicate Discord delivery
|
||||
if (wsBridge && wsBridge.isConnected()) {
|
||||
wsBridge.sendChat({
|
||||
content: text,
|
||||
conversation_id: activeSessionId,
|
||||
conversation_id: (0, step_probe_1.getActiveSessionId)(),
|
||||
project_name: projectName,
|
||||
});
|
||||
logToFile(`[SNAPSHOT-WS] sent (${text.length} chars)`);
|
||||
if (activeSessionId) {
|
||||
(0, step_probe_1.writeRegistration)(activeSessionId);
|
||||
if ((0, step_probe_1.getActiveSessionId)()) {
|
||||
(0, step_probe_1.writeRegistration)((0, step_probe_1.getActiveSessionId)());
|
||||
}
|
||||
return;
|
||||
}
|
||||
// File route (fallback — only when WS is NOT connected)
|
||||
const snapshotDir = path.join(bridgePath, 'chat_snapshots');
|
||||
if (!fs.existsSync(snapshotDir)) {
|
||||
fs.mkdirSync(snapshotDir, { recursive: true });
|
||||
}
|
||||
const id = Date.now().toString();
|
||||
const data = {
|
||||
id,
|
||||
project_name: projectName,
|
||||
content: text,
|
||||
timestamp: Date.now() / 1000,
|
||||
};
|
||||
const filePath = path.join(snapshotDir, `${id}.json`);
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
console.log(`Gravity Bridge: chat snapshot written (${text.length} chars) → ${id}.json`);
|
||||
logToFile(`[SNAPSHOT] written ${id}.json (${text.length} chars)`);
|
||||
logToFile(`[SNAPSHOT] content: ${text.substring(0, 200)}`);
|
||||
// Lazily register session → project mapping (correct because projectName is per-window)
|
||||
if (activeSessionId) {
|
||||
(0, step_probe_1.writeRegistration)(activeSessionId);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
@@ -177,38 +145,17 @@ function writeChatSnapshot(text) {
|
||||
}
|
||||
function writeChatSnapshotWithFiles(text, files) {
|
||||
try {
|
||||
// WS route (preferred) — skip file write to prevent duplicate Discord delivery
|
||||
if (wsBridge && wsBridge.isConnected()) {
|
||||
wsBridge.sendChat({
|
||||
content: text,
|
||||
attached_files: files,
|
||||
conversation_id: activeSessionId,
|
||||
conversation_id: (0, step_probe_1.getActiveSessionId)(),
|
||||
project_name: projectName,
|
||||
});
|
||||
logToFile(`[SNAPSHOT-WS] sent with ${files.length} files (${text.length} chars)`);
|
||||
if (activeSessionId) {
|
||||
(0, step_probe_1.writeRegistration)(activeSessionId);
|
||||
if ((0, step_probe_1.getActiveSessionId)()) {
|
||||
(0, step_probe_1.writeRegistration)((0, step_probe_1.getActiveSessionId)());
|
||||
}
|
||||
return;
|
||||
}
|
||||
// File route (fallback — only when WS is NOT connected)
|
||||
const snapshotDir = path.join(bridgePath, 'chat_snapshots');
|
||||
if (!fs.existsSync(snapshotDir)) {
|
||||
fs.mkdirSync(snapshotDir, { recursive: true });
|
||||
}
|
||||
const id = Date.now().toString();
|
||||
const data = {
|
||||
id,
|
||||
project_name: projectName,
|
||||
content: text,
|
||||
attached_files: files,
|
||||
timestamp: Date.now() / 1000,
|
||||
};
|
||||
const filePath = path.join(snapshotDir, `${id}.json`);
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
logToFile(`[SNAPSHOT] written ${id}.json (${text.length} chars, ${files.length} files)`);
|
||||
if (activeSessionId) {
|
||||
(0, step_probe_1.writeRegistration)(activeSessionId);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
@@ -405,7 +352,6 @@ async function activate(context) {
|
||||
const config = vscode.workspace.getConfiguration('gravityBridge');
|
||||
const configPath = config.get('bridgePath');
|
||||
bridgePath = configPath || path.join(os.homedir(), '.gemini', 'antigravity', 'bridge');
|
||||
ensureBridgeDir();
|
||||
console.log(`Gravity Bridge: bridge path: ${bridgePath}`);
|
||||
// ── WebSocket Hub Connection ──
|
||||
const hubUrl = process.env.GRAVITY_HUB_URL || config.get('hubUrl') || '';
|
||||
@@ -541,6 +487,7 @@ async function activate(context) {
|
||||
get activeSessionId() { return (0, step_probe_1.getStepProbeContext)().activeSessionId; },
|
||||
get sessionStalled() { return (0, step_probe_1.getStepProbeContext)().sessionStalled; },
|
||||
get lastPendingStepIndex() { return (0, step_probe_1.getStepProbeContext)().lastPendingStepIndex; },
|
||||
writeChatSnapshot,
|
||||
};
|
||||
const bridgePort = await (0, http_bridge_1.startHttpBridge)(httpBridgeCtx, sdk);
|
||||
let localPort = bridgePort;
|
||||
|
||||
File diff suppressed because one or more lines are too long
454
extension/package-lock.json
generated
454
extension/package-lock.json
generated
@@ -1,82 +1,380 @@
|
||||
{
|
||||
"name": "gravity-bridge",
|
||||
"version": "0.5.25",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "gravity-bridge",
|
||||
"version": "0.5.25",
|
||||
"dependencies": {
|
||||
"ws": "^8.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/vscode": "^1.100.0",
|
||||
"typescript": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.100.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.37",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz",
|
||||
"integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/vscode": {
|
||||
"version": "1.100.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.100.0.tgz",
|
||||
"integrity": "sha512-4uNyvzHoraXEeCamR3+fzcBlh7Afs4Ifjs4epINyUX/jvdk0uzLnwiDY35UKDKnkCHP5Nu3dljl2H8lR6s+rQw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
"name": "gravity-bridge",
|
||||
"version": "0.5.34",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "gravity-bridge",
|
||||
"version": "0.5.34",
|
||||
"dependencies": {
|
||||
"cheerio": "^1.2.0",
|
||||
"ws": "^8.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/vscode": "^1.100.0",
|
||||
"typescript": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.100.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.37",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz",
|
||||
"integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/vscode": {
|
||||
"version": "1.100.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.100.0.tgz",
|
||||
"integrity": "sha512-4uNyvzHoraXEeCamR3+fzcBlh7Afs4Ifjs4epINyUX/jvdk0uzLnwiDY35UKDKnkCHP5Nu3dljl2H8lR6s+rQw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/cheerio": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz",
|
||||
"integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cheerio-select": "^2.1.0",
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.2.2",
|
||||
"encoding-sniffer": "^0.2.1",
|
||||
"htmlparser2": "^10.1.0",
|
||||
"parse5": "^7.3.0",
|
||||
"parse5-htmlparser2-tree-adapter": "^7.1.0",
|
||||
"parse5-parser-stream": "^7.1.2",
|
||||
"undici": "^7.19.0",
|
||||
"whatwg-mimetype": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.18.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cheerio-select": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
|
||||
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-select": "^5.1.0",
|
||||
"css-what": "^6.1.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
|
||||
"integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-what": "^6.1.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"domutils": "^3.0.1",
|
||||
"nth-check": "^2.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/css-what": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
|
||||
"integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"entities": "^4.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domelementtype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
],
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/domhandler": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/encoding-sniffer": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
|
||||
"integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"iconv-lite": "^0.6.3",
|
||||
"whatwg-encoding": "^3.1.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
|
||||
"integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
|
||||
"funding": [
|
||||
"https://github.com/fb55/htmlparser2?sponsor=1",
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.2.2",
|
||||
"entities": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2/node_modules/entities": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
|
||||
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nth-check": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5-htmlparser2-tree-adapter": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
|
||||
"integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"domhandler": "^5.0.3",
|
||||
"parse5": "^7.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5-parser-stream": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
|
||||
"integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parse5": "^7.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5/node_modules/entities": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz",
|
||||
"integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.18.1"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/whatwg-encoding": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
||||
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
|
||||
"deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"iconv-lite": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-mimetype": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
|
||||
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "gravity-bridge",
|
||||
"displayName": "Gravity Bridge",
|
||||
"description": "Discord-based unified approval system for Antigravity AI interactions.",
|
||||
"version": "0.5.30",
|
||||
"version": "0.5.36",
|
||||
"publisher": "variet",
|
||||
"engines": {
|
||||
"vscode": "^1.100.0"
|
||||
@@ -84,6 +84,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"cheerio": "^1.2.0",
|
||||
"ws": "^8.19.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
96
extension/src/brain-watcher.ts
Normal file
96
extension/src/brain-watcher.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { WSBridgeClient } from './ws-client';
|
||||
|
||||
export interface BrainWatcherContext {
|
||||
logToFile: (msg: string) => void;
|
||||
wsBridge: WSBridgeClient;
|
||||
projectName: string;
|
||||
}
|
||||
|
||||
export class BrainWatcher {
|
||||
private brainDir: string;
|
||||
private ctx: BrainWatcherContext;
|
||||
private currentSessionId: string = '';
|
||||
private watcher: fs.FSWatcher | null = null;
|
||||
private lastEventTimes: Map<string, number> = new Map();
|
||||
|
||||
constructor(ctx: BrainWatcherContext) {
|
||||
this.ctx = ctx;
|
||||
// The bridgePath is ~/.gemini/antigravity/bridge, so brain is sibling
|
||||
this.brainDir = path.join(os.homedir(), '.gemini', 'antigravity', 'brain');
|
||||
}
|
||||
|
||||
public updateSession(sessionId: string) {
|
||||
if (!sessionId || this.currentSessionId === sessionId) {
|
||||
return;
|
||||
}
|
||||
this.currentSessionId = sessionId;
|
||||
this.startWatching(sessionId);
|
||||
}
|
||||
|
||||
private startWatching(sessionId: string) {
|
||||
this.stop();
|
||||
|
||||
const sessionDir = path.join(this.brainDir, sessionId);
|
||||
if (!fs.existsSync(sessionDir)) {
|
||||
// It might not be created yet, poll gently
|
||||
setTimeout(() => this.startWatching(sessionId), 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.watcher = fs.watch(sessionDir, { persistent: false }, (eventType, filename) => {
|
||||
if (!filename || !filename.endsWith('.md')) return;
|
||||
|
||||
// Dedup rapid events
|
||||
const now = Date.now();
|
||||
const last = this.lastEventTimes.get(filename) || 0;
|
||||
if (now - last < 500) return; // 500ms debounce
|
||||
this.lastEventTimes.set(filename, now);
|
||||
|
||||
this.handleFileChange(sessionDir, filename, eventType);
|
||||
});
|
||||
this.ctx.logToFile(`[BRAIN-WATCHER] Started watching session: ${sessionId.substring(0, 8)}`);
|
||||
} catch (e: any) {
|
||||
this.ctx.logToFile(`[BRAIN-WATCHER] Failed to watch ${sessionId}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private handleFileChange(dir: string, filename: string, rawEventType: string) {
|
||||
const filePath = path.join(dir, filename);
|
||||
let content = '';
|
||||
let eventType = 'file_changed';
|
||||
|
||||
try {
|
||||
if (fs.existsSync(filePath)) {
|
||||
content = fs.readFileSync(filePath, 'utf-8');
|
||||
} else {
|
||||
eventType = 'file_deleted';
|
||||
}
|
||||
} catch (e) {
|
||||
// File might be locked or deleted during read
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.ctx.wsBridge && this.ctx.wsBridge.isConnected()) {
|
||||
this.ctx.wsBridge.sendBrainEvent({
|
||||
event_type: eventType,
|
||||
conversation_id: this.currentSessionId,
|
||||
file_name: filename,
|
||||
content: content,
|
||||
timestamp: Date.now() / 1000,
|
||||
project_name: this.ctx.projectName,
|
||||
});
|
||||
this.ctx.logToFile(`[BRAIN-WATCHER] Sent ${eventType} for ${filename}`);
|
||||
}
|
||||
}
|
||||
|
||||
public stop() {
|
||||
if (this.watcher) {
|
||||
this.watcher.close();
|
||||
this.watcher = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,13 +86,7 @@ function detectProjectName(): string {
|
||||
|
||||
// ─── Bridge File I/O ───
|
||||
|
||||
function ensureBridgeDir() {
|
||||
const dirs = ['', 'response', 'commands', 'chat_snapshots'];
|
||||
for (const d of dirs) {
|
||||
const p = path.join(bridgePath, d);
|
||||
if (!fs.existsSync(p)) { fs.mkdirSync(p, { recursive: true }); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Module-level activeSessionId so writeChatSnapshot can register sessions lazily
|
||||
let activeSessionId = '';
|
||||
@@ -102,34 +96,15 @@ const recentDiscordSentTexts: Map<string, number> = new Map();
|
||||
|
||||
function writeChatSnapshot(text: string) {
|
||||
try {
|
||||
// WS route (preferred) — skip file write to prevent duplicate Discord delivery
|
||||
if (wsBridge && wsBridge.isConnected()) {
|
||||
wsBridge.sendChat({
|
||||
content: text,
|
||||
conversation_id: activeSessionId,
|
||||
conversation_id: getStepProbeSessionId(),
|
||||
project_name: projectName,
|
||||
});
|
||||
logToFile(`[SNAPSHOT-WS] sent (${text.length} chars)`);
|
||||
if (activeSessionId) { writeRegistration(activeSessionId); }
|
||||
return;
|
||||
if (getStepProbeSessionId()) { writeRegistration(getStepProbeSessionId()); }
|
||||
}
|
||||
// File route (fallback — only when WS is NOT connected)
|
||||
const snapshotDir = path.join(bridgePath, 'chat_snapshots');
|
||||
if (!fs.existsSync(snapshotDir)) { fs.mkdirSync(snapshotDir, { recursive: true }); }
|
||||
const id = Date.now().toString();
|
||||
const data = {
|
||||
id,
|
||||
project_name: projectName,
|
||||
content: text,
|
||||
timestamp: Date.now() / 1000,
|
||||
};
|
||||
const filePath = path.join(snapshotDir, `${id}.json`);
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
console.log(`Gravity Bridge: chat snapshot written (${text.length} chars) → ${id}.json`);
|
||||
logToFile(`[SNAPSHOT] written ${id}.json (${text.length} chars)`);
|
||||
logToFile(`[SNAPSHOT] content: ${text.substring(0, 200)}`);
|
||||
// Lazily register session → project mapping (correct because projectName is per-window)
|
||||
if (activeSessionId) { writeRegistration(activeSessionId); }
|
||||
} catch (e: any) {
|
||||
console.log(`Gravity Bridge: snapshot write error: ${e.message}`);
|
||||
}
|
||||
@@ -137,33 +112,16 @@ function writeChatSnapshot(text: string) {
|
||||
|
||||
function writeChatSnapshotWithFiles(text: string, files: Array<{ name: string, content: string }>) {
|
||||
try {
|
||||
// WS route (preferred) — skip file write to prevent duplicate Discord delivery
|
||||
if (wsBridge && wsBridge.isConnected()) {
|
||||
wsBridge.sendChat({
|
||||
content: text,
|
||||
attached_files: files,
|
||||
conversation_id: activeSessionId,
|
||||
conversation_id: getStepProbeSessionId(),
|
||||
project_name: projectName,
|
||||
});
|
||||
logToFile(`[SNAPSHOT-WS] sent with ${files.length} files (${text.length} chars)`);
|
||||
if (activeSessionId) { writeRegistration(activeSessionId); }
|
||||
return;
|
||||
if (getStepProbeSessionId()) { writeRegistration(getStepProbeSessionId()); }
|
||||
}
|
||||
// File route (fallback — only when WS is NOT connected)
|
||||
const snapshotDir = path.join(bridgePath, 'chat_snapshots');
|
||||
if (!fs.existsSync(snapshotDir)) { fs.mkdirSync(snapshotDir, { recursive: true }); }
|
||||
const id = Date.now().toString();
|
||||
const data = {
|
||||
id,
|
||||
project_name: projectName,
|
||||
content: text,
|
||||
attached_files: files,
|
||||
timestamp: Date.now() / 1000,
|
||||
};
|
||||
const filePath = path.join(snapshotDir, `${id}.json`);
|
||||
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
logToFile(`[SNAPSHOT] written ${id}.json (${text.length} chars, ${files.length} files)`);
|
||||
if (activeSessionId) { writeRegistration(activeSessionId); }
|
||||
} catch (e: any) {
|
||||
console.log(`Gravity Bridge: snapshot+files write error: ${e.message}`);
|
||||
}
|
||||
@@ -383,7 +341,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
const config = vscode.workspace.getConfiguration('gravityBridge');
|
||||
const configPath = config.get<string>('bridgePath');
|
||||
bridgePath = configPath || path.join(os.homedir(), '.gemini', 'antigravity', 'bridge');
|
||||
ensureBridgeDir();
|
||||
|
||||
console.log(`Gravity Bridge: bridge path: ${bridgePath}`);
|
||||
|
||||
// ── WebSocket Hub Connection ──
|
||||
@@ -527,6 +485,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
get activeSessionId() { return getStepProbeContext().activeSessionId; },
|
||||
get sessionStalled() { return getStepProbeContext().sessionStalled; },
|
||||
get lastPendingStepIndex() { return getStepProbeContext().lastPendingStepIndex; },
|
||||
writeChatSnapshot,
|
||||
};
|
||||
const bridgePort = await startHttpBridge(httpBridgeCtx, sdk);
|
||||
let localPort = bridgePort;
|
||||
|
||||
@@ -13,6 +13,8 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { WSBridgeClient } from './ws-client';
|
||||
|
||||
let lastFilePermissionTime = 0;
|
||||
|
||||
// ─── Context interface (shared state from extension.ts) ───
|
||||
|
||||
export interface HttpBridgeContext {
|
||||
@@ -127,7 +129,7 @@ export function startHttpBridge(ctx: HttpBridgeContext, sdk: any): Promise<numbe
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
fs.writeFileSync(path.join(ctx.bridgePath, 'dump_html.json'), dumpBody, 'utf-8');
|
||||
} catch(e) {}
|
||||
} catch (e) { }
|
||||
res.writeHead(200); res.end('ok');
|
||||
});
|
||||
return;
|
||||
@@ -140,9 +142,9 @@ export function startHttpBridge(ctx: HttpBridgeContext, sdk: any): Promise<numbe
|
||||
try {
|
||||
const params = JSON.parse(rpcBody);
|
||||
const result = await sdk.ls.rawRPC(params.method, params.args || {});
|
||||
res.writeHead(200, {'Content-Type': 'application/json'});
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(typeof result === 'string' ? result : JSON.stringify(result));
|
||||
} catch(e: any) {
|
||||
} catch (e: any) {
|
||||
res.writeHead(500); res.end(e.message);
|
||||
}
|
||||
});
|
||||
@@ -246,9 +248,6 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
|
||||
}
|
||||
|
||||
const rid = data.request_id || Date.now().toString();
|
||||
// Write pending file for Discord bot
|
||||
const pendingDir = path.join(ctx.bridgePath, 'pending');
|
||||
if (!fs.existsSync(pendingDir)) fs.mkdirSync(pendingDir, { recursive: true });
|
||||
const pending: Record<string, any> = {
|
||||
...data,
|
||||
request_id: rid,
|
||||
@@ -265,22 +264,13 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
|
||||
if (cmdLower.includes('allow') && !pending.buttons) {
|
||||
// Dedup: skip if another file_permission pending was created within 10s
|
||||
const nowMs = Date.now();
|
||||
try {
|
||||
const existingFiles = fs.readdirSync(pendingDir).filter((f: string) => f.endsWith('.json'));
|
||||
for (const ef of existingFiles) {
|
||||
const existing = JSON.parse(fs.readFileSync(path.join(pendingDir, ef), 'utf-8'));
|
||||
if (existing.step_type === 'file_permission' && existing.status === 'pending'
|
||||
&& existing.project_name === ctx.projectName) {
|
||||
const age = nowMs - (existing.timestamp * 1000);
|
||||
if (age < 10_000 && age >= 0) {
|
||||
ctx.logToFile(`[HTTP] filtered duplicate file_permission (${age}ms old): ${ef}`);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ ok: false, filtered: true, reason: 'dedup_file_permission' }));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
if (nowMs - lastFilePermissionTime < 10000) {
|
||||
ctx.logToFile(`[HTTP] filtered duplicate file_permission (${nowMs - lastFilePermissionTime}ms old)`);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ ok: false, filtered: true, reason: 'dedup_file_permission' }));
|
||||
return;
|
||||
}
|
||||
lastFilePermissionTime = nowMs;
|
||||
|
||||
pending.buttons = [
|
||||
{ text: 'Allow Once', index: 0 },
|
||||
@@ -292,8 +282,7 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
|
||||
const rawDesc = (data.description || data.command || '').replace(/Deny|Allow Once|Allow This Conversation/gi, '').trim();
|
||||
pending.command = `파일 접근 권한${rawDesc ? ': ' + rawDesc : ''}`;
|
||||
}
|
||||
fs.writeFileSync(path.join(pendingDir, `${rid}.json`), JSON.stringify(pending, null, 2));
|
||||
// WS dual-write
|
||||
// WS dispatch
|
||||
if (ctx.wsBridge && ctx.wsBridge.isConnected()) {
|
||||
ctx.wsBridge.sendPending({
|
||||
request_id: rid,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export function generateApprovalObserverScript(_port: number): string {
|
||||
return `
|
||||
// ── Gravity Bridge v4: React Tailwind UI Observer ──
|
||||
// ── Gravity Bridge v5: Context-First DOM Extraction ──
|
||||
(function(){
|
||||
'use strict';
|
||||
var BASE='',_obs=false,_sent={},_ready=false;
|
||||
@@ -9,7 +9,7 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
var CLEANUP_MS=300000;
|
||||
|
||||
function log(m){console.log('[GB Observer] '+m);}
|
||||
log('v4 Script loaded — deep Tailwind DOM traversal enabled');
|
||||
log('v5 Script loaded — Context-First Tailored Extraction');
|
||||
|
||||
// React-Compatible Synthetic Clicker
|
||||
function dispatchReactClick(el){
|
||||
@@ -21,19 +21,10 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
el.dispatchEvent(new MouseEvent('mouseup', {bubbles:true, cancelable:true, view:window, composed:true}));
|
||||
el.dispatchEvent(new MouseEvent('click', {bubbles:true, cancelable:true, view:window, composed:true}));
|
||||
} catch(e) {
|
||||
el.click(); // fallback
|
||||
el.click();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Find common container for the step ──
|
||||
function findButtonContainer(btn){
|
||||
return btn.closest('.p-1')
|
||||
|| btn.closest('.bg-agent-convo-background')
|
||||
|| btn.closest('[class*="border-gray-500/10"]')
|
||||
|| btn.closest('.monaco-list-row')
|
||||
|| btn.parentElement;
|
||||
}
|
||||
|
||||
function cleanButtonText(btn) {
|
||||
if (!btn) return '';
|
||||
var clone = btn.cloneNode(true);
|
||||
@@ -43,10 +34,9 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
}
|
||||
var tr = clone.querySelector('.truncate');
|
||||
var txt = (tr ? tr.textContent : clone.textContent) || '';
|
||||
return txt.trim().replace(/^[\s\u200B-\u200D\uFEFF\u00A0]+/, '').replace(/(Alt|Ctrl|Shift|Meta)\\+.*/i,'').trim();
|
||||
return txt.trim().replace(/^[\s\u200B-\u200D\uFEFF\u00A0]+/, '').replace(/(Alt|Ctrl|Shift|Meta)\\\\+.*/i,'').trim();
|
||||
}
|
||||
|
||||
// ── Stable button fingerprint ──
|
||||
function btnId(b,type){
|
||||
var txt = cleanButtonText(b);
|
||||
var parent = b.parentElement;
|
||||
@@ -58,104 +48,78 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
return type+'|'+txt+'|'+idx;
|
||||
}
|
||||
|
||||
// ── Context extraction — target BOTH chat history and command payload ──
|
||||
function extractCommandContext(b){
|
||||
var container = findButtonContainer(b);
|
||||
var container = b.closest('.p-1') || b.parentElement.parentElement;
|
||||
if (!container) return "";
|
||||
|
||||
var titleSpans = container.querySelectorAll('span[title^="command("]');
|
||||
if (titleSpans && titleSpans.length > 0) {
|
||||
var t = titleSpans[0].getAttribute('title');
|
||||
if (t && t.length > 5) return t.substring(0, 800);
|
||||
}
|
||||
|
||||
var preEls = container.querySelectorAll('pre');
|
||||
if (preEls && preEls.length > 0) {
|
||||
var t2 = (preEls[preEls.length-1].textContent || '').trim();
|
||||
if (t2.length > 2) return t2.substring(0, 800);
|
||||
}
|
||||
|
||||
var codeText = '';
|
||||
var codes = container.querySelectorAll('code, [class*="command"]');
|
||||
for(var i=0; i<codes.length; i++) {
|
||||
codeText += (codes[i].textContent || '').trim() + ' ';
|
||||
}
|
||||
if (codeText.length > 2) return codeText.trim().substring(0, 800);
|
||||
|
||||
var fallback = (container.textContent || '').replace(cleanButtonText(b), '').trim();
|
||||
return fallback.substring(0, 500);
|
||||
}
|
||||
|
||||
function extractChatContextFromNode(botTurn) {
|
||||
if (!botTurn) return '';
|
||||
|
||||
var res = '';
|
||||
// Use innerText if available on the markdown container (preserves spacing perfectly)
|
||||
var md = botTurn.querySelector('.markdown-body') || botTurn.querySelector('.prose');
|
||||
if (md && md.innerText && md.innerText.trim().length > 10) {
|
||||
res = md.innerText.trim();
|
||||
return res.substring(0, 3500);
|
||||
}
|
||||
|
||||
var toolContainer = botTurn.querySelector('.bg-agent-convo-background') || botTurn.querySelector('.bg-ide-background-color');
|
||||
var textParts = [];
|
||||
function walk(node) {
|
||||
if (toolContainer && node === toolContainer) return;
|
||||
if (node.id === 'antigravity.agentSidePanelInputBox') return;
|
||||
if (node.nodeType === 1) {
|
||||
var tag = node.tagName.toUpperCase();
|
||||
if (tag==='BUTTON' || tag==='SVG' || tag==='STYLE' || tag==='SCRIPT') return;
|
||||
// Skip tool action blocks aggressively if they masquerade as normal divs
|
||||
if (node !== botTurn && node.classList && (node.classList.contains('bg-ide-background-color') || node.classList.contains('bg-agent-convo-background'))) return;
|
||||
}
|
||||
if (node.nodeType === 3) {
|
||||
var val = node.nodeValue;
|
||||
if (val && val.trim()) textParts.push(val.trim());
|
||||
} else {
|
||||
if (node.childNodes && node.childNodes.length > 0) {
|
||||
for(var i=0; i<node.childNodes.length; i++) {
|
||||
walk(node.childNodes[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.nodeType === 1) {
|
||||
var tg = node.tagName.toUpperCase();
|
||||
if (tg==='P' || tg==='DIV' || tg==='BR' || tg==='LI' || tg==='PRE') textParts.push('\\n');
|
||||
}
|
||||
}
|
||||
walk(botTurn);
|
||||
res = textParts.join(' ').replace(/ \\n /g, '\\n').replace(/\\n+/g, '\\n').trim();
|
||||
return res.substring(0, 3500);
|
||||
}
|
||||
|
||||
function extractChatContext(b) {
|
||||
try {
|
||||
var botTurn = b.closest('.bg-agent-convo-background') || b.closest('.text-ide-message-block-bot-color');
|
||||
var botTurn = b.closest('.text-ide-message-block-bot-color') || b.closest('.bg-agent-convo-background');
|
||||
if (!botTurn) {
|
||||
var container = findButtonContainer(b);
|
||||
var container = b.closest('.p-1') || b.parentElement;
|
||||
botTurn = container ? container.parentElement : null;
|
||||
}
|
||||
if (!botTurn) return '';
|
||||
|
||||
var toolContainer = findButtonContainer(b) || b;
|
||||
var textParts = [];
|
||||
|
||||
function walk(node) {
|
||||
if (node === toolContainer) return true; // Stop traversal at the tool box
|
||||
if (node.nodeType === 1) {
|
||||
var tag = node.tagName.toUpperCase();
|
||||
if (tag==='BUTTON' || tag==='SVG' || tag==='STYLE' || tag==='SCRIPT') return false;
|
||||
}
|
||||
if (node.nodeType === 3) {
|
||||
var val = node.nodeValue;
|
||||
if (val && val.trim()) textParts.push(val.trim());
|
||||
} else {
|
||||
for(var i=0; i<node.childNodes.length; i++) {
|
||||
if (walk(node.childNodes[i])) return true;
|
||||
}
|
||||
}
|
||||
if (node.nodeType === 1) {
|
||||
var tg = node.tagName.toUpperCase();
|
||||
if (tg==='P' || tg==='DIV' || tg==='BR' || tg==='LI' || tg==='PRE') textParts.push('\\n');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
walk(botTurn);
|
||||
var result = textParts.join(' ').replace(/ \\n /g, '\\n').replace(/\\n+/g, '\\n').trim();
|
||||
return result.substring(0, 1500);
|
||||
return extractChatContextFromNode(botTurn);
|
||||
} catch(e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function extractChatContextFromNode(botTurn) {
|
||||
if (!botTurn) return '';
|
||||
var toolContainer = botTurn.querySelector('.bg-ide-background-color'); // Stop at tool blocks
|
||||
var textParts = [];
|
||||
function walk(node) {
|
||||
if (toolContainer && node === toolContainer) return true;
|
||||
if (node.nodeType === 1) {
|
||||
var tag = node.tagName.toUpperCase();
|
||||
if (tag==='BUTTON' || tag==='SVG' || tag==='STYLE' || tag==='SCRIPT') return false;
|
||||
}
|
||||
if (node.nodeType === 3) {
|
||||
var val = node.nodeValue;
|
||||
if (val && val.trim()) textParts.push(val.trim());
|
||||
} else {
|
||||
for(var i=0; i<node.childNodes.length; i++) {
|
||||
if (walk(node.childNodes[i])) return true;
|
||||
}
|
||||
}
|
||||
if (node.nodeType === 1) {
|
||||
var tg = node.tagName.toUpperCase();
|
||||
if (tg==='P' || tg==='DIV' || tg==='BR' || tg==='LI' || tg==='PRE') textParts.push('\\n');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
walk(botTurn);
|
||||
var result = textParts.join(' ').replace(/ \\n /g, '\\n').replace(/\\n+/g, '\\n').trim();
|
||||
return result.substring(0, 3500);
|
||||
}
|
||||
|
||||
function extractContext(b) {
|
||||
var cmd = extractCommandContext(b);
|
||||
var chat = extractChatContext(b);
|
||||
@@ -166,15 +130,21 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
return combined.trim();
|
||||
}
|
||||
|
||||
// ── Action Buttons Patterns (EN / KO) ──
|
||||
var PATS = [
|
||||
{ type: 'command', re: /^(?:Always\\s*)?(?:Run\\b|결행사양\\s*항상|결행)/i },
|
||||
{ type: 'permission', re: /^(?:Always\\s*)?(?:Allow\\b|허용)/i },
|
||||
{ type: 'permission', re: /^(?:Always\\s*)?(?:Approve\\b|승인)/i },
|
||||
{ type: 'diff_review', re: /^(?:Always\\s*)?(?:Accept\\b|수락|반영)/i },
|
||||
];
|
||||
var ALL_ACTION_RE=[/^(?:Always\\s*)?(?:Run\\b|결행)/i,/^(?:Always\\s*)?(?:Accept\\b|수락|반영)/i,/^(?:Reject\\b|거절|거부)/i,/^(?:Always\\s*)?(?:Allow\\b|허용)/i,/^(?:Deny\\b|차단)/i,/^(?:Always\\s*)?(?:Approve\\b|승인)/i,/^(?:Cancel\\b|취소)/i,/^Retry\\b/i,/^(?:Dismiss\\b|무시)/i,/^(?:Stop\\b|정지)/i,/^Decline\\b/i];
|
||||
var REJECT_RE=[/^(?:Reject\\b|거절|거부)/i,/^(?:Cancel\\b|취소)/i,/^(?:Deny\\b|차단)/i,/^(?:Stop\\b|정지)/i,/^Decline\\b/i,/^(?:Dismiss\\b|무시)/i];
|
||||
var ACTION_WORDS = ['Allow', 'Run', 'Approve', 'Accept', '결행', '수락', '반영', '허용', '승인'];
|
||||
var REJECT_WORDS = ['Reject', 'Cancel', 'Deny', 'Stop', 'Decline', 'Dismiss', '거절', '거부', '취소', '차단', '정지', '무시'];
|
||||
|
||||
function isActionBtn(txt) {
|
||||
for(var i=0; i<ACTION_WORDS.length; i++) {
|
||||
if(txt.indexOf(ACTION_WORDS[i]) !== -1) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function isRejectBtn(txt) {
|
||||
for(var i=0; i<REJECT_WORDS.length; i++) {
|
||||
if(txt.indexOf(REJECT_WORDS[i]) !== -1) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function collectSiblingButtons(container,triggerBtn){
|
||||
if(!container)return [];
|
||||
@@ -183,110 +153,86 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
for(var i=0;i<siblings.length;i++){
|
||||
var sb=siblings[i];
|
||||
if(sb.disabled||sb.hidden||(!sb.offsetParent&&sb.style.display!=='fixed'))continue;
|
||||
|
||||
var stxt = cleanButtonText(sb);
|
||||
if(stxt.length <= 1) continue; // Ignore icon buttons
|
||||
|
||||
var isAction=false;
|
||||
for(var a=0;a<ALL_ACTION_RE.length;a++){
|
||||
if(ALL_ACTION_RE[a].test(stxt)){isAction=true;break;}
|
||||
}
|
||||
if(!isAction)continue;
|
||||
if(stxt.length <= 1) continue;
|
||||
if(!isActionBtn(stxt) && !isRejectBtn(stxt)) continue;
|
||||
result.push({btn:sb,text:stxt,isPrimary:(sb===triggerBtn)});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
var HARDCODED_PORT=${_port};
|
||||
|
||||
function tryPingAsync(port){
|
||||
return fetch('http://127.0.0.1:'+port+'/ping?t='+Date.now(),{signal:AbortSignal.timeout(2000)})
|
||||
.then(function(r){return r.text();})
|
||||
.then(function(t){return t==='pong';})
|
||||
.catch(function(){return false;});
|
||||
.then(function(r){return r.text();}).then(function(t){return t==='pong';}).catch(function(){return false;});
|
||||
}
|
||||
|
||||
function discoverPort(cb){
|
||||
log('Waiting for Gravity Bridge status...');
|
||||
var attempts=0;
|
||||
var timer=setInterval(function(){
|
||||
attempts++;
|
||||
var items = document.querySelectorAll('[aria-label^="Gravity Bridge Control"], [title^="Gravity Bridge Control"]');
|
||||
if (items.length > 0) {
|
||||
var text = items[0].getAttribute('aria-label') || items[0].getAttribute('title') || '';
|
||||
var m = text.match(/port:(\d+)/);
|
||||
var m = text.match(/port:(\\d+)/);
|
||||
if (m && m[1]) {
|
||||
var domPort = parseInt(m[1], 10);
|
||||
clearInterval(timer);
|
||||
tryPingAsync(domPort).then(function(ok){
|
||||
if(ok) cb(domPort); else cb(HARDCODED_PORT);
|
||||
});
|
||||
tryPingAsync(parseInt(m[1], 10)).then(function(ok){ cb(ok ? parseInt(m[1],10) : HARDCODED_PORT); });
|
||||
return;
|
||||
}
|
||||
}
|
||||
// If we are in the webview, the status bar is invisible. Skip quickly.
|
||||
if(attempts>1){
|
||||
clearInterval(timer);
|
||||
tryPingAsync(HARDCODED_PORT).then(function(ok){ cb(HARDCODED_PORT); }); // Assume HARDCODED_PORT works!
|
||||
tryPingAsync(HARDCODED_PORT).then(function(){ cb(HARDCODED_PORT); });
|
||||
}
|
||||
},500); // Wait 500ms * 2 = 1 second total
|
||||
},500);
|
||||
}
|
||||
|
||||
discoverPort(function(port){
|
||||
BASE='http://127.0.0.1:'+port;
|
||||
fetch(BASE+'/ping').then(function(r){return r.text();}).then(function(t){
|
||||
if(t==='pong'){_ready=true;startObserver();}
|
||||
}).catch(function(e){});
|
||||
_ready=true;
|
||||
startObserver();
|
||||
});
|
||||
|
||||
var _chatSnapshots = [];
|
||||
var _firstChatScan = true;
|
||||
var _lastText = "";
|
||||
var _lastTextTime = 0;
|
||||
var _lastTextSent = false;
|
||||
|
||||
function scanChatBodies() {
|
||||
if(!_ready)return;
|
||||
var botTurns = document.querySelectorAll('.text-ide-message-block-bot-color');
|
||||
for (var i = 0; i < botTurns.length; i++) {
|
||||
var turn = botTurns[i];
|
||||
if (turn.dataset.agChatScraped === "true" || turn.dataset.agChatScraped === "pending") continue;
|
||||
|
||||
if (_firstChatScan) {
|
||||
turn.dataset.agChatScraped = "true";
|
||||
continue;
|
||||
}
|
||||
|
||||
var currentText = turn.textContent || '';
|
||||
var found = -1;
|
||||
for (var j = 0; j < _chatSnapshots.length; j++) {
|
||||
if (_chatSnapshots[j].node === turn) { found = j; break; }
|
||||
}
|
||||
|
||||
if (found === -1) {
|
||||
_chatSnapshots.push({ node: turn, text: currentText, lastChanged: Date.now() });
|
||||
} else {
|
||||
if (_chatSnapshots[found].text !== currentText) {
|
||||
_chatSnapshots[found].text = currentText;
|
||||
_chatSnapshots[found].lastChanged = Date.now();
|
||||
if (botTurns.length === 0) return;
|
||||
|
||||
var lastTurn = botTurns[botTurns.length - 1];
|
||||
if (lastTurn.dataset.agChatScraped === "true" || lastTurn.dataset.agChatScraped === "pending") return;
|
||||
|
||||
var currentText = lastTurn.textContent || '';
|
||||
if (currentText.length < 5) return;
|
||||
|
||||
if (_lastText !== currentText) {
|
||||
_lastText = currentText;
|
||||
_lastTextTime = Date.now();
|
||||
_lastTextSent = false;
|
||||
} else if (!_lastTextSent) {
|
||||
if (Date.now() - _lastTextTime > 3000) {
|
||||
_lastTextSent = true;
|
||||
lastTurn.dataset.agChatScraped = "pending";
|
||||
var finalTxt = extractChatContextFromNode(lastTurn);
|
||||
if (finalTxt && finalTxt.length > 5 && finalTxt !== "Review Changes") {
|
||||
fetch(BASE+'/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: finalTxt })
|
||||
}).then(function(){
|
||||
lastTurn.dataset.agChatScraped = "true";
|
||||
}).catch(function(){
|
||||
lastTurn.dataset.agChatScraped = "false";
|
||||
});
|
||||
} else {
|
||||
if (Date.now() - _chatSnapshots[found].lastChanged > 3500) {
|
||||
turn.dataset.agChatScraped = "pending"; // prevent re-entry
|
||||
var finalTxt = extractChatContextFromNode(turn);
|
||||
if (finalTxt && finalTxt.length > 5) {
|
||||
fetch(BASE+'/chat', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ text: finalTxt })
|
||||
}).then(function(){
|
||||
turn.dataset.agChatScraped = "true";
|
||||
}).catch(function(){
|
||||
turn.dataset.agChatScraped = "false"; // retry
|
||||
});
|
||||
} else {
|
||||
turn.dataset.agChatScraped = "true";
|
||||
}
|
||||
}
|
||||
lastTurn.dataset.agChatScraped = "true";
|
||||
}
|
||||
}
|
||||
}
|
||||
_firstChatScan = false;
|
||||
}
|
||||
|
||||
function scan(){
|
||||
@@ -301,26 +247,17 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
if(b.disabled||b.hidden||(!b.offsetParent&&b.style.display!=='fixed'))continue;
|
||||
|
||||
var txt=cleanButtonText(b);
|
||||
console.log("[JSDOM] Button scan:", txt);
|
||||
if(txt.length <= 1) continue; // Icon
|
||||
if(txt.length <= 1) continue;
|
||||
|
||||
var matchedType=null;
|
||||
for(var p=0;p<PATS.length;p++){
|
||||
if(PATS[p].re.test(txt)){
|
||||
if (b.closest('.codelens-decoration') && PATS[p].type !== 'diff_review' && PATS[p].type !== 'permission') {
|
||||
continue;
|
||||
}
|
||||
matchedType=PATS[p].type;
|
||||
break;
|
||||
}
|
||||
if(!isActionBtn(txt)) continue;
|
||||
// Skip inline code lens buttons unless they actually match the pattern properly
|
||||
if (b.closest('.codelens-decoration') && !txt.includes('Accept') && !txt.includes('수락') && !txt.includes('반영')) {
|
||||
continue;
|
||||
}
|
||||
if(!matchedType){
|
||||
console.log("[JSDOM] NOT MATCHED:", txt);
|
||||
continue;
|
||||
}
|
||||
var container=findButtonContainer(b);
|
||||
|
||||
var matchedType = txt.includes('Accept') ? 'diff_review' : (txt.includes('Run') ? 'command' : 'permission');
|
||||
var container=b.closest('.p-1') || b.parentElement.parentElement;
|
||||
var groupKey=matchedType+'|'+btnId(b,matchedType);
|
||||
console.log("[JSDOM] MATCHED:", matchedType, "KEY:", groupKey, "SENT:", !!_sent[groupKey]);
|
||||
if(_sent[groupKey])continue;
|
||||
|
||||
var siblings=collectSiblingButtons(container,b);
|
||||
@@ -338,7 +275,6 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
}
|
||||
|
||||
var desc=extractContext(b);
|
||||
|
||||
var is_dom_dummy = false;
|
||||
if (!desc || desc.trim().length <= 2) {
|
||||
desc = "MISSING_DESCRIPTION_FROM_DOM_FALLBACK_TO_STEP_PROBE";
|
||||
@@ -417,17 +353,15 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
}
|
||||
|
||||
function clickRejectButton(approveBtn){
|
||||
var container=findButtonContainer(approveBtn);
|
||||
var container=approveBtn.closest('.p-1') || approveBtn.parentElement.parentElement;
|
||||
if(!container)return;
|
||||
var siblings=container.querySelectorAll('button');
|
||||
for(var i=0;i<siblings.length;i++){
|
||||
var t=cleanButtonText(siblings[i]);
|
||||
for(var r=0;r<REJECT_RE.length;r++){
|
||||
if(REJECT_RE[r].test(t)){
|
||||
log('Clicking reject: '+t);
|
||||
dispatchReactClick(siblings[i]);
|
||||
return;
|
||||
}
|
||||
if(isRejectBtn(t)){
|
||||
log('Clicking reject: '+t);
|
||||
dispatchReactClick(siblings[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -476,22 +410,17 @@ export function generateApprovalObserverScript(_port: number): string {
|
||||
if(_ready&&BASE){
|
||||
fetch(BASE+'/trigger-click?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
|
||||
if(!d.action)return;
|
||||
var approveRe=[/^(?:Always\\\\s*)?(?:Run\\\\b|결행)/i,/^(?:Always\\\\s*)?(?:Accept\\\\b|수락)/i,/^(?:Always\\\\s*)?(?:Accept all\\\\b|모두 수락)/i,/^(?:Always\\\\s*)?(?:Allow\\\\b|허용)/i,/^(?:Always\\\\s*)?(?:Approve\\\\b|승인)/i];
|
||||
var rejectRe=[/^(?:Reject\\\\b|거절|거부)/i,/^(?:Cancel\\\\b|취소)/i,/^(?:Deny\\\\b|차단)/i,/^(?:Stop\\\\b|정지)/i,/^Decline\\\\b/i,/^(?:Dismiss\\\\b|무시)/i];
|
||||
var patterns=(d.action==='approve')?approveRe:rejectRe;
|
||||
|
||||
var isApprove = (d.action==='approve');
|
||||
var btns = document.querySelectorAll('button');
|
||||
for(var i=0;i<btns.length;i++){
|
||||
var bx = btns[i];
|
||||
if(bx.disabled||bx.hidden||(!bx.offsetParent&&bx.style.display!=='fixed'))continue;
|
||||
var t = cleanButtonText(bx);
|
||||
if(t.length <= 1) continue;
|
||||
for(var pi=0;pi<patterns.length;pi++){
|
||||
if(patterns[pi].test(t)){
|
||||
log('Fallback TRIGGER-CLICK on "' + t + '"');
|
||||
dispatchReactClick(bx);
|
||||
return;
|
||||
}
|
||||
if (isApprove ? isActionBtn(t) : isRejectBtn(t)) {
|
||||
log('Fallback TRIGGER-CLICK on "' + t + '"');
|
||||
dispatchReactClick(bx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}).catch(function(){});
|
||||
|
||||
@@ -9,6 +9,7 @@ import * as path from 'path';
|
||||
import { WSBridgeClient } from './ws-client';
|
||||
import { extractPlannerText, extractToolCommand, extractToolDescription } from './step-utils';
|
||||
import { initApprovalHandler, setupResponseWatcher } from './approval-handler';
|
||||
import { BrainWatcher } from './brain-watcher';
|
||||
|
||||
// Re-export from approval-handler for backward compatibility with extension.ts imports
|
||||
export { handleDiffReviewResponse, tryApprovalStrategies } from './approval-handler';
|
||||
@@ -35,6 +36,7 @@ export interface BridgeContext {
|
||||
|
||||
let ctx: BridgeContext;
|
||||
let responseWatcher: fs.FSWatcher | null = null;
|
||||
let brainWatcher: BrainWatcher | null = null;
|
||||
let activeTrajectoryId = '';
|
||||
const recentPendingSteps = new Map<string, number>();
|
||||
const PENDING_MEMORY_TTL_MS = 60_000;
|
||||
@@ -276,15 +278,16 @@ function setupMonitor() {
|
||||
} catch (e: any) {
|
||||
if (pollCount <= 30) ctx.logToFile(`[POLL] Fallback 2 error for sid=${sid}: ${e.message}`);
|
||||
// If trajectory explicitly does not exist, it might be an Antigravity or non-Cascade session directory.
|
||||
if (e.message?.includes('trajectory not found')) {
|
||||
continue;
|
||||
}
|
||||
// FIXED: known-issues "AI Response Missing for New Sessions" -> Force register to prevent session loss on proto/UTF-8 parse errors
|
||||
// We MUST register it so activeSessionId tracks it properly.
|
||||
// To prevent old ghost sessions from hijacking, we only mark it RUNNING if it was recently modified.
|
||||
const ageMs = Date.now() - brainDirs[i].time;
|
||||
const isFresh = ageMs < 120_000; // updated within 2 mins
|
||||
|
||||
allTraj.trajectorySummaries[sid] = {
|
||||
status: 'CASCADE_RUN_STATUS_RUNNING',
|
||||
status: isFresh ? 'CASCADE_RUN_STATUS_RUNNING' : 'CASCADE_RUN_STATUS_IDLE',
|
||||
stepCount: 1, // Assume progressing to allow loop delta>0 trigger
|
||||
lastModifiedTime: new Date(brainDirs[i].time).toISOString(),
|
||||
summary: 'Discovered via brain/ scan (Fallback Error)',
|
||||
summary: 'Discovered via brain/ scan (Antigravity Native)',
|
||||
trajectoryMetadata: { workspaces: [{ workspaceFolderAbsoluteUri: ctx.workspaceUri.replace(/\\/g, '/') }] }
|
||||
};
|
||||
}
|
||||
@@ -381,6 +384,9 @@ function setupMonitor() {
|
||||
// Session changed?
|
||||
if (bestSessionId !== ctx.activeSessionId) {
|
||||
ctx.activeSessionId = bestSessionId;
|
||||
if (brainWatcher) {
|
||||
brainWatcher.updateSession(bestSessionId);
|
||||
}
|
||||
activeTrajectoryId = (bestSession as any).trajectoryId || '';
|
||||
activeSessionTitle = currentTitle;
|
||||
lastKnownStepCount = currentCount;
|
||||
@@ -1261,6 +1267,13 @@ export function writePendingApproval(data: { conversation_id: string; command: s
|
||||
*/
|
||||
export function initStepProbe(context: BridgeContext) {
|
||||
ctx = context;
|
||||
if (ctx.wsBridge) {
|
||||
brainWatcher = new BrainWatcher({
|
||||
logToFile: ctx.logToFile,
|
||||
wsBridge: ctx.wsBridge,
|
||||
projectName: ctx.projectName
|
||||
});
|
||||
}
|
||||
initApprovalHandler(context, () => activeTrajectoryId);
|
||||
setupMonitor();
|
||||
setupResponseWatcher();
|
||||
|
||||
Reference in New Issue
Block a user