fix: wire plan-gate activation and add express.json to steer/plan-gate routes

- Wire planGateRuntime.activate() into session creation path when planGate is true
  (Bug 1: sessions map stayed empty, plan/status always returned none)
- Add express.json({ limit: '1mb' }) to session-steer and plan-gate POST routes
  (Bug 2: req.body was undefined, POST with JSON body returned 400)
- Pass planGateRuntime dependency to createOpenChamberSessionService
- Add route-level and integration tests for both fixes
This commit is contained in:
2026-09-07 23:41:36 +00:00
parent 092db7cae4
commit 9a0c67081e
7 changed files with 164 additions and 5 deletions
@@ -6,6 +6,8 @@
// Follows the message-queue precedent: hub subscription, idle detection,
// prompt_async with synthetic parts, waitForPromptLanded verification.
import express from 'express';
const asRecord = (value) => (value && typeof value === 'object' && !Array.isArray(value) ? value : null);
const asNonEmptyString = (value) => (typeof value === 'string' && value.trim() ? value.trim() : '');
const asList = (value) => (Array.isArray(value) ? value : []);
@@ -215,7 +217,7 @@ export function registerSessionSteerRoutes(app, runtime) {
res.status(status).json({ error: error?.message ?? fallback });
};
app.post('/api/openchamber/session/:sessionID/steer', async (req, res) => {
app.post('/api/openchamber/session/:sessionID/steer', express.json({ limit: '1mb' }), async (req, res) => {
try {
const sessionId = asNonEmptyString(req.params?.sessionID);
const directive = asNonEmptyString(req.body?.directive);
@@ -1,5 +1,7 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import { createSessionSteerRuntime } from './runtime.js';
import express from 'express';
import request from 'supertest';
import { createSessionSteerRuntime, registerSessionSteerRoutes } from './runtime.js';
const SESSION = 'ses_steer_test_1';
const DIRECTORY = '/repo';
@@ -151,3 +153,41 @@ describe('session-steer runtime', () => {
expect(openCode.state.sent.length).toBe(0);
});
});
describe('session-steer route middleware', () => {
it('parses JSON body without global middleware on steer route', async () => {
const { state, fetchImpl } = createOpenCode();
state.statuses[SESSION] = { type: 'idle' };
state.tail = [{ info: { role: 'user', id: 'msg_1', time: { created: 100 } } }];
const runtime = createSessionSteerRuntime({
globalEventHub: { subscribeEvent() { return () => {}; } },
buildOpenCodeUrl: (fetchPath) => `http://opencode.test${fetchPath}`,
getOpenCodeAuthHeaders: () => ({}),
broadcastGlobalUiEvent: () => {},
fetchImpl,
});
const app = express();
registerSessionSteerRoutes(app, runtime);
const res = await request(app)
.post(`/api/openchamber/session/${SESSION}/steer`)
.send({ directive: 'Focus on tests', mode: 'interrupt', directory: DIRECTORY })
.expect(202);
expect(res.body.accepted).toBe(true);
expect(state.sent.length).toBe(1);
});
it('returns 400 directive is required when body is missing', async () => {
const runtime = createSessionSteerRuntime({
globalEventHub: { subscribeEvent() { return () => {}; } },
buildOpenCodeUrl: (fetchPath) => `http://opencode.test${fetchPath}`,
getOpenCodeAuthHeaders: () => ({}),
broadcastGlobalUiEvent: () => {},
});
const app = express();
registerSessionSteerRoutes(app, runtime);
await request(app)
.post(`/api/openchamber/session/${SESSION}/steer`)
.send({ mode: 'interrupt', directory: DIRECTORY })
.expect(400, { error: 'directive is required' });
});
});