-
Notifications
You must be signed in to change notification settings - Fork 56
chore: rolling promotion dev -> main #1116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
27f9680
fc907bb
4be5de4
350c2de
a03c844
dba6067
1cc4f45
8c74df5
b5988f7
dc9bceb
fc0885c
15f2eb0
3581d8f
5699871
260dace
72c7dff
42c3062
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| /** | ||
| * Shared mock for `@anthropic-ai/claude-agent-sdk`. | ||
| * | ||
| * Bun's `mock.module()` is process-global and first-registration-wins. When | ||
| * multiple test files register competing mocks for the same module, whichever | ||
| * loads first locks the global cache, and other files' mocks become dead weight. | ||
| * | ||
| * This file provides a SINGLE `queryMock` instance used by both: | ||
| * - `src/__tests__/sdk-integration.test.ts` | ||
| * - `src/services/executors/__tests__/_sdk-mocks.ts` | ||
| * | ||
| * It ONLY mocks `@anthropic-ai/claude-agent-sdk` — no directory, registry, or | ||
| * other module mocks — so it's safe to import from any test file without | ||
| * polluting unrelated modules. | ||
| */ | ||
|
|
||
| import { mock } from 'bun:test'; | ||
|
|
||
| /** Default SDK query implementation — yields one assistant reply + success result with session_id. */ | ||
| const defaultQueryImpl = () => { | ||
| const gen = (async function* () { | ||
| yield { type: 'assistant', message: { content: [{ type: 'text', text: 'reply' }] } }; | ||
| yield { type: 'result', subtype: 'success', session_id: 'sdk-session-aaa' }; | ||
| })(); | ||
| return Object.assign(gen, { | ||
| interrupt: mock(), | ||
| setPermissionMode: mock(), | ||
| setModel: mock(), | ||
| return: mock(async () => ({ value: undefined, done: true })), | ||
| throw: mock(async () => ({ value: undefined, done: true })), | ||
| }); | ||
| }; | ||
|
|
||
| export const queryMock = mock(defaultQueryImpl); | ||
|
|
||
| export function resetQueryMock(): void { | ||
| queryMock.mockReset(); | ||
| queryMock.mockImplementation(defaultQueryImpl); | ||
| } | ||
|
|
||
| // ============================================================================ | ||
| // Register the SDK mock — this is the single source of truth for the process. | ||
| // ============================================================================ | ||
|
|
||
| mock.module('@anthropic-ai/claude-agent-sdk', () => ({ | ||
| query: queryMock, | ||
| createSdkMcpServer: mock((opts: any) => ({ | ||
| type: 'sdk' as const, | ||
| name: opts.name, | ||
| instance: {}, | ||
| })), | ||
| tool: mock((_name: string, _desc: string, _schema: any, handler: any) => ({ | ||
| name: _name, | ||
| description: _desc, | ||
| inputSchema: _schema, | ||
| handler, | ||
| })), | ||
| })); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| -- 035_bridge_sessions.sql — Dedicated bridge session tracking | ||
| -- Persists omni bridge sessions so they survive process restarts. | ||
| -- Complements the executors table with bridge-specific fields. | ||
|
|
||
| CREATE TABLE IF NOT EXISTS genie_bridge_sessions ( | ||
| id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text, | ||
| executor_id TEXT REFERENCES executors(id) ON DELETE SET NULL, | ||
| instance_id TEXT NOT NULL, | ||
| chat_id TEXT NOT NULL, | ||
| agent_name TEXT NOT NULL, | ||
| tmux_pane_id TEXT, | ||
| claude_session_id TEXT, | ||
| status TEXT NOT NULL DEFAULT 'active' | ||
| CHECK (status IN ('active', 'closed', 'orphaned')), | ||
| started_at TIMESTAMPTZ NOT NULL DEFAULT now(), | ||
| last_activity_at TIMESTAMPTZ NOT NULL DEFAULT now(), | ||
| closed_at TIMESTAMPTZ, | ||
| metadata JSONB DEFAULT '{}' | ||
| ); | ||
|
|
||
| CREATE INDEX IF NOT EXISTS idx_bridge_sessions_status | ||
| ON genie_bridge_sessions(status); | ||
| CREATE INDEX IF NOT EXISTS idx_bridge_sessions_instance_chat | ||
| ON genie_bridge_sessions(instance_id, chat_id); | ||
| CREATE INDEX IF NOT EXISTS idx_bridge_sessions_active | ||
| ON genie_bridge_sessions(status, last_activity_at) | ||
| WHERE status = 'active'; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -306,6 +306,57 @@ async function checkWorkerProfiles(): Promise<CheckResult[]> { | |
| return results; | ||
| } | ||
|
|
||
| /** | ||
| * Check Omni bridge health | ||
| */ | ||
| async function checkBridge(): Promise<CheckResult[]> { | ||
| const results: CheckResult[] = []; | ||
|
|
||
| try { | ||
| const { getBridge } = await import('../services/omni-bridge.js'); | ||
| const bridge = getBridge(); | ||
|
|
||
| if (!bridge) { | ||
| results.push({ | ||
| name: 'Bridge running', | ||
| status: 'warn', | ||
| message: 'not running in this process', | ||
| suggestion: 'Bridge starts automatically with: genie serve', | ||
| }); | ||
| return results; | ||
| } | ||
|
Comment on lines
+312
to
+327
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This health check only works inside the bridge-owning process.
🤖 Prompt for AI Agents |
||
|
|
||
| const s = await bridge.status(); | ||
|
|
||
| results.push({ | ||
| name: 'NATS connection', | ||
| status: s.connected ? 'pass' : 'fail', | ||
| message: s.connected ? `connected (${s.natsUrl})` : `disconnected (${s.natsUrl})`, | ||
| suggestion: s.connected ? undefined : 'Check NATS server: nats-server or omni start', | ||
| }); | ||
|
|
||
| results.push({ | ||
| name: 'Active sessions', | ||
| status: 'pass', | ||
| message: `${s.activeSessions} / ${s.maxConcurrent} (queue: ${s.queueDepth})`, | ||
| }); | ||
|
|
||
| results.push({ | ||
| name: 'PG backing', | ||
| status: s.pgAvailable ? 'pass' : 'warn', | ||
| message: s.pgAvailable ? 'connected' : 'degraded (in-memory)', | ||
| }); | ||
| } catch { | ||
| results.push({ | ||
| name: 'Bridge module', | ||
| status: 'warn', | ||
| message: 'could not load omni-bridge', | ||
| }); | ||
| } | ||
|
|
||
| return results; | ||
| } | ||
|
|
||
| /** | ||
| * Main doctor command | ||
| */ | ||
|
|
@@ -334,6 +385,7 @@ export async function doctorCommand(options?: { fix?: boolean }): Promise<void> | |
| runCheckSection('Configuration', await checkConfiguration(), counts); | ||
| runCheckSection('Tmux', await checkTmux(), counts); | ||
| runCheckSection('Worker Profiles', await checkWorkerProfiles(), counts); | ||
| runCheckSection('Omni Bridge', await checkBridge(), counts); | ||
|
|
||
| // Summary | ||
| console.log(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enforce one active row per bridge session key.
The new indexes speed lookups, but nothing here prevents two
status='active'rows for the same logical session. The runtime already assumes singular active rows during recovery, so duplicate inserts will make restart recovery nondeterministic and keep stale sessions alive in PG.Suggested migration
📝 Committable suggestion
🤖 Prompt for AI Agents