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:
2026-03-08 01:13:28 +09:00
parent 4bb72921ae
commit 876143d397
6 changed files with 297 additions and 90 deletions

View File

@@ -87,7 +87,7 @@ function detectProjectName() {
}
// ─── 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)) {
@@ -97,9 +97,21 @@ function ensureBridgeDir() {
}
function writeChatSnapshot(text) {
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) {
console.log(`Gravity Bridge: snapshot write error: ${e.message}`);
@@ -117,31 +129,46 @@ function processCommandFile(filePath) {
try {
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}"`);
if (cmd.action === 'approve' && sdk) {
// 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;
}
// 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) => console.log(`Gravity Bridge: approve error: ${e.message}`));
}
else if (cmd.action === 'reject' && sdk) {
else if (action === 'reject' && sdk) {
sdk.cascade.rejectStep().catch((e) => 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) => 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) => {
// 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) => console.log(`Gravity Bridge: sendPrompt failed: ${e.message}`));
}
// Remove processed command file
try {
@@ -198,42 +225,59 @@ async function initSDK(context) {
return false;
}
}
// Track last seen step per session to avoid re-fetching
const lastSeenStep = new Map();
function setupMonitor() {
if (!sdk) {
return;
}
// Step count changed → fetch conversation content
// Step count changed → fetch new steps via GetCascadeTrajectorySteps
sdk.monitor.onStepCountChanged(async (e) => {
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) => m.role === 'assistant' && m.content);
if (assistantMsgs.length > 0) {
const lastMsg = assistantMsgs[assistantMsgs.length - 1];
const text = `🤖 **${e.title}**\n\n${lastMsg.content}`;
// 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);
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 (${lastMsg.content.length} chars)`);
return;
}
}
// Find PlannerResponse steps
if (conversation && conversation.steps) {
const responses = conversation.steps.filter((s) => 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)`);
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) {
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) {
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})`);
});
// New conversation started
@@ -252,6 +296,50 @@ function setupMonitor() {
sdk.monitor.start(3000, 2000);
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) {
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 ───
async function activate(context) {
console.log('Gravity Bridge: activating...');

File diff suppressed because one or more lines are too long