initialize Electron desktop app and remove unused files
This commit is contained in:
@@ -0,0 +1,625 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const handleBridgeRunMock = vi.hoisted(() => vi.fn(async () => {}))
|
||||
const resumeBridgeRunMock = vi.hoisted(() => vi.fn(async () => {}))
|
||||
const loadSessionStateFromDbMock = vi.hoisted(() => vi.fn())
|
||||
const ensureReadyMock = vi.hoisted(() => vi.fn())
|
||||
const getRuntimeStateMock = vi.hoisted(() => vi.fn())
|
||||
const getSessionMock = vi.hoisted(() => vi.fn((sessionId?: string) => sessionId
|
||||
? { id: sessionId, profile: 'default', source: 'cli', model: 'gpt-test', provider: 'openai' }
|
||||
: undefined))
|
||||
const bridgeMock = vi.hoisted(() => ({
|
||||
status: vi.fn(),
|
||||
statusIfLoaded: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('../../packages/server/src/services/hermes/run-chat/handle-bridge-run', () => ({
|
||||
handleBridgeRun: handleBridgeRunMock,
|
||||
resumeBridgeRun: resumeBridgeRunMock,
|
||||
}))
|
||||
|
||||
vi.mock('../../packages/server/src/services/hermes/run-chat/load-state', () => ({
|
||||
loadSessionStateFromDb: loadSessionStateFromDbMock,
|
||||
resolveRunSource: vi.fn((source?: string) => source || 'cli'),
|
||||
}))
|
||||
|
||||
vi.mock('../../packages/server/src/services/hermes/run-chat/session-command', () => ({
|
||||
handleSessionCommand: vi.fn(),
|
||||
isSessionCommand: vi.fn(() => false),
|
||||
parseSessionCommand: vi.fn(() => null),
|
||||
}))
|
||||
|
||||
vi.mock('../../packages/server/src/services/hermes/agent-bridge', () => ({
|
||||
AgentBridgeClient: vi.fn(() => bridgeMock),
|
||||
}))
|
||||
|
||||
vi.mock('../../packages/server/src/services/hermes/agent-bridge/manager', () => ({
|
||||
getAgentBridgeManager: vi.fn(() => ({
|
||||
ensureReady: ensureReadyMock,
|
||||
getRuntimeState: getRuntimeStateMock,
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('../../packages/server/src/services/logger', () => ({
|
||||
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
|
||||
}))
|
||||
|
||||
vi.mock('../../packages/server/src/lib/llm-prompt', () => ({
|
||||
getSystemPrompt: vi.fn(() => 'system prompt'),
|
||||
}))
|
||||
|
||||
vi.mock('../../packages/server/src/db/hermes/session-store', () => ({
|
||||
getSession: getSessionMock,
|
||||
getSessionDetail: vi.fn(() => null),
|
||||
}))
|
||||
|
||||
vi.mock('../../packages/server/src/services/hermes/hermes-profile', () => ({
|
||||
getActiveProfileName: vi.fn(() => 'default'),
|
||||
getProfileDir: vi.fn(() => '/tmp/hermes-default'),
|
||||
listProfileNamesFromDisk: vi.fn(() => ['default']),
|
||||
}))
|
||||
|
||||
vi.mock('../../packages/server/src/middleware/user-auth', () => ({
|
||||
authenticateUserToken: vi.fn(),
|
||||
isAuthEnabled: vi.fn(async () => false),
|
||||
}))
|
||||
|
||||
vi.mock('../../packages/server/src/db/hermes/users-store', () => ({
|
||||
userCanAccessProfile: vi.fn(() => true),
|
||||
}))
|
||||
|
||||
function makeServerHarness() {
|
||||
const handlers = new Map<string, Function>()
|
||||
const emitted: Array<{ room: string; event: string; payload: any }> = []
|
||||
const namespace = {
|
||||
adapter: { rooms: new Map() },
|
||||
to: vi.fn((room: string) => ({
|
||||
emit: vi.fn((event: string, payload: any) => emitted.push({ room, event, payload })),
|
||||
})),
|
||||
use: vi.fn(),
|
||||
on: vi.fn(),
|
||||
}
|
||||
const io = { of: vi.fn(() => namespace) }
|
||||
const socket = {
|
||||
id: 'socket-1',
|
||||
connected: true,
|
||||
handshake: { auth: {}, query: { profile: 'default' } },
|
||||
data: {},
|
||||
emit: vi.fn(),
|
||||
join: vi.fn(),
|
||||
to: vi.fn(() => ({ emit: vi.fn() })),
|
||||
on: vi.fn((event: string, handler: Function) => {
|
||||
handlers.set(event, handler)
|
||||
}),
|
||||
}
|
||||
return { emitted, handlers, io, namespace, socket }
|
||||
}
|
||||
|
||||
describe('ensureBridgeReadyForChatRun', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
ensureReadyMock.mockReset()
|
||||
getRuntimeStateMock.mockReset()
|
||||
bridgeMock.status.mockReset()
|
||||
bridgeMock.statusIfLoaded.mockReset()
|
||||
handleBridgeRunMock.mockReset()
|
||||
resumeBridgeRunMock.mockReset()
|
||||
loadSessionStateFromDbMock.mockReset()
|
||||
getSessionMock.mockReset()
|
||||
getSessionMock.mockImplementation((sessionId?: string) => sessionId
|
||||
? { id: sessionId, profile: 'default', source: 'cli', model: 'gpt-test', provider: 'openai' }
|
||||
: undefined)
|
||||
ensureReadyMock.mockResolvedValue({
|
||||
reachable: true,
|
||||
status: 'ready',
|
||||
endpoint: 'ipc:///tmp/hermes-agent-bridge.sock',
|
||||
})
|
||||
getRuntimeStateMock.mockReturnValue({ endpoint: 'ipc:///tmp/hermes-agent-bridge.sock' })
|
||||
})
|
||||
|
||||
it('allows reachable bridge readiness', async () => {
|
||||
const { ensureBridgeReadyForChatRun } = await import('../../packages/server/src/services/hermes/run-chat')
|
||||
|
||||
await expect(ensureBridgeReadyForChatRun()).resolves.toEqual({ ok: true })
|
||||
expect(ensureReadyMock).toHaveBeenCalledWith({ timeoutMs: 1000, connectRetryMs: 0, recover: false })
|
||||
})
|
||||
|
||||
it('returns a visible error when the bridge is unreachable', async () => {
|
||||
ensureReadyMock.mockResolvedValueOnce({
|
||||
reachable: false,
|
||||
status: 'unreachable',
|
||||
endpoint: 'ipc:///tmp/hermes-agent-bridge.sock',
|
||||
error: 'connect ECONNREFUSED ipc:///tmp/hermes-agent-bridge.sock',
|
||||
})
|
||||
const { ensureBridgeReadyForChatRun } = await import('../../packages/server/src/services/hermes/run-chat')
|
||||
|
||||
await expect(ensureBridgeReadyForChatRun()).resolves.toEqual({
|
||||
ok: false,
|
||||
error: 'connect ECONNREFUSED configured endpoint',
|
||||
})
|
||||
})
|
||||
|
||||
it('redacts configured tcp host:port when the bridge is unreachable', async () => {
|
||||
ensureReadyMock.mockResolvedValueOnce({
|
||||
reachable: false,
|
||||
status: 'unreachable',
|
||||
endpoint: 'tcp://example.internal:43123',
|
||||
error: 'connect ECONNREFUSED example.internal:43123',
|
||||
})
|
||||
const { ensureBridgeReadyForChatRun } = await import('../../packages/server/src/services/hermes/run-chat')
|
||||
|
||||
await expect(ensureBridgeReadyForChatRun()).resolves.toEqual({
|
||||
ok: false,
|
||||
error: 'connect ECONNREFUSED configured endpoint',
|
||||
})
|
||||
})
|
||||
|
||||
it('handles thrown ensureReady failures', async () => {
|
||||
ensureReadyMock.mockRejectedValueOnce(new Error('bridge startup timed out'))
|
||||
const { ensureBridgeReadyForChatRun } = await import('../../packages/server/src/services/hermes/run-chat')
|
||||
|
||||
await expect(ensureBridgeReadyForChatRun()).resolves.toEqual({
|
||||
ok: false,
|
||||
error: 'bridge startup timed out',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('ChatRunSocket bridge readiness gating', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
ensureReadyMock.mockReset()
|
||||
getRuntimeStateMock.mockReset()
|
||||
bridgeMock.status.mockReset()
|
||||
bridgeMock.statusIfLoaded.mockReset()
|
||||
handleBridgeRunMock.mockReset()
|
||||
resumeBridgeRunMock.mockReset()
|
||||
loadSessionStateFromDbMock.mockReset()
|
||||
ensureReadyMock.mockResolvedValue({
|
||||
reachable: true,
|
||||
status: 'ready',
|
||||
endpoint: 'ipc:///tmp/hermes-agent-bridge.sock',
|
||||
})
|
||||
getRuntimeStateMock.mockReturnValue({ endpoint: 'ipc:///tmp/hermes-agent-bridge.sock' })
|
||||
bridgeMock.statusIfLoaded.mockResolvedValue({ ok: true, exists: false, running: false, loaded: false })
|
||||
loadSessionStateFromDbMock.mockResolvedValue({
|
||||
messages: [],
|
||||
isWorking: false,
|
||||
isAborting: false,
|
||||
events: [],
|
||||
queue: [],
|
||||
})
|
||||
})
|
||||
|
||||
it('emits run.failed before starting a cli run when the bridge is unreachable', async () => {
|
||||
ensureReadyMock.mockResolvedValueOnce({
|
||||
reachable: false,
|
||||
status: 'unreachable',
|
||||
endpoint: 'ipc:///tmp/hermes-agent-bridge.sock',
|
||||
error: 'bridge offline',
|
||||
})
|
||||
const { ChatRunSocket } = await import('../../packages/server/src/services/hermes/run-chat')
|
||||
const { handlers, io, socket } = makeServerHarness()
|
||||
const server = new ChatRunSocket(io as any)
|
||||
|
||||
;(server as any).onConnection(socket)
|
||||
await handlers.get('run')?.({ input: 'hello', session_id: 'session-1', source: 'cli' })
|
||||
|
||||
expect(handleBridgeRunMock).not.toHaveBeenCalled()
|
||||
expect(socket.emit).toHaveBeenCalledWith('run.failed', {
|
||||
event: 'run.failed',
|
||||
session_id: 'session-1',
|
||||
error: 'Agent Bridge is not reachable: bridge offline',
|
||||
})
|
||||
expect((server as any).sessionMap.get('session-1')).toEqual(expect.objectContaining({
|
||||
isWorking: false,
|
||||
profile: undefined,
|
||||
}))
|
||||
})
|
||||
|
||||
it('routes legacy api_server runs through the bridge 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)
|
||||
|
||||
;(server as any).onConnection(socket)
|
||||
await handlers.get('run')?.({ input: 'hello', session_id: 'session-1', source: 'api_server' })
|
||||
|
||||
expect(ensureReadyMock).toHaveBeenCalledTimes(1)
|
||||
expect(handleBridgeRunMock).toHaveBeenCalledTimes(1)
|
||||
expect(handleBridgeRunMock.mock.calls[0][2]).toEqual(expect.objectContaining({
|
||||
input: 'hello',
|
||||
source: 'api_server',
|
||||
}))
|
||||
expect(socket.emit).not.toHaveBeenCalledWith('run.failed', expect.anything())
|
||||
})
|
||||
|
||||
it('routes global-agent Hermes runs through the bridge run path while preserving session source', async () => {
|
||||
const { ChatRunSocket } = await import('../../packages/server/src/services/hermes/run-chat')
|
||||
const { handlers, io, socket } = makeServerHarness()
|
||||
const server = new ChatRunSocket(io as any)
|
||||
|
||||
;(server as any).onConnection(socket)
|
||||
await handlers.get('run')?.({
|
||||
input: 'hello',
|
||||
session_id: 'session-1',
|
||||
source: 'cli',
|
||||
session_source: 'global_agent',
|
||||
})
|
||||
|
||||
expect(ensureReadyMock).toHaveBeenCalledTimes(1)
|
||||
expect(handleBridgeRunMock).toHaveBeenCalledTimes(1)
|
||||
expect(handleBridgeRunMock.mock.calls[0][2]).toEqual(expect.objectContaining({
|
||||
source: 'cli',
|
||||
session_source: 'global_agent',
|
||||
}))
|
||||
expect(socket.emit).not.toHaveBeenCalledWith('run.failed', expect.anything())
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
;(server as any).onConnection(socket)
|
||||
await handlers.get('run')?.({
|
||||
input: 'hello',
|
||||
session_id: 'session-1',
|
||||
// Sessions stored before Phase 003 can still carry the retired source.
|
||||
source: 'retired_source',
|
||||
session_source: 'global_agent',
|
||||
})
|
||||
|
||||
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 () => {
|
||||
ensureReadyMock
|
||||
.mockResolvedValueOnce({
|
||||
reachable: false,
|
||||
status: 'unreachable',
|
||||
endpoint: 'ipc:///tmp/hermes-agent-bridge.sock',
|
||||
error: 'bridge offline',
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
reachable: true,
|
||||
status: 'ready',
|
||||
endpoint: 'ipc:///tmp/hermes-agent-bridge.sock',
|
||||
})
|
||||
const { ChatRunSocket } = await import('../../packages/server/src/services/hermes/run-chat')
|
||||
const { emitted, io, socket } = makeServerHarness()
|
||||
const server = new ChatRunSocket(io as any)
|
||||
|
||||
;(server as any).sessionMap.set('session-1', {
|
||||
messages: [],
|
||||
isWorking: true,
|
||||
isAborting: false,
|
||||
events: [],
|
||||
queue: [{
|
||||
queue_id: 'queue-next',
|
||||
input: 'second queued message',
|
||||
source: 'cli',
|
||||
profile: 'default',
|
||||
}],
|
||||
profile: 'default',
|
||||
source: 'cli',
|
||||
})
|
||||
|
||||
;(server as any).runQueuedItem(socket, 'session-1', {
|
||||
queue_id: 'queue-failed',
|
||||
input: 'first queued message',
|
||||
source: 'cli',
|
||||
profile: 'default',
|
||||
}, 'default')
|
||||
|
||||
await vi.waitFor(() => expect(ensureReadyMock).toHaveBeenCalledTimes(2))
|
||||
expect(handleBridgeRunMock).toHaveBeenCalledTimes(1)
|
||||
expect(handleBridgeRunMock.mock.calls[0][2]).toEqual(expect.objectContaining({
|
||||
input: 'second queued message',
|
||||
queue_id: 'queue-next',
|
||||
}))
|
||||
expect(socket.emit).toHaveBeenCalledWith('run.failed', {
|
||||
event: 'run.failed',
|
||||
session_id: 'session-1',
|
||||
error: 'Agent Bridge is not reachable: bridge offline',
|
||||
queue_remaining: 1,
|
||||
})
|
||||
expect(emitted).toContainEqual({
|
||||
room: 'session:session-1',
|
||||
event: 'run.queued',
|
||||
payload: expect.objectContaining({
|
||||
event: 'run.queued',
|
||||
session_id: 'session-1',
|
||||
queue_length: 0,
|
||||
dequeued_queue_id: 'queue-next',
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
it('reattaches a loaded running bridge run without probing manager readiness again', async () => {
|
||||
bridgeMock.statusIfLoaded
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
exists: true,
|
||||
running: true,
|
||||
current_run_id: 'run-1',
|
||||
loaded: true,
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
exists: true,
|
||||
running: true,
|
||||
current_run_id: 'run-1',
|
||||
loaded: true,
|
||||
})
|
||||
const { ChatRunSocket } = await import('../../packages/server/src/services/hermes/run-chat')
|
||||
const { emitted, handlers, io, socket } = makeServerHarness()
|
||||
const server = new ChatRunSocket(io as any)
|
||||
|
||||
;(server as any).onConnection(socket)
|
||||
await handlers.get('resume')?.({ session_id: 'session-1' })
|
||||
|
||||
expect(ensureReadyMock).not.toHaveBeenCalled()
|
||||
expect(resumeBridgeRunMock).toHaveBeenCalledTimes(1)
|
||||
expect(socket.emit).toHaveBeenCalledWith('resumed', expect.objectContaining({
|
||||
session_id: 'session-1',
|
||||
isWorking: true,
|
||||
}))
|
||||
expect(emitted.some(({ event }) => event === 'run.failed')).toBe(false)
|
||||
expect((server as any).sessionMap.get('session-1')).toEqual(expect.objectContaining({
|
||||
isWorking: true,
|
||||
isAborting: false,
|
||||
runId: 'run-1',
|
||||
activeRunMarker: undefined,
|
||||
profile: 'default',
|
||||
source: 'cli',
|
||||
events: [],
|
||||
}))
|
||||
expect((server as any).bridgeResumePolls.size).toBe(0)
|
||||
})
|
||||
|
||||
it('emits a non-terminal reattach warning and preserves stale state when bridge status lookup throws during resume', async () => {
|
||||
bridgeMock.statusIfLoaded.mockRejectedValueOnce(new Error('connect ECONNREFUSED ipc:///tmp/hermes-agent-bridge.sock'))
|
||||
loadSessionStateFromDbMock.mockResolvedValueOnce({
|
||||
messages: [],
|
||||
isWorking: false,
|
||||
isAborting: true,
|
||||
runId: 'stale-run',
|
||||
activeRunMarker: 'marker-1',
|
||||
profile: 'default',
|
||||
source: 'cli',
|
||||
events: [{ event: 'run.started', data: { run_id: 'stale-run' } }],
|
||||
queue: [],
|
||||
})
|
||||
const { ChatRunSocket } = await import('../../packages/server/src/services/hermes/run-chat')
|
||||
const { emitted, handlers, io, socket } = makeServerHarness()
|
||||
const server = new ChatRunSocket(io as any)
|
||||
|
||||
;(server as any).onConnection(socket)
|
||||
await handlers.get('resume')?.({ session_id: 'session-1' })
|
||||
|
||||
expect(ensureReadyMock).not.toHaveBeenCalled()
|
||||
expect(resumeBridgeRunMock).not.toHaveBeenCalled()
|
||||
expect(emitted).toContainEqual({
|
||||
room: 'session:session-1',
|
||||
event: 'run.reattach_failed',
|
||||
payload: {
|
||||
event: 'run.reattach_failed',
|
||||
session_id: 'session-1',
|
||||
error: 'connect ECONNREFUSED configured endpoint',
|
||||
message: 'Unable to confirm Agent Bridge status while resuming: connect ECONNREFUSED configured endpoint',
|
||||
text: 'Unable to confirm Agent Bridge status while resuming: connect ECONNREFUSED configured endpoint',
|
||||
},
|
||||
})
|
||||
expect(socket.emit).toHaveBeenCalledWith('resumed', expect.objectContaining({
|
||||
session_id: 'session-1',
|
||||
isWorking: false,
|
||||
isAborting: true,
|
||||
events: [expect.objectContaining({
|
||||
event: 'run.reattach_failed',
|
||||
data: expect.objectContaining({
|
||||
error: 'connect ECONNREFUSED configured endpoint',
|
||||
}),
|
||||
})],
|
||||
}))
|
||||
expect((server as any).sessionMap.get('session-1')).toEqual(expect.objectContaining({
|
||||
isWorking: false,
|
||||
isAborting: true,
|
||||
runId: 'stale-run',
|
||||
activeRunMarker: 'marker-1',
|
||||
profile: 'default',
|
||||
source: 'cli',
|
||||
events: expect.arrayContaining([
|
||||
expect.objectContaining({ event: 'run.started' }),
|
||||
expect.objectContaining({
|
||||
event: 'run.reattach_failed',
|
||||
data: expect.objectContaining({
|
||||
error: 'connect ECONNREFUSED configured endpoint',
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
}))
|
||||
expect((server as any).bridgeResumePolls.size).toBe(0)
|
||||
})
|
||||
|
||||
it('suppresses transient bridge status timeout warnings while resuming', async () => {
|
||||
bridgeMock.statusIfLoaded.mockRejectedValueOnce(new Error('Agent bridge request timed out after 1000ms'))
|
||||
loadSessionStateFromDbMock.mockResolvedValueOnce({
|
||||
messages: [],
|
||||
isWorking: false,
|
||||
isAborting: false,
|
||||
runId: undefined,
|
||||
activeRunMarker: undefined,
|
||||
profile: 'default',
|
||||
source: 'cli',
|
||||
events: [],
|
||||
queue: [],
|
||||
})
|
||||
const { ChatRunSocket } = await import('../../packages/server/src/services/hermes/run-chat')
|
||||
const { emitted, handlers, io, socket } = makeServerHarness()
|
||||
const server = new ChatRunSocket(io as any)
|
||||
|
||||
;(server as any).onConnection(socket)
|
||||
await handlers.get('resume')?.({ session_id: 'session-1' })
|
||||
|
||||
expect(ensureReadyMock).not.toHaveBeenCalled()
|
||||
expect(resumeBridgeRunMock).not.toHaveBeenCalled()
|
||||
expect(emitted.some(({ event }) => event === 'run.reattach_failed')).toBe(false)
|
||||
expect(socket.emit).toHaveBeenCalledWith('resumed', expect.objectContaining({
|
||||
session_id: 'session-1',
|
||||
isWorking: false,
|
||||
events: [],
|
||||
}))
|
||||
expect((server as any).sessionMap.get('session-1')).toEqual(expect.objectContaining({
|
||||
isWorking: false,
|
||||
events: [],
|
||||
}))
|
||||
expect((server as any).bridgeResumePolls.size).toBe(0)
|
||||
})
|
||||
|
||||
it('reattaches a session stored with a retired source on the bridge', async () => {
|
||||
getSessionMock.mockImplementation((sessionId?: string) => sessionId
|
||||
? { 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,
|
||||
isAborting: false,
|
||||
runId: undefined,
|
||||
activeRunMarker: undefined,
|
||||
profile: 'default',
|
||||
source: 'retired_source',
|
||||
events: [],
|
||||
queue: [],
|
||||
})
|
||||
const { ChatRunSocket } = await import('../../packages/server/src/services/hermes/run-chat')
|
||||
const { emitted, handlers, io, socket } = makeServerHarness()
|
||||
const server = new ChatRunSocket(io as any)
|
||||
|
||||
;(server as any).onConnection(socket)
|
||||
await handlers.get('resume')?.({ session_id: 'session-1' })
|
||||
|
||||
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: 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 () => {
|
||||
getSessionMock.mockImplementation((sessionId?: string) => sessionId
|
||||
? { id: sessionId, profile: 'default', source: 'global_agent', model: 'gpt-test', provider: 'openai' }
|
||||
: undefined)
|
||||
bridgeMock.statusIfLoaded.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
exists: true,
|
||||
running: true,
|
||||
loaded: true,
|
||||
current_run_id: 'run-global',
|
||||
})
|
||||
loadSessionStateFromDbMock.mockResolvedValueOnce({
|
||||
messages: [],
|
||||
isWorking: false,
|
||||
isAborting: false,
|
||||
runId: undefined,
|
||||
activeRunMarker: undefined,
|
||||
profile: 'default',
|
||||
source: 'global_agent',
|
||||
events: [],
|
||||
queue: [],
|
||||
})
|
||||
const { ChatRunSocket } = await import('../../packages/server/src/services/hermes/run-chat')
|
||||
const { emitted, handlers, io, socket } = makeServerHarness()
|
||||
const server = new ChatRunSocket(io as any)
|
||||
|
||||
;(server as any).onConnection(socket)
|
||||
await handlers.get('resume')?.({ session_id: 'session-1' })
|
||||
|
||||
expect(ensureReadyMock).not.toHaveBeenCalled()
|
||||
expect(bridgeMock.statusIfLoaded).toHaveBeenCalledWith('session-1', 'default', { timeoutMs: 1000 })
|
||||
expect(resumeBridgeRunMock).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
socket,
|
||||
expect.objectContaining({
|
||||
sessionId: 'session-1',
|
||||
runId: 'run-global',
|
||||
source: 'global_agent',
|
||||
}),
|
||||
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: true,
|
||||
events: [],
|
||||
}))
|
||||
})
|
||||
|
||||
it('checks Hermes bridge status when resuming an api server session', async () => {
|
||||
getSessionMock.mockImplementation((sessionId?: string) => sessionId
|
||||
? { id: sessionId, profile: 'default', source: 'api_server', model: 'gpt-test', provider: 'openai' }
|
||||
: undefined)
|
||||
bridgeMock.statusIfLoaded.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
exists: false,
|
||||
running: false,
|
||||
loaded: false,
|
||||
})
|
||||
loadSessionStateFromDbMock.mockResolvedValueOnce({
|
||||
messages: [],
|
||||
isWorking: false,
|
||||
isAborting: false,
|
||||
runId: undefined,
|
||||
activeRunMarker: undefined,
|
||||
profile: 'default',
|
||||
source: 'api_server',
|
||||
events: [],
|
||||
queue: [],
|
||||
})
|
||||
const { ChatRunSocket } = await import('../../packages/server/src/services/hermes/run-chat')
|
||||
const { emitted, handlers, io, socket } = makeServerHarness()
|
||||
const server = new ChatRunSocket(io as any)
|
||||
|
||||
;(server as any).onConnection(socket)
|
||||
await handlers.get('resume')?.({ session_id: 'session-1' })
|
||||
|
||||
expect(ensureReadyMock).not.toHaveBeenCalled()
|
||||
expect(bridgeMock.statusIfLoaded).toHaveBeenCalledWith('session-1', 'default', { timeoutMs: 1000 })
|
||||
expect(resumeBridgeRunMock).not.toHaveBeenCalled()
|
||||
expect(emitted.some(({ event }) => event === 'run.reattach_failed')).toBe(false)
|
||||
expect(socket.emit).toHaveBeenCalledWith('resumed', expect.objectContaining({
|
||||
session_id: 'session-1',
|
||||
isWorking: false,
|
||||
events: [],
|
||||
}))
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user