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 ───
|
||||
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
@@ -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