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:
5
docs/devlog/2026-03-08.md
Normal file
5
docs/devlog/2026-03-08.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# 2026-03-08 Devlog — Bridge 프로토콜 수정
|
||||||
|
|
||||||
|
| # | 시간 | 작업 | 커밋 | 상태 |
|
||||||
|
|---|------|------|------|------|
|
||||||
|
| 1 | 01:00 | Extension↔Bot 프로토콜 불일치 3건 수정 + sql-wasm 번들링 | 커밋예정 | 🔧 |
|
||||||
26
docs/devlog/entries/20260308-001.md
Normal file
26
docs/devlog/entries/20260308-001.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Extension↔Bot 프로토콜 불일치 수정 + sql-wasm 번들링
|
||||||
|
|
||||||
|
- **시간**: 2026-03-08 00:50~01:10
|
||||||
|
- **Commit**: 커밋예정
|
||||||
|
- **Vikunja**: #240 → 진행중
|
||||||
|
|
||||||
|
## 결정 사항
|
||||||
|
|
||||||
|
### GetConversation → GetCascadeTrajectorySteps
|
||||||
|
- SDK의 `getConversation()` 메서드가 `GetConversation` RPC를 호출하지만, LS에 해당 메서드 없음 (404)
|
||||||
|
- `ls-rpc-reference.md`에서 확인: 실제 메서드는 `GetCascadeTrajectorySteps`
|
||||||
|
- Fallback 체인: Steps → GetCascadeTrajectory → title-only
|
||||||
|
|
||||||
|
### Bot↔Extension 프로토콜 불일치 3건
|
||||||
|
1. **Snapshot**: Bot은 `chat_snapshots/*.json` (JSON), Extension은 `response/chat_snapshot.txt` (텍스트)
|
||||||
|
2. **Command**: Bot은 `text` 필드, Extension은 `message` 필드 읽음
|
||||||
|
3. **Consumed**: Bot이 `consumed: true` 설정하지만 Extension이 삭제하지 않아 38개 누적
|
||||||
|
|
||||||
|
### sql-wasm.wasm VSIX 누락
|
||||||
|
- `.vscodeignore`가 `node_modules/**` 제외 → sql.js wasm 파일 미포함
|
||||||
|
- 해결: 빌드 시 `out/sdk/`에 수동 복사
|
||||||
|
|
||||||
|
## 미완료
|
||||||
|
- [ ] 윈도우 리로드 후 E2E 테스트 (Discord→Antigravity, Antigravity→Discord)
|
||||||
|
- [ ] `GetCascadeTrajectorySteps` 응답 구조 파악 → extractAIText 정확도 검증
|
||||||
|
- [ ] VSIX 빌드 스크립트에 sql-wasm 복사 자동화
|
||||||
Binary file not shown.
@@ -87,7 +87,7 @@ function detectProjectName() {
|
|||||||
}
|
}
|
||||||
// ─── Bridge File I/O ───
|
// ─── Bridge File I/O ───
|
||||||
function ensureBridgeDir() {
|
function ensureBridgeDir() {
|
||||||
const dirs = ['', 'response', 'commands'];
|
const dirs = ['', 'response', 'commands', 'chat_snapshots'];
|
||||||
for (const d of dirs) {
|
for (const d of dirs) {
|
||||||
const p = path.join(bridgePath, d);
|
const p = path.join(bridgePath, d);
|
||||||
if (!fs.existsSync(p)) {
|
if (!fs.existsSync(p)) {
|
||||||
@@ -97,9 +97,21 @@ function ensureBridgeDir() {
|
|||||||
}
|
}
|
||||||
function writeChatSnapshot(text) {
|
function writeChatSnapshot(text) {
|
||||||
try {
|
try {
|
||||||
const filePath = path.join(bridgePath, 'response', 'chat_snapshot.txt');
|
// Write to chat_snapshots/*.json for Bot's chat_snapshot_scanner to pick up
|
||||||
fs.writeFileSync(filePath, text, 'utf-8');
|
const snapshotDir = path.join(bridgePath, 'chat_snapshots');
|
||||||
console.log(`Gravity Bridge: chat snapshot written (${text.length} chars)`);
|
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) {
|
catch (e) {
|
||||||
console.log(`Gravity Bridge: snapshot write error: ${e.message}`);
|
console.log(`Gravity Bridge: snapshot write error: ${e.message}`);
|
||||||
@@ -117,31 +129,46 @@ function processCommandFile(filePath) {
|
|||||||
try {
|
try {
|
||||||
const content = fs.readFileSync(filePath, 'utf-8');
|
const content = fs.readFileSync(filePath, 'utf-8');
|
||||||
const cmd = JSON.parse(content);
|
const cmd = JSON.parse(content);
|
||||||
// Ignore commands for other projects
|
// Skip already consumed commands
|
||||||
if (cmd.project_name && cmd.project_name !== projectName) {
|
if (cmd.consumed) {
|
||||||
console.log(`Gravity Bridge: skipping command for "${cmd.project_name}"`);
|
try {
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log(`Gravity Bridge: command — "${cmd.message || cmd.action}"`);
|
// Ignore commands for other projects
|
||||||
if (cmd.action === 'approve' && sdk) {
|
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}`));
|
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}`));
|
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}`));
|
sdk.cascade.acceptTerminalCommand().catch((e) => console.log(`Gravity Bridge: approve_terminal error: ${e.message}`));
|
||||||
}
|
}
|
||||||
else if (cmd.message && sdk) {
|
else if (text === '!stop') {
|
||||||
// Send message to Antigravity
|
// Cancel current operation
|
||||||
sdk.cascade.sendPrompt(cmd.message).then(() => {
|
vscode.commands.executeCommand('antigravity.agent.rejectAgentStep')
|
||||||
console.log(`Gravity Bridge: ✅ sent via SDK sendPrompt`);
|
.then(() => console.log('Gravity Bridge: ✅ stop sent'), () => { });
|
||||||
}).catch((e) => {
|
}
|
||||||
// Fallback to VS Code command
|
else if (text.startsWith('!auto ')) {
|
||||||
console.log(`Gravity Bridge: SDK sendPrompt failed: ${e.message}, trying command...`);
|
// Auto-approve mode toggle
|
||||||
vscode.commands.executeCommand('antigravity.sendPromptToAgentPanel', cmd.message)
|
const mode = text.includes('on') ? 'true' : 'false';
|
||||||
.then(() => console.log('Gravity Bridge: ✅ sent via sendPromptToAgentPanel'));
|
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
|
// Remove processed command file
|
||||||
try {
|
try {
|
||||||
@@ -198,42 +225,59 @@ async function initSDK(context) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Track last seen step per session to avoid re-fetching
|
||||||
|
const lastSeenStep = new Map();
|
||||||
function setupMonitor() {
|
function setupMonitor() {
|
||||||
if (!sdk) {
|
if (!sdk) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Step count changed → fetch conversation content
|
// Step count changed → fetch new steps via GetCascadeTrajectorySteps
|
||||||
sdk.monitor.onStepCountChanged(async (e) => {
|
sdk.monitor.onStepCountChanged(async (e) => {
|
||||||
console.log(`Gravity Bridge: [SDK] step changed: "${e.title}" step ${e.newCount} (+${e.delta})`);
|
console.log(`Gravity Bridge: [SDK] step changed: "${e.title}" step ${e.newCount} (+${e.delta})`);
|
||||||
// Get fresh session to have the ID
|
|
||||||
try {
|
try {
|
||||||
const conversation = await sdk.ls.getConversation(e.sessionId);
|
// Use the correct LS RPC: GetCascadeTrajectorySteps (not GetConversation which doesn't exist)
|
||||||
if (conversation && conversation.messages) {
|
const fromStep = lastSeenStep.get(e.sessionId) ?? Math.max(0, e.newCount - e.delta);
|
||||||
// Find the last assistant message
|
const stepsData = await sdk.ls.rawRPC('GetCascadeTrajectorySteps', {
|
||||||
const assistantMsgs = conversation.messages.filter((m) => m.role === 'assistant' && m.content);
|
cascadeId: e.sessionId,
|
||||||
if (assistantMsgs.length > 0) {
|
startStepIndex: fromStep
|
||||||
const lastMsg = assistantMsgs[assistantMsgs.length - 1];
|
});
|
||||||
const text = `🤖 **${e.title}**\n\n${lastMsg.content}`;
|
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);
|
writeChatSnapshot(text);
|
||||||
console.log(`Gravity Bridge: [SDK] relayed AI response (${lastMsg.content.length} chars)`);
|
console.log(`Gravity Bridge: [SDK] relayed AI response (${aiText.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)`);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Log the raw structure for debugging
|
||||||
|
console.log(`Gravity Bridge: [SDK] steps data keys: ${JSON.stringify(Object.keys(stepsData))}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (err) {
|
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
|
// Fallback: just send the title + step info
|
||||||
|
lastSeenStep.set(e.sessionId, e.newCount);
|
||||||
writeChatSnapshot(`🤖 **${e.title}**\n\n(step ${e.newCount}, +${e.delta})`);
|
writeChatSnapshot(`🤖 **${e.title}**\n\n(step ${e.newCount}, +${e.delta})`);
|
||||||
});
|
});
|
||||||
// New conversation started
|
// New conversation started
|
||||||
@@ -252,6 +296,50 @@ function setupMonitor() {
|
|||||||
sdk.monitor.start(3000, 2000);
|
sdk.monitor.start(3000, 2000);
|
||||||
console.log('Gravity Bridge: [SDK] monitor started (USS 3s, trajectory 2s)');
|
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 ───
|
// ─── Activation ───
|
||||||
async function activate(context) {
|
async function activate(context) {
|
||||||
console.log('Gravity Bridge: activating...');
|
console.log('Gravity Bridge: activating...');
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -56,7 +56,7 @@ function detectProjectName(): string {
|
|||||||
// ─── Bridge File I/O ───
|
// ─── Bridge File I/O ───
|
||||||
|
|
||||||
function ensureBridgeDir() {
|
function ensureBridgeDir() {
|
||||||
const dirs = ['', 'response', 'commands'];
|
const dirs = ['', 'response', 'commands', 'chat_snapshots'];
|
||||||
for (const d of dirs) {
|
for (const d of dirs) {
|
||||||
const p = path.join(bridgePath, d);
|
const p = path.join(bridgePath, d);
|
||||||
if (!fs.existsSync(p)) { fs.mkdirSync(p, { recursive: true }); }
|
if (!fs.existsSync(p)) { fs.mkdirSync(p, { recursive: true }); }
|
||||||
@@ -65,9 +65,19 @@ function ensureBridgeDir() {
|
|||||||
|
|
||||||
function writeChatSnapshot(text: string) {
|
function writeChatSnapshot(text: string) {
|
||||||
try {
|
try {
|
||||||
const filePath = path.join(bridgePath, 'response', 'chat_snapshot.txt');
|
// Write to chat_snapshots/*.json for Bot's chat_snapshot_scanner to pick up
|
||||||
fs.writeFileSync(filePath, text, 'utf-8');
|
const snapshotDir = path.join(bridgePath, 'chat_snapshots');
|
||||||
console.log(`Gravity Bridge: chat snapshot written (${text.length} chars)`);
|
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) {
|
} catch (e: any) {
|
||||||
console.log(`Gravity Bridge: snapshot write error: ${e.message}`);
|
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 content = fs.readFileSync(filePath, 'utf-8');
|
||||||
const cmd = JSON.parse(content);
|
const cmd = JSON.parse(content);
|
||||||
|
|
||||||
// Ignore commands for other projects
|
// Skip already consumed commands
|
||||||
if (cmd.project_name && cmd.project_name !== projectName) {
|
if (cmd.consumed) {
|
||||||
console.log(`Gravity Bridge: skipping command for "${cmd.project_name}"`);
|
try { fs.unlinkSync(filePath); } catch { }
|
||||||
return;
|
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) =>
|
sdk.cascade.acceptStep().catch((e: any) =>
|
||||||
console.log(`Gravity Bridge: approve error: ${e.message}`)
|
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) =>
|
sdk.cascade.rejectStep().catch((e: any) =>
|
||||||
console.log(`Gravity Bridge: reject error: ${e.message}`)
|
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) =>
|
sdk.cascade.acceptTerminalCommand().catch((e: any) =>
|
||||||
console.log(`Gravity Bridge: approve_terminal error: ${e.message}`)
|
console.log(`Gravity Bridge: approve_terminal error: ${e.message}`)
|
||||||
);
|
);
|
||||||
} else if (cmd.message && sdk) {
|
} else if (text === '!stop') {
|
||||||
// Send message to Antigravity
|
// Cancel current operation
|
||||||
sdk.cascade.sendPrompt(cmd.message).then(() => {
|
vscode.commands.executeCommand('antigravity.agent.rejectAgentStep')
|
||||||
console.log(`Gravity Bridge: ✅ sent via SDK sendPrompt`);
|
.then(() => console.log('Gravity Bridge: ✅ stop sent'),
|
||||||
}).catch((e: any) => {
|
() => { });
|
||||||
// Fallback to VS Code command
|
} else if (text.startsWith('!auto ')) {
|
||||||
console.log(`Gravity Bridge: SDK sendPrompt failed: ${e.message}, trying command...`);
|
// Auto-approve mode toggle
|
||||||
vscode.commands.executeCommand('antigravity.sendPromptToAgentPanel', cmd.message)
|
const mode = text.includes('on') ? 'true' : 'false';
|
||||||
.then(() => console.log('Gravity Bridge: ✅ sent via sendPromptToAgentPanel'));
|
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
|
// 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() {
|
function setupMonitor() {
|
||||||
if (!sdk) { return; }
|
if (!sdk) { return; }
|
||||||
|
|
||||||
// Step count changed → fetch conversation content
|
// Step count changed → fetch new steps via GetCascadeTrajectorySteps
|
||||||
sdk.monitor.onStepCountChanged(async (e: any) => {
|
sdk.monitor.onStepCountChanged(async (e: any) => {
|
||||||
console.log(`Gravity Bridge: [SDK] step changed: "${e.title}" step ${e.newCount} (+${e.delta})`);
|
console.log(`Gravity Bridge: [SDK] step changed: "${e.title}" step ${e.newCount} (+${e.delta})`);
|
||||||
|
|
||||||
// Get fresh session to have the ID
|
|
||||||
try {
|
try {
|
||||||
const conversation = await sdk.ls.getConversation(e.sessionId);
|
// Use the correct LS RPC: GetCascadeTrajectorySteps (not GetConversation which doesn't exist)
|
||||||
if (conversation && conversation.messages) {
|
const fromStep = lastSeenStep.get(e.sessionId) ?? Math.max(0, e.newCount - e.delta);
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find PlannerResponse steps
|
const stepsData = await sdk.ls.rawRPC('GetCascadeTrajectorySteps', {
|
||||||
if (conversation && conversation.steps) {
|
cascadeId: e.sessionId,
|
||||||
const responses = conversation.steps.filter(
|
startStepIndex: fromStep
|
||||||
(s: any) => s.type === 'PlannerResponse' && s.summary
|
});
|
||||||
);
|
|
||||||
if (responses.length > 0) {
|
lastSeenStep.set(e.sessionId, e.newCount);
|
||||||
const last = responses[responses.length - 1];
|
|
||||||
writeChatSnapshot(`🤖 **${e.title}**\n\n${last.summary}`);
|
if (stepsData) {
|
||||||
console.log(`Gravity Bridge: [SDK] relayed PlannerResponse (${last.summary.length} chars)`);
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
// Log the raw structure for debugging
|
||||||
|
console.log(`Gravity Bridge: [SDK] steps data keys: ${JSON.stringify(Object.keys(stepsData))}`);
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} 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
|
// Fallback: just send the title + step info
|
||||||
|
lastSeenStep.set(e.sessionId, e.newCount);
|
||||||
writeChatSnapshot(`🤖 **${e.title}**\n\n(step ${e.newCount}, +${e.delta})`);
|
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)');
|
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 ───
|
// ─── Activation ───
|
||||||
|
|
||||||
export async function activate(context: vscode.ExtensionContext) {
|
export async function activate(context: vscode.ExtensionContext) {
|
||||||
|
|||||||
Reference in New Issue
Block a user