Phase 003: remove the coding-agent and ekko-agent runtimes
Every chat session now runs through the Hermes bridge, so the three chat chains become feature-equivalent instead of three partially-maintained dispatch paths. Keeping the coding-agent and in-house agent runtimes alive meant every bridge change had to be mirrored into two more runtimes that had already drifted. Historical rows are read, not migrated: sessions stored with the retired source normalize to cli on load, workflow graphs naming the retired agents keep loading and run on the bridge, and the visible-source check plus SOURCE_LABELS still recognize the old value so those sessions stay listed. The public surface follows the code: the OpenAPI generator and the MCP chat-run tool schema drop the six retired entry fields so external callers are not told about runtimes that no longer exist. harness-check gains a RETIRED_RUNTIME scan so the residual check is a mechanical gate instead of a manual grep, with the two surviving read-side compat sites recorded with reasons. Refs: docs/phases/phase_003.md
This commit is contained in:
@@ -2,7 +2,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const handleBridgeRunMock = vi.hoisted(() => vi.fn(async () => {}))
|
||||
const resumeBridgeRunMock = vi.hoisted(() => vi.fn(async () => {}))
|
||||
const handleCodingAgentRunMock = vi.hoisted(() => vi.fn(async () => {}))
|
||||
const loadSessionStateFromDbMock = vi.hoisted(() => vi.fn())
|
||||
const ensureReadyMock = vi.hoisted(() => vi.fn())
|
||||
const getRuntimeStateMock = vi.hoisted(() => vi.fn())
|
||||
@@ -24,10 +23,6 @@ vi.mock('../../packages/server/src/services/hermes/run-chat/load-state', () => (
|
||||
resolveRunSource: vi.fn((source?: string) => source || 'cli'),
|
||||
}))
|
||||
|
||||
vi.mock('../../packages/server/src/services/hermes/run-chat/handle-coding-agent-run', () => ({
|
||||
handleCodingAgentRun: handleCodingAgentRunMock,
|
||||
}))
|
||||
|
||||
vi.mock('../../packages/server/src/services/hermes/run-chat/session-command', () => ({
|
||||
handleSessionCommand: vi.fn(),
|
||||
isSessionCommand: vi.fn(() => false),
|
||||
@@ -109,7 +104,6 @@ describe('ensureBridgeReadyForChatRun', () => {
|
||||
bridgeMock.statusIfLoaded.mockReset()
|
||||
handleBridgeRunMock.mockReset()
|
||||
resumeBridgeRunMock.mockReset()
|
||||
handleCodingAgentRunMock.mockReset()
|
||||
loadSessionStateFromDbMock.mockReset()
|
||||
getSessionMock.mockReset()
|
||||
getSessionMock.mockImplementation((sessionId?: string) => sessionId
|
||||
@@ -180,7 +174,6 @@ describe('ChatRunSocket bridge readiness gating', () => {
|
||||
bridgeMock.statusIfLoaded.mockReset()
|
||||
handleBridgeRunMock.mockReset()
|
||||
resumeBridgeRunMock.mockReset()
|
||||
handleCodingAgentRunMock.mockReset()
|
||||
loadSessionStateFromDbMock.mockReset()
|
||||
ensureReadyMock.mockResolvedValue({
|
||||
reachable: true,
|
||||
@@ -263,7 +256,7 @@ describe('ChatRunSocket bridge readiness gating', () => {
|
||||
expect(socket.emit).not.toHaveBeenCalledWith('run.failed', expect.anything())
|
||||
})
|
||||
|
||||
it('routes global coding-agent runs through the coding-agent path while preserving session source', async () => {
|
||||
it('routes runs with an unknown legacy source through the bridge run path', async () => {
|
||||
const { ChatRunSocket } = await import('../../packages/server/src/services/hermes/run-chat')
|
||||
const { handlers, io, socket } = makeServerHarness()
|
||||
const server = new ChatRunSocket(io as any)
|
||||
@@ -272,24 +265,18 @@ describe('ChatRunSocket bridge readiness gating', () => {
|
||||
await handlers.get('run')?.({
|
||||
input: 'hello',
|
||||
session_id: 'session-1',
|
||||
source: 'coding_agent',
|
||||
// Sessions stored before Phase 003 can still carry the retired source.
|
||||
source: 'retired_source',
|
||||
session_source: 'global_agent',
|
||||
coding_agent_id: 'codex',
|
||||
})
|
||||
|
||||
expect(ensureReadyMock).not.toHaveBeenCalled()
|
||||
expect(handleCodingAgentRunMock).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
socket,
|
||||
expect.objectContaining({
|
||||
source: 'coding_agent',
|
||||
session_source: 'global_agent',
|
||||
coding_agent_id: 'codex',
|
||||
}),
|
||||
'default',
|
||||
expect.any(Map),
|
||||
)
|
||||
expect(handleBridgeRunMock).not.toHaveBeenCalled()
|
||||
expect(ensureReadyMock).toHaveBeenCalledTimes(1)
|
||||
expect(handleBridgeRunMock).toHaveBeenCalledTimes(1)
|
||||
expect(handleBridgeRunMock.mock.calls[0][2]).toEqual(expect.objectContaining({
|
||||
source: 'retired_source',
|
||||
session_source: 'global_agent',
|
||||
}))
|
||||
expect(socket.emit).not.toHaveBeenCalledWith('run.failed', expect.anything())
|
||||
})
|
||||
|
||||
it('continues with remaining queued bridge runs when readiness fails before a dequeued run starts', async () => {
|
||||
@@ -496,10 +483,17 @@ describe('ChatRunSocket bridge readiness gating', () => {
|
||||
expect((server as any).bridgeResumePolls.size).toBe(0)
|
||||
})
|
||||
|
||||
it('does not query Hermes bridge status when resuming a coding agent session', async () => {
|
||||
it('reattaches a session stored with a retired source on the bridge', async () => {
|
||||
getSessionMock.mockImplementation((sessionId?: string) => sessionId
|
||||
? { id: sessionId, profile: 'default', source: 'coding_agent', model: 'codex', provider: 'codex' }
|
||||
? { id: sessionId, profile: 'default', source: 'retired_source', model: 'gpt-test', provider: 'openai' }
|
||||
: undefined)
|
||||
bridgeMock.statusIfLoaded.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
exists: true,
|
||||
running: true,
|
||||
current_run_id: 'run-legacy',
|
||||
loaded: true,
|
||||
})
|
||||
loadSessionStateFromDbMock.mockResolvedValueOnce({
|
||||
messages: [],
|
||||
isWorking: false,
|
||||
@@ -507,7 +501,7 @@ describe('ChatRunSocket bridge readiness gating', () => {
|
||||
runId: undefined,
|
||||
activeRunMarker: undefined,
|
||||
profile: 'default',
|
||||
source: 'coding_agent',
|
||||
source: 'retired_source',
|
||||
events: [],
|
||||
queue: [],
|
||||
})
|
||||
@@ -518,15 +512,25 @@ describe('ChatRunSocket bridge readiness gating', () => {
|
||||
;(server as any).onConnection(socket)
|
||||
await handlers.get('resume')?.({ session_id: 'session-1' })
|
||||
|
||||
expect(ensureReadyMock).not.toHaveBeenCalled()
|
||||
expect(bridgeMock.statusIfLoaded).not.toHaveBeenCalled()
|
||||
expect(resumeBridgeRunMock).not.toHaveBeenCalled()
|
||||
expect(bridgeMock.statusIfLoaded).toHaveBeenCalledWith('session-1', 'default', { timeoutMs: 1000 })
|
||||
expect(resumeBridgeRunMock).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
socket,
|
||||
expect.objectContaining({ sessionId: 'session-1', runId: 'run-legacy' }),
|
||||
expect.any(Map),
|
||||
bridgeMock,
|
||||
expect.any(Function),
|
||||
)
|
||||
expect(emitted.some(({ event }) => event === 'run.reattach_failed')).toBe(false)
|
||||
expect(socket.emit).toHaveBeenCalledWith('resumed', expect.objectContaining({
|
||||
session_id: 'session-1',
|
||||
isWorking: false,
|
||||
isWorking: true,
|
||||
events: [],
|
||||
}))
|
||||
expect((server as any).sessionMap.get('session-1')).toEqual(expect.objectContaining({
|
||||
isWorking: true,
|
||||
source: 'cli',
|
||||
}))
|
||||
})
|
||||
|
||||
it('reattaches global-agent bridge runs with the global source preserved', async () => {
|
||||
|
||||
Reference in New Issue
Block a user