diff --git a/packages/dashboard-server/src/lib/utils.ts b/packages/dashboard-server/src/lib/utils.ts index ac98554..addb30f 100644 --- a/packages/dashboard-server/src/lib/utils.ts +++ b/packages/dashboard-server/src/lib/utils.ts @@ -217,7 +217,7 @@ export function sendHtmlFileOrFallback( statusIfMissing = 404, ): void { if (fs.existsSync(filePath)) { - res.sendFile(path.resolve(filePath)); + res.type('html').send(fs.readFileSync(path.resolve(filePath), 'utf-8')); return; } diff --git a/packages/dashboard-server/src/mocks/routes.ts b/packages/dashboard-server/src/mocks/routes.ts index 5bc3e3a..a47e834 100644 --- a/packages/dashboard-server/src/mocks/routes.ts +++ b/packages/dashboard-server/src/mocks/routes.ts @@ -83,7 +83,7 @@ export function registerMockRoutes(app: Express, verbose: boolean): void { }); app.post('/api/spawn', (req: Request, res: Response) => { - const { name, cli = 'claude-code', task } = req.body || {}; + const { name, cli = 'claude-code', task, continueFrom } = req.body || {}; log(`POST /api/spawn - ${name}`); if (!name) { @@ -96,6 +96,8 @@ export function registerMockRoutes(app: Express, verbose: boolean): void { name, cli, task, + continueFrom, + resumed: typeof continueFrom === 'string' && continueFrom.length > 0, message: `Agent ${name} spawned successfully (mock)`, }); }); diff --git a/packages/dashboard-server/src/proxy-server.test.ts b/packages/dashboard-server/src/proxy-server.test.ts index d48ef2e..e4dd2c1 100644 --- a/packages/dashboard-server/src/proxy-server.test.ts +++ b/packages/dashboard-server/src/proxy-server.test.ts @@ -234,6 +234,81 @@ describe('Dashboard Server', () => { }); }); + describe('Broker Proxy Mode', () => { + let brokerServer: HttpServer; + let dashboardServer: DashboardServer; + let forwardedSpawnBody: unknown; + + beforeAll(async () => { + brokerServer = createHttpServer((req, res) => { + if (req.method !== 'POST' || req.url !== '/api/spawn') { + res.writeHead(404); + res.end(); + return; + } + + let body = ''; + req.on('data', (chunk) => { + body += chunk; + }); + req.on('end', () => { + forwardedSpawnBody = JSON.parse(body); + res.writeHead(200, { 'content-type': 'application/json' }); + res.end(JSON.stringify({ success: true })); + }); + }); + await new Promise((resolve) => { + brokerServer.listen(0, () => resolve()); + }); + + const brokerAddress = brokerServer.address(); + if (!brokerAddress || typeof brokerAddress === 'string') { + throw new Error('Broker address not available'); + } + + dashboardServer = createServer({ + port: 0, + mock: false, + relayUrl: `http://localhost:${brokerAddress.port}`, + verbose: false, + }); + await new Promise((resolve) => { + dashboardServer.server.listen(0, () => resolve()); + }); + }); + + afterAll(async () => { + await dashboardServer.close(); + await new Promise((resolve, reject) => { + brokerServer.close((err) => (err ? reject(err) : resolve())); + }); + }); + + it('should forward continueFrom when resuming a previous session', async () => { + const address = dashboardServer.server.address(); + if (!address || typeof address === 'string') { + throw new Error('Server address not available'); + } + + const response = await fetch(`http://localhost:${address.port}/api/spawn`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + name: 'worker', + cli: 'claude-code', + task: 'resume context', + continueFrom: 'worker', + }), + }); + + expect(response.ok).toBe(true); + expect(forwardedSpawnBody).toMatchObject({ + name: 'worker', + continueFrom: 'worker', + }); + }); + }); + describe('Proxy Mode (Configuration)', () => { let server: DashboardServer; diff --git a/packages/dashboard-server/src/routes/broker-proxy.ts b/packages/dashboard-server/src/routes/broker-proxy.ts index 788a5ea..ce910bf 100644 --- a/packages/dashboard-server/src/routes/broker-proxy.ts +++ b/packages/dashboard-server/src/routes/broker-proxy.ts @@ -109,11 +109,13 @@ export function registerBrokerProxyRoutes(app: Express, ctx: RouteContext): void typeof rawBody.model === 'string' ? rawBody.model : undefined, ); const model = parsed.model; + const continueFrom = typeof rawBody.continueFrom === 'string' ? rawBody.continueFrom : undefined; return { ...rawBody, cli: parsed.cli, args: parsed.args, model, + continueFrom, includeWorkflowConventions, task: withWorkflowConventions(task, includeWorkflowConventions), }; diff --git a/test/bugs/bug7-resume-session.test.ts b/test/bugs/bug7-resume-session.test.ts index 7f804b5..624a3c4 100644 --- a/test/bugs/bug7-resume-session.test.ts +++ b/test/bugs/bug7-resume-session.test.ts @@ -63,22 +63,22 @@ describe('BUG 7 — Resume session loses context', () => { if (res.status === 200) { const data = await res.json(); - // BUG: The response doesn't indicate whether session was resumed - // because the server never processes continueFrom expect(data.success).toBeDefined(); + expect(data.continueFrom).toBe('test-resume-agent'); + expect(data.resumed).toBe(true); } }); - it('spawn.ts should destructure continueFrom from request body', () => { + it('broker-proxy.ts should explicitly forward continueFrom from request body', () => { // This is a static code analysis test const fs = require('fs'); const path = require('path'); const spawnRouteCode = fs.readFileSync( - path.resolve(__dirname, '../../packages/dashboard-server/src/routes/spawn.ts'), + path.resolve(__dirname, '../../packages/dashboard-server/src/routes/broker-proxy.ts'), 'utf-8' ); - // FIX: continueFrom is now read from req.body and forwarded to spawn + // FIX: continueFrom is now read from req.body and forwarded to the broker spawn endpoint // Verify the fix is in place by checking the source contains continueFrom expect(spawnRouteCode).toContain('continueFrom'); });