Files
openchamber/packages/web/server/lib/hermes-integration/runtime.test.js
T
bot-hermes be159dac19 feat: add Hermes integration panel to Settings → Integrations
Server-side:
- New hermes-integration module (runtime.js + routes.js + tests)
  exposing GET /api/openchamber/hermes/status
- Reports availability of agent-activity, session-steer, and plan-gate
  runtimes plus plan-gate default config
- Wired into feature-routes-runtime.js alongside existing integrations

UI:
- New HermesIntegration.tsx collapsible panel in Integrations page
- Connection status indicator (green/red dot) with 30s polling
- Plan gate default toggle (persists via updateDesktopSettings +
  recordDeferredOpenCodeRestart)
- Read-only status rows for activity stream, steer channel, plan gate
- Server version and uptime display

Settings:
- hermesPlanGateDefault field in settings registry and useUIStore
- Search index entry with keywords: hermes, agent, integration, etc.
- i18n keys for all 12 locales (English text as fallback)

Verification:
- Server tests: 7/7 passed (runtime + routes)
- UI tests: 4/4 passed (HermesIntegration.test.tsx)
- Typecheck: clean (no new errors)
- oxlint: clean on all new/modified files
2026-09-10 11:25:51 +00:00

74 lines
2.5 KiB
JavaScript

import { describe, expect, it, vi } from 'vitest';
import { createHermesIntegrationRuntime } from './runtime.js';
const createRuntime = (overrides = {}) => {
return createHermesIntegrationRuntime({
agentActivityRuntime: { processPayload: vi.fn() },
sessionSteerRuntime: { steer: vi.fn() },
planGateRuntime: { approvePlan: vi.fn() },
readSettingsFromDiskMigrated: vi.fn(async () => ({
hermesPlanGateDefault: false,
})),
startedAt: () => Date.now() - 10_000,
...overrides,
});
};
describe('Hermes integration runtime', () => {
it('reports all three integrations as available', async () => {
const runtime = createRuntime();
const status = await runtime.getStatus();
expect(status.ok).toBe(true);
expect(status.server.version).toBeDefined();
expect(status.integrations.agentActivity.available).toBe(true);
expect(status.integrations.steer.available).toBe(true);
expect(status.integrations.planGate.available).toBe(true);
});
it('reports availability as false when runtimes are null', async () => {
const runtime = createRuntime({
agentActivityRuntime: null,
sessionSteerRuntime: null,
planGateRuntime: null,
});
const status = await runtime.getStatus();
expect(status.integrations.agentActivity.available).toBe(false);
expect(status.integrations.steer.available).toBe(false);
expect(status.integrations.planGate.available).toBe(false);
});
it('includes uptime and config', async () => {
const runtime = createRuntime();
const status = await runtime.getStatus();
expect(typeof status.server.uptimeMs).toBe('number');
expect(status.server.uptimeMs).toBeGreaterThanOrEqual(0);
expect(status.config.planGateDefault).toBe(false);
expect(status.config.activityRateLimitMs).toBe(2000);
});
it('reads planGateDefault from settings', async () => {
const readSettingsFromDiskMigrated = vi.fn(async () => ({
hermesPlanGateDefault: true,
}));
const runtime = createRuntime({ readSettingsFromDiskMigrated });
const status = await runtime.getStatus();
expect(status.config.planGateDefault).toBe(true);
});
it('handles settings read failure gracefully', async () => {
const readSettingsFromDiskMigrated = vi.fn(async () => {
throw new Error('settings read failed');
});
const runtime = createRuntime({ readSettingsFromDiskMigrated });
const status = await runtime.getStatus();
expect(status.ok).toBe(true);
expect(status.config.planGateDefault).toBe(false);
});
});