2026-05-21 23:21:26 +08:00
import { beforeEach , describe , expect , it , vi } from 'vitest'
const getSystemPromptMock = vi . fn ()
const getSessionMock = vi . fn ()
const createSessionMock = vi . fn ()
const addMessageMock = vi . fn ()
const updateSessionMock = vi . fn ()
const updateSessionStatsMock = vi . fn ()
const updateUsageMock = vi . fn ()
const buildCompressedHistoryMock = vi . fn ()
const buildDbHistoryMock = vi . fn ()
2026-05-22 09:46:50 +08:00
const buildSnapshotAwareHistoryMock = vi . fn ( async ( _sessionId : string , _profile : string , history : any []) => history )
2026-05-21 23:21:26 +08:00
const pushStateMock = vi . fn ()
const replaceStateMock = vi . fn ()
const forceCompressBridgeHistoryMock = vi . fn ()
const calcAndUpdateUsageMock = vi . fn ()
const estimateUsageTokensFromMessagesMock = vi . fn ()
2026-05-22 09:46:50 +08:00
const updateContextTokenUsageMock = vi . fn (( sid : string , state : any , emit : any , contextTokens : number , usage ?: { inputTokens : number ; outputTokens : number }) => {
state . contextTokens = contextTokens
emit ( 'usage.updated' , {
event : 'usage.updated' ,
session_id : sid ,
inputTokens : usage?.inputTokens ?? state . inputTokens ?? 0 ,
outputTokens : usage?.outputTokens ?? state . outputTokens ?? 0 ,
contextTokens ,
})
return contextTokens
})
const getCachedBridgeContextOverheadMock = vi . fn (() => undefined )
const contextTokensWithCachedOverheadMock = vi . fn (( _state : any , messageTokens : number ) => messageTokens )
const updateMessageContextTokenUsageMock = vi . fn (( sid : string , state : any , emit : any , messageTokens : number , usage ?: { inputTokens : number ; outputTokens : number }) => updateContextTokenUsageMock ( sid , state , emit , messageTokens , usage ))
2026-05-21 23:21:26 +08:00
const flushBridgePendingToDbMock = vi . fn ()
const ensureOpenBridgeAssistantMessageMock = vi . fn ()
const syncBridgeReasoningToMessageMock = vi . fn ()
const recordBridgeToolStartedMock = vi . fn ()
const recordBridgeToolCompletedMock = vi . fn ()
const resolveBridgeRunModelConfigMock = vi . fn ()
vi . mock ( '../../packages/server/src/lib/llm-prompt' , () => ({
getSystemPrompt : getSystemPromptMock ,
}))
vi . mock ( '../../packages/server/src/db/hermes/session-store' , () => ({
getSession : getSessionMock ,
createSession : createSessionMock ,
addMessage : addMessageMock ,
updateSession : updateSessionMock ,
updateSessionStats : updateSessionStatsMock ,
}))
vi . mock ( '../../packages/server/src/db/hermes/usage-store' , () => ({
updateUsage : updateUsageMock ,
}))
vi . mock ( '../../packages/server/src/services/logger' , () => ({
logger : { info : vi.fn (), warn : vi.fn (), error : vi.fn (), debug : vi.fn () },
bridgeLogger : { info : vi.fn (), warn : vi.fn (), error : vi.fn (), debug : vi.fn () },
}))
vi . mock ( '../../packages/server/src/services/hermes/run-chat/compression' , () => ({
buildCompressedHistory : buildCompressedHistoryMock ,
buildDbHistory : buildDbHistoryMock ,
2026-05-22 09:46:50 +08:00
buildSnapshotAwareHistory : buildSnapshotAwareHistoryMock ,
2026-05-21 23:21:26 +08:00
pushState : pushStateMock ,
replaceState : replaceStateMock ,
forceCompressBridgeHistory : forceCompressBridgeHistoryMock ,
}))
vi . mock ( '../../packages/server/src/services/hermes/run-chat/usage' , () => ({
calcAndUpdateUsage : calcAndUpdateUsageMock ,
estimateUsageTokensFromMessages : estimateUsageTokensFromMessagesMock ,
2026-05-22 09:46:50 +08:00
getCachedBridgeContextOverhead : getCachedBridgeContextOverheadMock ,
contextTokensWithCachedOverhead : contextTokensWithCachedOverheadMock ,
updateContextTokenUsage : updateContextTokenUsageMock ,
updateMessageContextTokenUsage : updateMessageContextTokenUsageMock ,
2026-05-21 23:21:26 +08:00
}))
vi . mock ( '../../packages/server/src/services/hermes/run-chat/bridge-message' , () => ({
flushBridgePendingToDb : flushBridgePendingToDbMock ,
ensureOpenBridgeAssistantMessage : ensureOpenBridgeAssistantMessageMock ,
syncBridgeReasoningToMessage : syncBridgeReasoningToMessageMock ,
recordBridgeToolStarted : recordBridgeToolStartedMock ,
recordBridgeToolCompleted : recordBridgeToolCompletedMock ,
}))
vi . mock ( '../../packages/server/src/services/hermes/run-chat/model-config' , () => ({
resolveBridgeRunModelConfig : resolveBridgeRunModelConfigMock ,
}))
function makeSocket() {
return {
connected : true ,
emit : vi.fn (),
join : vi.fn (),
2026-05-24 08:59:21 +08:00
to : vi.fn (() => ({ emit : vi.fn () })),
2026-05-21 23:21:26 +08:00
} as any
}
function makeNamespace ( emit : ReturnType < typeof vi.fn >) {
const room = new Set ([ 'socket-1' ])
return {
adapter : { rooms : new Map ([[ 'session:session-1' , room ]]) },
to : vi.fn (() => ({ emit })),
} as any
}
function makeState() {
return {
messages : [],
isWorking : false ,
events : [],
queue : [],
} as any
}
describe ( 'bridge run final context usage' , () => {
beforeEach (() => {
vi . clearAllMocks ()
getSystemPromptMock . mockReturnValue ( 'system prompt' )
getSessionMock . mockReturnValue ({ id : 'session-1' , profile : 'default' , model : '' , provider : '' })
resolveBridgeRunModelConfigMock . mockResolvedValue ({ model : 'gpt-test' , provider : 'openai' })
buildCompressedHistoryMock . mockResolvedValue ([{ role : 'user' , content : 'previous' }])
buildDbHistoryMock . mockResolvedValue ([
{ role : 'user' , content : 'hello' },
{ role : 'assistant' , content : 'done' },
])
2026-05-22 09:46:50 +08:00
buildSnapshotAwareHistoryMock . mockImplementation ( async ( _sessionId : string , _profile : string , history : any []) => history )
2026-05-21 23:21:26 +08:00
calcAndUpdateUsageMock . mockResolvedValue ({ inputTokens : 11 , outputTokens : 7 })
2026-05-22 09:46:50 +08:00
estimateUsageTokensFromMessagesMock . mockReturnValue ({ inputTokens : 11 , outputTokens : 7 })
2026-05-26 16:32:07 +08:00
getCachedBridgeContextOverheadMock . mockImplementation (( state : any ) => {
const fixed = state ? . bridgeContext ? . fixedContextTokens
return typeof fixed === 'number' ? fixed : undefined
})
contextTokensWithCachedOverheadMock . mockImplementation (( state : any , messageTokens : number ) => {
const fixed = state ? . bridgeContext ? . fixedContextTokens
return typeof fixed === 'number' ? fixed + messageTokens : messageTokens
})
updateMessageContextTokenUsageMock . mockImplementation (( sid : string , state : any , emit : any , messageTokens : number , usage ?: { inputTokens : number ; outputTokens : number }) => {
const contextTokens = contextTokensWithCachedOverheadMock ( state , messageTokens )
return updateContextTokenUsageMock ( sid , state , emit , contextTokens , usage )
})
2026-05-21 23:21:26 +08:00
})
it ( 'refreshes full context tokens when a bridge run completes' , async () => {
const emit = vi . fn ()
const nsp = makeNamespace ( emit )
const socket = makeSocket ()
const state = makeState ()
const sessionMap = new Map ([[ 'session-1' , state ]])
const bridge = {
chat : vi.fn (). mockResolvedValue ({ run_id : 'run-1' , status : 'started' }),
contextEstimate : vi.fn (). mockResolvedValue ({
token_count : 12345 ,
2026-05-26 16:32:07 +08:00
fixed_context_tokens : 12327 ,
2026-05-21 23:21:26 +08:00
message_count : 2 ,
tool_count : 4 ,
system_prompt_chars : 13 ,
}),
streamOutput : vi.fn ( async function * () {
yield { run_id : 'run-1' , done : true , status : 'completed' , output : 'done' }
}),
} as any
const { handleBridgeRun } = await import ( '../../packages/server/src/services/hermes/run-chat/handle-bridge-run' )
await handleBridgeRun (
nsp ,
socket ,
{ input : 'hello' , session_id : 'session-1' },
'default' ,
sessionMap ,
bridge ,
false ,
vi . fn (),
vi . fn (),
)
expect ( bridge . contextEstimate ). toHaveBeenCalledWith (
'session-1' ,
2026-05-26 16:32:07 +08:00
[],
2026-05-24 12:52:14 +08:00
expect . stringContaining ( '[Current Hermes profile: default]' ),
2026-05-21 23:21:26 +08:00
'default' ,
{ model : 'gpt-test' , provider : 'openai' },
)
2026-05-24 12:52:14 +08:00
expect ( bridge . contextEstimate . mock . calls [ 0 ][ 2 ]). toContain ( 'system prompt' )
expect ( bridge . contextEstimate . mock . calls [ 0 ][ 2 ]). toContain ( 'X-Hermes-Profile' )
2026-05-21 23:21:26 +08:00
expect ( state . contextTokens ). toBe ( 12345 )
expect ( emit ). toHaveBeenCalledWith ( 'usage.updated' , expect . objectContaining ({
inputTokens : 11 ,
outputTokens : 7 ,
contextTokens : 12345 ,
}))
expect ( emit ). toHaveBeenCalledWith ( 'run.completed' , expect . objectContaining ({
inputTokens : 11 ,
outputTokens : 7 ,
contextTokens : 12345 ,
}))
})
2026-05-25 19:26:23 +08:00
it ( 'evaluates active goals after a successful bridge run and queues continuation prompts' , async () => {
const emit = vi . fn ()
const nsp = makeNamespace ( emit )
const socket = makeSocket ()
const state = makeState ()
const sessionMap = new Map ([[ 'session-1' , state ]])
const dequeueNextQueuedRun = vi . fn ()
addMessageMock . mockReturnValue ( 42 )
const bridge = {
chat : vi.fn (). mockResolvedValue ({ run_id : 'run-1' , status : 'started' }),
contextEstimate : vi.fn (). mockResolvedValue ({
token_count : 12345 ,
message_count : 2 ,
tool_count : 4 ,
system_prompt_chars : 13 ,
}),
goalEvaluate : vi.fn (). mockResolvedValue ({
handled : true ,
should_continue : true ,
continuation_prompt : '[Continuing toward your standing goal]\nGoal: fix tests' ,
message : '↻ Continuing toward goal (1/20): tests still fail' ,
verdict : 'continue' ,
}),
streamOutput : vi.fn ( async function * () {
yield {
run_id : 'run-1' ,
done : true ,
status : 'completed' ,
output : 'not finished' ,
result : { final_response : 'not finished' },
}
}),
} as any
const { handleBridgeRun } = await import ( '../../packages/server/src/services/hermes/run-chat/handle-bridge-run' )
await handleBridgeRun (
nsp ,
socket ,
{
input : 'hello' ,
session_id : 'session-1' ,
model_groups : [{ provider : 'openai' , models : [ 'gpt-test' ] }],
},
'default' ,
sessionMap ,
bridge ,
false ,
vi . fn (),
dequeueNextQueuedRun ,
)
expect ( bridge . goalEvaluate ). toHaveBeenCalledWith ( 'session-1' , 'not finished' , 'default' )
expect ( addMessageMock ). toHaveBeenCalledWith ( expect . objectContaining ({
session_id : 'session-1' ,
role : 'command' ,
content : '↻ Continuing toward goal (1/20): tests still fail' ,
}))
expect ( emit ). toHaveBeenCalledWith ( 'session.command' , expect . objectContaining ({
command : 'goal' ,
action : 'continue' ,
message : '↻ Continuing toward goal (1/20): tests still fail' ,
}))
expect ( state . queue ). toEqual ([ expect . objectContaining ({
input : '[Continuing toward your standing goal]\nGoal: fix tests' ,
displayInput : null ,
storageMessage : '[Continuing toward your standing goal]\nGoal: fix tests' ,
model : 'gpt-test' ,
provider : 'openai' ,
model_groups : [{ provider : 'openai' , models : [ 'gpt-test' ] }],
goalContinuation : true ,
})])
expect ( dequeueNextQueuedRun ). toHaveBeenCalledWith ( socket , 'session-1' )
})
it ( 'skips hidden goal continuation runs without pausing when the judge is unavailable' , async () => {
const emit = vi . fn ()
const nsp = makeNamespace ( emit )
const socket = makeSocket ()
const state = makeState ()
const sessionMap = new Map ([[ 'session-1' , state ]])
const dequeueNextQueuedRun = vi . fn ()
addMessageMock . mockReturnValue ( 43 )
const bridge = {
chat : vi.fn (). mockResolvedValue ({ run_id : 'run-1' , status : 'started' }),
command : vi.fn (),
contextEstimate : vi.fn (). mockResolvedValue ({
token_count : 12345 ,
message_count : 2 ,
tool_count : 4 ,
system_prompt_chars : 13 ,
}),
goalEvaluate : vi.fn (). mockResolvedValue ({
handled : true ,
should_continue : true ,
continuation_prompt : '[Continuing toward your standing goal]\nGoal: fix tests' ,
message : '↻ Continuing toward goal (1/20): no auxiliary client configured' ,
verdict : 'continue' ,
reason : 'no auxiliary client configured' ,
}),
streamOutput : vi.fn ( async function * () {
yield {
run_id : 'run-1' ,
done : true ,
status : 'completed' ,
output : 'done' ,
result : { final_response : 'done' },
}
}),
} as any
const { handleBridgeRun } = await import ( '../../packages/server/src/services/hermes/run-chat/handle-bridge-run' )
await handleBridgeRun (
nsp ,
socket ,
{ input : 'hello' , session_id : 'session-1' },
'default' ,
sessionMap ,
bridge ,
false ,
vi . fn (),
dequeueNextQueuedRun ,
)
expect ( bridge . command ). not . toHaveBeenCalled ()
expect ( state . queue ). toEqual ([])
expect ( dequeueNextQueuedRun ). not . toHaveBeenCalled ()
expect ( emit ). toHaveBeenCalledWith ( 'session.command' , expect . objectContaining ({
command : 'goal' ,
action : 'judge_unavailable' ,
message : 'Goal judge is not configured; automatic goal continuation was skipped. The goal remains active, but Hermes cannot mark it done automatically.' ,
}))
})
2026-05-22 09:46:50 +08:00
it ( 'uses cached fixed context instead of bridge estimate when available' , async () => {
const emit = vi . fn ()
const nsp = makeNamespace ( emit )
const socket = makeSocket ()
const state = makeState ()
const sessionMap = new Map ([[ 'session-1' , state ]])
const bridge = {
chat : vi.fn (). mockResolvedValue ({ run_id : 'run-1' , status : 'started' }),
contextEstimate : vi.fn (),
streamOutput : vi.fn ( async function * () {
2026-05-26 16:32:07 +08:00
yield {
run_id : 'run-1' ,
done : false ,
status : 'running' ,
events : [{
event : 'bridge.context.ready' ,
fixed_context_tokens : 20_000 ,
system_prompt_tokens : 3_000 ,
tool_tokens : 17_000 ,
}],
}
2026-05-22 09:46:50 +08:00
yield { run_id : 'run-1' , done : true , status : 'completed' , output : 'done' }
}),
} as any
const { handleBridgeRun } = await import ( '../../packages/server/src/services/hermes/run-chat/handle-bridge-run' )
await handleBridgeRun (
nsp ,
socket ,
{ input : 'hello' , session_id : 'session-1' },
'default' ,
sessionMap ,
bridge ,
false ,
vi . fn (),
vi . fn (),
)
expect ( bridge . contextEstimate ). not . toHaveBeenCalled ()
expect ( updateMessageContextTokenUsageMock ). toHaveBeenCalledWith (
'session-1' ,
state ,
expect . any ( Function ),
18 ,
{ inputTokens : 11 , outputTokens : 7 },
)
expect ( state . contextTokens ). toBe ( 20 _018 )
expect ( emit ). toHaveBeenCalledWith ( 'run.completed' , expect . objectContaining ({
contextTokens : 20_018 ,
}))
})
2026-05-26 16:32:07 +08:00
it ( 'keeps bridge context ready updates on the snapshot-aware token baseline' , async () => {
const emit = vi . fn ()
const nsp = makeNamespace ( emit )
const socket = makeSocket ()
const state = makeState ()
const sessionMap = new Map ([[ 'session-1' , state ]])
calcAndUpdateUsageMock . mockResolvedValue ({ inputTokens : 28_000 , outputTokens : 0 })
buildDbHistoryMock . mockResolvedValue ([
{ role : 'user' , content : 'very large old context' },
{ role : 'assistant' , content : 'large old response' },
{ role : 'user' , content : 'hello' },
])
buildSnapshotAwareHistoryMock . mockResolvedValue ([
{ role : 'user' , content : '[Previous context summary]\n\nsmall summary' },
{ role : 'user' , content : 'hello' },
])
estimateUsageTokensFromMessagesMock . mockImplementation (( messages : any []) => {
if ( messages ? .[ 0 ] ? . content ? . includes ( 'small summary' )) {
return { inputTokens : 9_000 , outputTokens : 0 }
}
return { inputTokens : 28_000 , outputTokens : 0 }
})
const bridge = {
chat : vi.fn (). mockResolvedValue ({ run_id : 'run-1' , status : 'started' }),
contextEstimate : vi.fn (),
streamOutput : vi.fn ( async function * () {
yield {
run_id : 'run-1' ,
done : false ,
status : 'running' ,
events : [{
event : 'bridge.context.ready' ,
fixed_context_tokens : 10_000 ,
system_prompt_tokens : 2_000 ,
tool_tokens : 8_000 ,
}],
}
yield { run_id : 'run-1' , done : true , status : 'completed' , output : 'done' }
}),
} as any
const { handleBridgeRun } = await import ( '../../packages/server/src/services/hermes/run-chat/handle-bridge-run' )
await handleBridgeRun (
nsp ,
socket ,
{ input : 'hello' , session_id : 'session-1' },
'default' ,
sessionMap ,
bridge ,
false ,
vi . fn (),
vi . fn (),
)
expect ( updateMessageContextTokenUsageMock ). toHaveBeenCalledWith (
'session-1' ,
state ,
expect . any ( Function ),
9 _000 ,
{ inputTokens : 28_000 , outputTokens : 0 },
)
expect ( updateMessageContextTokenUsageMock ). not . toHaveBeenCalledWith (
'session-1' ,
state ,
expect . any ( Function ),
28 _000 ,
{ inputTokens : 28_000 , outputTokens : 0 },
)
expect ( state . contextTokens ). toBe ( 19 _000 )
expect ( emit ). toHaveBeenCalledWith ( 'run.completed' , expect . objectContaining ({
contextTokens : 19_000 ,
}))
})
2026-05-25 11:09:16 +08:00
it ( 'persists pending tool marker text before a bridge run completes' , async () => {
const emit = vi . fn ()
const nsp = makeNamespace ( emit )
const socket = makeSocket ()
const state = makeState ()
const persistedContent : string [] = []
flushBridgePendingToDbMock . mockImplementation (( targetState : any ) => {
persistedContent . push ( targetState . bridgePendingAssistantContent || '' )
targetState . bridgePendingAssistantContent = ''
})
ensureOpenBridgeAssistantMessageMock . mockImplementation (( targetState : any , sessionId : string , runMarker : string ) => {
let message = [... targetState . messages ]. reverse (). find (( m : any ) => m . runMarker === runMarker && m . role === 'assistant' && m . finish_reason == null )
if ( ! message ) {
message = {
id : targetState.messages.length + 1 ,
session_id : sessionId ,
runMarker ,
role : 'assistant' ,
content : '' ,
timestamp : Math.floor ( Date . now () / 1000 ),
}
targetState . messages . push ( message )
}
return message
})
const sessionMap = new Map ([[ 'session-1' , state ]])
const bridge = {
chat : vi.fn (). mockResolvedValue ({ run_id : 'run-1' , status : 'started' }),
contextEstimate : vi.fn (). mockResolvedValue ({
token_count : 12345 ,
message_count : 2 ,
tool_count : 4 ,
system_prompt_chars : 13 ,
}),
streamOutput : vi.fn ( async function * () {
yield { run_id : 'run-1' , done : false , status : 'running' , delta : 'Text [Call' , events : [] }
yield { run_id : 'run-1' , done : true , status : 'completed' , output : '' , events : [] }
}),
} as any
const { handleBridgeRun } = await import ( '../../packages/server/src/services/hermes/run-chat/handle-bridge-run' )
await handleBridgeRun (
nsp ,
socket ,
{ input : 'hello' , session_id : 'session-1' },
'default' ,
sessionMap ,
bridge ,
false ,
vi . fn (),
vi . fn (),
)
expect ( persistedContent ). toContain ( 'Text [Call' )
expect ( emit ). toHaveBeenCalledWith ( 'message.delta' , expect . objectContaining ({
delta : 'Text ' ,
output : 'Text ' ,
}))
expect ( emit ). toHaveBeenCalledWith ( 'message.delta' , expect . objectContaining ({
delta : '[Call' ,
output : 'Text [Call' ,
}))
expect ( emit ). toHaveBeenCalledWith ( 'run.completed' , expect . objectContaining ({
output : 'Text [Call' ,
}))
})
2026-05-25 15:48:17 +08:00
it ( 'persists the visible plan command instead of the expanded skill prompt' , async () => {
const emit = vi . fn ()
const nsp = makeNamespace ( emit )
const socket = makeSocket ()
const state = makeState ()
const sessionMap = new Map ([[ 'session-1' , state ]])
const bridge = {
chat : vi.fn (). mockResolvedValue ({ run_id : 'run-1' , status : 'started' }),
contextEstimate : vi.fn (). mockResolvedValue ({
token_count : 12345 ,
message_count : 2 ,
tool_count : 4 ,
system_prompt_chars : 13 ,
}),
streamOutput : vi.fn ( async function * () {
yield { run_id : 'run-1' , done : true , status : 'completed' , output : 'planned' }
}),
} as any
const { handleBridgeRun } = await import ( '../../packages/server/src/services/hermes/run-chat/handle-bridge-run' )
await handleBridgeRun (
nsp ,
socket ,
{
input : '[IMPORTANT: expanded plan skill prompt]' ,
display_input : '/plan build the feature' ,
display_role : 'command' ,
storage_message : '/plan build the feature' ,
session_id : 'session-1' ,
},
'default' ,
sessionMap ,
bridge ,
false ,
vi . fn (),
vi . fn (),
)
expect ( state . messages . find (( message : any ) => message . role === 'command' )). toEqual ( expect . objectContaining ({
role : 'command' ,
content : '/plan build the feature' ,
}))
expect ( addMessageMock ). toHaveBeenCalledWith ( expect . objectContaining ({
role : 'command' ,
content : '/plan build the feature' ,
}))
expect ( addMessageMock ). not . toHaveBeenCalledWith ( expect . objectContaining ({
role : 'user' ,
content : '[IMPORTANT: expanded plan skill prompt]' ,
}))
expect ( bridge . chat ). toHaveBeenCalledWith (
'session-1' ,
'[IMPORTANT: expanded plan skill prompt]' ,
expect . any ( Array ),
expect . any ( String ),
'default' ,
expect . objectContaining ({ storage_message : '/plan build the feature' }),
)
})
2026-05-21 23:21:26 +08:00
it ( 'refreshes full context tokens when a bridge run fails' , async () => {
const emit = vi . fn ()
const nsp = makeNamespace ( emit )
const socket = makeSocket ()
const state = makeState ()
const sessionMap = new Map ([[ 'session-1' , state ]])
const bridge = {
chat : vi.fn (). mockRejectedValue ( new Error ( 'bridge timeout' )),
contextEstimate : vi.fn (). mockResolvedValue ({
token_count : 54321 ,
2026-05-26 16:32:07 +08:00
fixed_context_tokens : 54303 ,
2026-05-21 23:21:26 +08:00
message_count : 1 ,
tool_count : 4 ,
system_prompt_chars : 13 ,
}),
streamOutput : vi.fn (),
} as any
const { handleBridgeRun } = await import ( '../../packages/server/src/services/hermes/run-chat/handle-bridge-run' )
await handleBridgeRun (
nsp ,
socket ,
{ input : 'hello' , session_id : 'session-1' },
'default' ,
sessionMap ,
bridge ,
false ,
vi . fn (),
vi . fn (),
)
expect ( state . contextTokens ). toBe ( 54321 )
expect ( emit ). toHaveBeenCalledWith ( 'usage.updated' , expect . objectContaining ({
inputTokens : 11 ,
outputTokens : 7 ,
contextTokens : 54321 ,
}))
expect ( emit ). toHaveBeenCalledWith ( 'run.failed' , expect . objectContaining ({
error : 'bridge timeout' ,
inputTokens : 11 ,
outputTokens : 7 ,
contextTokens : 54321 ,
}))
})
2026-05-25 19:26:23 +08:00
it ( 'emits bridge lifecycle status events so retries are visible' , async () => {
const emit = vi . fn ()
const nsp = makeNamespace ( emit )
const socket = makeSocket ()
const state = makeState ()
const sessionMap = new Map ([[ 'session-1' , state ]])
const bridge = {
chat : vi.fn (). mockResolvedValue ({ run_id : 'run-1' , status : 'started' }),
contextEstimate : vi.fn (). mockResolvedValue ({
token_count : 12345 ,
message_count : 2 ,
tool_count : 4 ,
system_prompt_chars : 13 ,
}),
streamOutput : vi.fn ( async function * () {
yield {
run_id : 'run-1' ,
done : false ,
status : 'running' ,
events : [
{ event : 'status' , kind : 'lifecycle' , text : 'Retrying in 3.0s (attempt 1/3)...' },
],
}
yield { run_id : 'run-1' , done : true , status : 'completed' , output : 'done' }
}),
} as any
const { handleBridgeRun } = await import ( '../../packages/server/src/services/hermes/run-chat/handle-bridge-run' )
await handleBridgeRun (
nsp ,
socket ,
{ input : 'hello' , session_id : 'session-1' },
'default' ,
sessionMap ,
bridge ,
false ,
vi . fn (),
vi . fn (),
)
expect ( replaceStateMock ). toHaveBeenCalledWith ( sessionMap , 'session-1' , 'agent.event' , expect . objectContaining ({
event : 'agent.event' ,
kind : 'lifecycle' ,
text : 'Retrying in 3.0s (attempt 1/3)...' ,
}))
expect ( emit ). toHaveBeenCalledWith ( 'agent.event' , expect . objectContaining ({
event : 'agent.event' ,
kind : 'lifecycle' ,
text : 'Retrying in 3.0s (attempt 1/3)...' ,
}))
})
2026-05-21 23:21:26 +08:00
})