fix(bridge): align Extension protocol with Bot — 3 mismatches fixed
- Snapshot: response/chat_snapshot.txt → chat_snapshots/*.json - Command field: cmd.message → cmd.text (matches Bot.write_command) - RPC: GetConversation (404) → GetCascadeTrajectorySteps - Bundle sql-wasm.js + sql-wasm.wasm into VSIX (45KB→379KB) - Handle consumed flag, clean 38 stale commands - Add extractAIText helper with fallback chain
This commit is contained in:
@@ -56,7 +56,7 @@ function detectProjectName(): string {
|
||||
// ─── Bridge File I/O ───
|
||||
|
||||
function ensureBridgeDir() {
|
||||
const dirs = ['', 'response', 'commands'];
|
||||
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 }); }
|
||||
@@ -65,9 +65,19 @@ function ensureBridgeDir() {
|
||||
|
||||
function writeChatSnapshot(text: string) {
|
||||
try {
|
||||
const filePath = path.join(bridgePath, 'response', 'chat_snapshot.txt');
|
||||
fs.writeFileSync(filePath, text, 'utf-8');
|
||||
console.log(`Gravity Bridge: chat snapshot written (${text.length} chars)`);
|
||||
// Write to chat_snapshots/*.json for Bot's chat_snapshot_scanner to pick up
|
||||
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`);
|
||||
} catch (e: any) {
|
||||
console.log(`Gravity Bridge: snapshot write error: ${e.message}`);
|
||||
}
|
||||
@@ -87,36 +97,50 @@ function processCommandFile(filePath: string) {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const cmd = JSON.parse(content);
|
||||
|
||||
// Ignore commands for other projects
|
||||
if (cmd.project_name && cmd.project_name !== projectName) {
|
||||
console.log(`Gravity Bridge: skipping command for "${cmd.project_name}"`);
|
||||
// Skip already consumed commands
|
||||
if (cmd.consumed) {
|
||||
try { fs.unlinkSync(filePath); } catch { }
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Gravity Bridge: command — "${cmd.message || cmd.action}"`);
|
||||
// Ignore commands for other projects
|
||||
if (cmd.project_name && cmd.project_name !== projectName) {
|
||||
console.log(`Gravity Bridge: skipping command for "${cmd.project_name}" (we are "${projectName}")`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmd.action === 'approve' && sdk) {
|
||||
// Bot writes 'text' field, not 'message'
|
||||
const text = cmd.text || cmd.message || '';
|
||||
const action = cmd.action || '';
|
||||
|
||||
console.log(`Gravity Bridge: command — text="${text}" action="${action}"`);
|
||||
|
||||
if (action === 'approve' && sdk) {
|
||||
sdk.cascade.acceptStep().catch((e: any) =>
|
||||
console.log(`Gravity Bridge: approve error: ${e.message}`)
|
||||
);
|
||||
} else if (cmd.action === 'reject' && sdk) {
|
||||
} else if (action === 'reject' && sdk) {
|
||||
sdk.cascade.rejectStep().catch((e: any) =>
|
||||
console.log(`Gravity Bridge: reject error: ${e.message}`)
|
||||
);
|
||||
} else if (cmd.action === 'approve_terminal' && sdk) {
|
||||
} else if (action === 'approve_terminal' && sdk) {
|
||||
sdk.cascade.acceptTerminalCommand().catch((e: any) =>
|
||||
console.log(`Gravity Bridge: approve_terminal error: ${e.message}`)
|
||||
);
|
||||
} else if (cmd.message && sdk) {
|
||||
// Send message to Antigravity
|
||||
sdk.cascade.sendPrompt(cmd.message).then(() => {
|
||||
console.log(`Gravity Bridge: ✅ sent via SDK sendPrompt`);
|
||||
}).catch((e: any) => {
|
||||
// Fallback to VS Code command
|
||||
console.log(`Gravity Bridge: SDK sendPrompt failed: ${e.message}, trying command...`);
|
||||
vscode.commands.executeCommand('antigravity.sendPromptToAgentPanel', cmd.message)
|
||||
.then(() => console.log('Gravity Bridge: ✅ sent via sendPromptToAgentPanel'));
|
||||
});
|
||||
} else if (text === '!stop') {
|
||||
// Cancel current operation
|
||||
vscode.commands.executeCommand('antigravity.agent.rejectAgentStep')
|
||||
.then(() => console.log('Gravity Bridge: ✅ stop sent'),
|
||||
() => { });
|
||||
} else if (text.startsWith('!auto ')) {
|
||||
// Auto-approve mode toggle
|
||||
const mode = text.includes('on') ? 'true' : 'false';
|
||||
console.log(`Gravity Bridge: auto-approve → ${mode}`);
|
||||
} else if (text) {
|
||||
// Send message to Antigravity — use VS Code command (most reliable)
|
||||
vscode.commands.executeCommand('antigravity.sendPromptToAgentPanel', text)
|
||||
.then(() => console.log(`Gravity Bridge: ✅ sent "${text.substring(0, 50)}" via sendPromptToAgentPanel`),
|
||||
(e: any) => console.log(`Gravity Bridge: sendPrompt failed: ${e.message}`));
|
||||
}
|
||||
|
||||
// Remove processed command file
|
||||
@@ -173,47 +197,63 @@ async function initSDK(context: vscode.ExtensionContext): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
// Track last seen step per session to avoid re-fetching
|
||||
const lastSeenStep = new Map<string, number>();
|
||||
|
||||
function setupMonitor() {
|
||||
if (!sdk) { return; }
|
||||
|
||||
// Step count changed → fetch conversation content
|
||||
// Step count changed → fetch new steps via GetCascadeTrajectorySteps
|
||||
sdk.monitor.onStepCountChanged(async (e: any) => {
|
||||
console.log(`Gravity Bridge: [SDK] step changed: "${e.title}" step ${e.newCount} (+${e.delta})`);
|
||||
|
||||
// Get fresh session to have the ID
|
||||
try {
|
||||
const conversation = await sdk.ls.getConversation(e.sessionId);
|
||||
if (conversation && conversation.messages) {
|
||||
// Find the last assistant message
|
||||
const assistantMsgs = conversation.messages.filter(
|
||||
(m: any) => m.role === 'assistant' && m.content
|
||||
);
|
||||
if (assistantMsgs.length > 0) {
|
||||
const lastMsg = assistantMsgs[assistantMsgs.length - 1];
|
||||
const text = `🤖 **${e.title}**\n\n${lastMsg.content}`;
|
||||
writeChatSnapshot(text);
|
||||
console.log(`Gravity Bridge: [SDK] relayed AI response (${lastMsg.content.length} chars)`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Use the correct LS RPC: GetCascadeTrajectorySteps (not GetConversation which doesn't exist)
|
||||
const fromStep = lastSeenStep.get(e.sessionId) ?? Math.max(0, e.newCount - e.delta);
|
||||
|
||||
// Find PlannerResponse steps
|
||||
if (conversation && conversation.steps) {
|
||||
const responses = conversation.steps.filter(
|
||||
(s: any) => s.type === 'PlannerResponse' && s.summary
|
||||
);
|
||||
if (responses.length > 0) {
|
||||
const last = responses[responses.length - 1];
|
||||
writeChatSnapshot(`🤖 **${e.title}**\n\n${last.summary}`);
|
||||
console.log(`Gravity Bridge: [SDK] relayed PlannerResponse (${last.summary.length} chars)`);
|
||||
const stepsData = await sdk.ls.rawRPC('GetCascadeTrajectorySteps', {
|
||||
cascadeId: e.sessionId,
|
||||
startStepIndex: fromStep
|
||||
});
|
||||
|
||||
lastSeenStep.set(e.sessionId, e.newCount);
|
||||
|
||||
if (stepsData) {
|
||||
// Try to extract AI text from the steps response
|
||||
const aiText = extractAIText(stepsData);
|
||||
if (aiText) {
|
||||
const text = `🤖 **${e.title}**\n\n${aiText}`;
|
||||
writeChatSnapshot(text);
|
||||
console.log(`Gravity Bridge: [SDK] relayed AI response (${aiText.length} chars)`);
|
||||
return;
|
||||
}
|
||||
// Log the raw structure for debugging
|
||||
console.log(`Gravity Bridge: [SDK] steps data keys: ${JSON.stringify(Object.keys(stepsData))}`);
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.log(`Gravity Bridge: [SDK] getConversation error: ${err.message}`);
|
||||
console.log(`Gravity Bridge: [SDK] GetCascadeTrajectorySteps error: ${err.message}`);
|
||||
|
||||
// Fallback: try GetCascadeTrajectory (full trajectory, heavier)
|
||||
try {
|
||||
const fullTraj = await sdk.ls.rawRPC('GetCascadeTrajectory', {
|
||||
cascadeId: e.sessionId
|
||||
});
|
||||
if (fullTraj) {
|
||||
const aiText = extractAIText(fullTraj);
|
||||
if (aiText) {
|
||||
writeChatSnapshot(`🤖 **${e.title}**\n\n${aiText}`);
|
||||
console.log(`Gravity Bridge: [SDK] relayed via GetCascadeTrajectory (${aiText.length} chars)`);
|
||||
return;
|
||||
}
|
||||
console.log(`Gravity Bridge: [SDK] trajectory keys: ${JSON.stringify(Object.keys(fullTraj))}`);
|
||||
}
|
||||
} catch (err2: any) {
|
||||
console.log(`Gravity Bridge: [SDK] GetCascadeTrajectory also failed: ${err2.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: just send the title + step info
|
||||
lastSeenStep.set(e.sessionId, e.newCount);
|
||||
writeChatSnapshot(`🤖 **${e.title}**\n\n(step ${e.newCount}, +${e.delta})`);
|
||||
});
|
||||
|
||||
@@ -237,6 +277,54 @@ function setupMonitor() {
|
||||
console.log('Gravity Bridge: [SDK] monitor started (USS 3s, trajectory 2s)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract AI response text from LS RPC step/trajectory data.
|
||||
* The exact structure depends on the protobuf schema — we try multiple paths.
|
||||
*/
|
||||
function extractAIText(data: any): string | null {
|
||||
if (!data) { return null; }
|
||||
|
||||
// Try common protobuf response patterns
|
||||
// Pattern 1: steps array with content
|
||||
const steps = data.steps || data.trajectorySteps || data.cascadeSteps;
|
||||
if (Array.isArray(steps) && steps.length > 0) {
|
||||
// Find the last step with AI content
|
||||
for (let i = steps.length - 1; i >= 0; i--) {
|
||||
const step = steps[i];
|
||||
// PlannerResponse / assistant content
|
||||
const content = step.content || step.text || step.summary ||
|
||||
step.plannerResponse || step.assistantMessage ||
|
||||
step.response?.content || step.response?.text;
|
||||
if (typeof content === 'string' && content.length > 10) {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern 2: messages array
|
||||
const messages = data.messages || data.chatMessages;
|
||||
if (Array.isArray(messages) && messages.length > 0) {
|
||||
for (let i = messages.length - 1; i >= 0; i--) {
|
||||
const msg = messages[i];
|
||||
if ((msg.role === 'assistant' || msg.type === 'assistant') && msg.content) {
|
||||
return msg.content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pattern 3: nested trajectory object
|
||||
if (data.trajectory) {
|
||||
return extractAIText(data.trajectory);
|
||||
}
|
||||
|
||||
// Pattern 4: single step response
|
||||
if (data.content && typeof data.content === 'string') {
|
||||
return data.content;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ─── Activation ───
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
Reference in New Issue
Block a user