nicholai acd04dd6ac 🎉 BREAKTHROUGH: WebSocket working! Real-time streaming functional
 What's Working:
- WebSocket connections established (patched worker to intercept upgrades)
- Real-time event streaming: Agent → DO → Browser
- Terminal panel showing live command execution
- Agent chat panel showing LLM thoughts
- Full infrastructure: UI → API → DO → SSH Proxy → LangGraph Agent

🔧 Key Changes:
- Created standalone DO worker at workers/bandit-agent-do/
- Deployed DO as separate Worker (bandit-agent-do)
- Updated wrangler.jsonc to reference external DO via script_name
- Modified patch-worker.js to intercept WS upgrades before Next.js
- Added __name polyfill to fix esbuild helper
- Created pnpm workspace config for monorepo

📝 Architecture:
- Frontend (Next.js) → Cloudflare Worker
- Worker intercepts /api/agent/*/ws → forwards to DO
- DO (bandit-agent-do) → manages WebSocket connections
- DO → calls SSH Proxy API
- SSH Proxy → runs LangGraph agent → executes SSH commands
- Events stream back: SSH Proxy → DO → WebSocket → UI

🐛 Known Issue:
- Agent logic needs refinement (not parsing SSH output correctly)
- But core infrastructure is 100% functional!

This resolves all WebSocket and real-time streaming issues.
2025-10-09 15:10:16 -06:00

93 lines
2.9 KiB
JavaScript

#!/usr/bin/env node
/**
* Patch the OpenNext worker to add WebSocket handling
* Intercepts WebSocket requests before they reach Next.js
*/
const fs = require('fs')
const path = require('path')
console.log('🔨 Patching worker to add WebSocket handler...')
const workerPath = path.join(__dirname, '../.open-next/worker.js')
if (!fs.existsSync(workerPath)) {
console.error('❌ Worker file not found at:', workerPath)
process.exit(1)
}
// Read worker file
let workerContent = fs.readFileSync(workerPath, 'utf-8')
// Check if already patched
if (workerContent.includes('// WebSocket Intercept Handler')) {
console.log('✅ Worker already patched, skipping')
process.exit(0)
}
// Create WebSocket intercept handler
const wsInterceptCode = `
// WebSocket Intercept Handler
function handleWebSocketUpgrade(request, env) {
const url = new URL(request.url);
const upgradeHeader = request.headers.get('Upgrade');
// Check if this is a WebSocket upgrade for agent endpoints
if (upgradeHeader === 'websocket' && url.pathname.includes('/api/agent/') && url.pathname.endsWith('/ws')) {
// Extract runId from path: /api/agent/{runId}/ws
const pathParts = url.pathname.split('/');
const runIdIndex = pathParts.indexOf('agent') + 1;
const runId = pathParts[runIdIndex];
if (runId && env.BANDIT_AGENT) {
// Forward directly to Durable Object
const id = env.BANDIT_AGENT.idFromName(runId);
const stub = env.BANDIT_AGENT.get(id);
return stub.fetch(request);
}
}
return null; // Not a WebSocket request, continue normal handling
}
`;
// Find where to inject the WebSocket intercept
const fetchFunctionStart = workerContent.indexOf('export default {');
if (fetchFunctionStart === -1) {
console.error('❌ Could not find export default in worker.js');
process.exit(1);
}
// Find the async fetch function
const asyncFetchStart = workerContent.indexOf('async fetch(request, env, ctx) {', fetchFunctionStart);
if (asyncFetchStart === -1) {
console.error('❌ Could not find async fetch function in worker.js');
process.exit(1);
}
// Find the opening brace of the fetch function
const fetchBodyStart = workerContent.indexOf('{', asyncFetchStart) + 1;
// Find the first return statement in the fetch body
const returnStatement = workerContent.indexOf('return', fetchBodyStart);
// Insert WebSocket intercept at the beginning of fetch, before the return
const patchedContent =
workerContent.slice(0, fetchBodyStart) +
wsInterceptCode +
`
// Check for WebSocket upgrades first (before Next.js)
const wsResponse = handleWebSocketUpgrade(request, env);
if (wsResponse) {
return wsResponse;
}
` +
workerContent.slice(fetchBodyStart);
// Write back
fs.writeFileSync(workerPath, patchedContent, 'utf-8');
console.log('✅ Worker patched successfully - WebSocket handler added');
console.log('📝 Note: WebSocket requests now bypass Next.js and go directly to DO');