Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 106 additions & 46 deletions src/services/AgentSummary/__tests__/agentSummary.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import type {
CacheSafeParams,
ForkedAgentResult,
} from '../../../utils/forkedAgent.js'
import { startAgentSummarization } from '../agentSummary.js'
import {
type AgentSummaryDependencies,
startAgentSummarization,
} from '../agentSummary.js'

const transcriptMessages = [
{ type: 'user', message: { content: 'start' }, uuid: 'u1' },
Expand All @@ -27,17 +30,14 @@ describe('startAgentSummarization', () => {
let forkCalls: ForkCall[]
let updateCalls: Array<{ taskId: string; summary: string }>
let transcriptMessagesForTest: Message[]
let debugLogs: string[]
let loggedErrors: Error[]
let clearedHandles: unknown[]

beforeEach(() => {
forkCalls = []
updateCalls = []
scheduled = undefined
handle = undefined
transcriptMessagesForTest = transcriptMessages
})

test('summarizes bounded transcript once and skips unchanged fingerprints', async () => {
handle = startAgentSummarization(
function startTestSummarization(
dependencies: AgentSummaryDependencies = {},
): { stop: () => void } {
return startAgentSummarization(
'task-1',
asAgentId('a0000000000000000'),
{
Expand All @@ -48,14 +48,22 @@ describe('startAgentSummarization', () => {
} as unknown as CacheSafeParams,
() => undefined,
{
clearTimeout: () => undefined,
clearTimeout: ((timeoutId: unknown) => {
clearedHandles.push(timeoutId)
}) as typeof clearTimeout,
getAgentTranscript: async () => ({
messages: transcriptMessagesForTest,
contentReplacements: [],
}),
isPoorModeActive: () => false,
logError: () => undefined,
logForDebugging: () => undefined,
logError: error => {
loggedErrors.push(
error instanceof Error ? error : new Error(String(error)),
)
},
logForDebugging: message => {
debugLogs.push(message)
},
runForkedAgent: async (args: ForkCall) => {
forkCalls.push(args)
return {
Expand All @@ -79,8 +87,24 @@ describe('startAgentSummarization', () => {
updateAgentSummary: (taskId: string, summary: string) => {
updateCalls.push({ taskId, summary })
},
...dependencies,
},
)
}

beforeEach(() => {
forkCalls = []
updateCalls = []
scheduled = undefined
handle = undefined
transcriptMessagesForTest = transcriptMessages
debugLogs = []
loggedErrors = []
clearedHandles = []
})

test('summarizes bounded transcript once and skips unchanged fingerprints', async () => {
handle = startTestSummarization()

expect(typeof scheduled).toBe('function')
await scheduled!()
Expand All @@ -106,47 +130,83 @@ describe('startAgentSummarization', () => {
expect(updateCalls).toHaveLength(1)
})

test('skips summarization when bounded context is too small', async () => {
transcriptMessagesForTest = transcriptMessages.slice(0, 2)

handle = startAgentSummarization(
'task-1',
asAgentId('a0000000000000000'),
test('skips summarization when filtering leaves too little bounded context', async () => {
transcriptMessagesForTest = [
{ type: 'user', message: { content: 'start' }, uuid: 'u1' },
{
forkContextMessages: transcriptMessages,
model: 'claude-test',
} as unknown as CacheSafeParams,
() => undefined,
{
clearTimeout: () => undefined,
getAgentTranscript: async () => ({
messages: transcriptMessagesForTest,
contentReplacements: [],
}),
isPoorModeActive: () => false,
logError: () => undefined,
logForDebugging: () => undefined,
runForkedAgent: async (args: ForkCall) => {
forkCalls.push(args)
return { messages: [] } as unknown as ForkedAgentResult
},
setTimeout: ((callback: TimerHandler) => {
if (typeof callback !== 'function') {
throw new Error('Expected timer callback')
}
scheduled = callback as () => void | Promise<void>
return 1 as unknown as ReturnType<typeof setTimeout>
}) as unknown as typeof setTimeout,
updateAgentSummary: (taskId: string, summary: string) => {
updateCalls.push({ taskId, summary })
type: 'assistant',
uuid: 'a1',
message: {
content: [{ type: 'tool_use', id: 'missing', name: 'Read' }],
},
},
{ type: 'user', message: { content: 'continue' }, uuid: 'u2' },
] as unknown as Message[]

handle = startTestSummarization()

expect(typeof scheduled).toBe('function')
await scheduled!()

expect(forkCalls).toEqual([])
expect(updateCalls).toEqual([])
expect(debugLogs).toContain(
'[AgentSummary] Skipping summary for task-1: no bounded context available',
)
})

test('skips summarization before building context when transcript is too short', async () => {
transcriptMessagesForTest = transcriptMessages.slice(0, 2)
handle = startTestSummarization()

expect(typeof scheduled).toBe('function')
await scheduled!()

expect(forkCalls).toEqual([])
expect(updateCalls).toEqual([])
expect(debugLogs).toContain(
'[AgentSummary] Skipping summary for task-1: not enough messages (2)',
)
})

test('skips and reschedules while poor mode is active', async () => {
handle = startTestSummarization({
isPoorModeActive: () => true,
})

expect(typeof scheduled).toBe('function')
await scheduled!()

expect(forkCalls).toEqual([])
expect(updateCalls).toEqual([])
expect(debugLogs).toContain(
'[AgentSummary] Skipping summary — poor mode active',
)
})

test('logs summary errors and keeps the next timer owned by the summarizer', async () => {
const error = new Error('fork failed')
handle = startTestSummarization({
runForkedAgent: async () => {
throw error
},
})

expect(typeof scheduled).toBe('function')
await scheduled!()

expect(loggedErrors).toEqual([error])
expect(updateCalls).toEqual([])
})

test('stop clears the pending summary timer', () => {
handle = startTestSummarization()

handle.stop()

expect(debugLogs).toContain(
'[AgentSummary] Stopping summarization for task-1',
)
expect(clearedHandles).toEqual([1])
})
})
7 changes: 7 additions & 0 deletions src/services/AgentSummary/__tests__/summaryContext.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ describe('getSummaryContextFingerprint', () => {
expect(estimateMessageChars(message)).toBeGreaterThan(0)
})

test('treats unsupported top-level primitives as zero-size estimates', () => {
expect(
estimateMessageChars((() => undefined) as unknown as Message),
).toBe(0)
expect(estimateMessageChars(1n as unknown as Message)).toBe(0)
})

test('returns null for an empty transcript', () => {
expect(getSummaryContextFingerprint([])).toBeNull()
})
Expand Down
31 changes: 31 additions & 0 deletions src/utils/__tests__/teammateMailbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,17 @@ describe('compactMailboxMessages', () => {

expect(compacted).toEqual([])
})

test('returns an empty mailbox when all retention lanes are disabled', () => {
const compacted = compactMailboxMessages([message('unread', false)], {
maxMessages: 0,
maxReadMessages: 0,
maxUnreadProtocolMessages: 0,
maxRetainedBytes: 1_000,
})

expect(compacted).toEqual([])
})
})

describe('teammate mailbox retention', () => {
Expand Down Expand Up @@ -331,6 +342,26 @@ describe('teammate mailbox retention', () => {
expect(await readFile(inboxPath, 'utf-8')).toBe('{not-json')
})

test('writeToMailbox rejects when the inbox path is already a directory', async () => {
const inboxPath = getInboxPath('worker', 'alpha')
await mkdir(inboxPath, { recursive: true })

const error = await writeToMailbox(
'worker',
{
from: 'team-lead',
text: 'new',
timestamp: new Date(5).toISOString(),
},
'alpha',
).then(
() => undefined,
error => error as NodeJS.ErrnoException,
)

expect(error?.code).toBe('EISDIR')
})

test('readMailbox fails closed on corrupt mailbox content', async () => {
const inboxPath = getInboxPath('worker', 'alpha')
await mkdir(dirname(inboxPath), { recursive: true })
Expand Down
17 changes: 17 additions & 0 deletions src/utils/__tests__/udsMessaging.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,23 @@ describe('UDS inbox retention', () => {
)
})

test('udsClient send reports connection failures without leaking token state', async () => {
const path = socketPath('uds-client-connect-error')
const capabilityDir = join(tempConfigDir, 'messaging-capabilities')
const capabilityName = `${createHash('sha256').update(path).digest('hex')}.json`
await mkdir(capabilityDir, { recursive: true, mode: 0o700 })
await writeFile(
join(capabilityDir, capabilityName),
JSON.stringify({ socketPath: path, authToken: 'test-token' }),
'utf-8',
)
const { sendToUdsSocket } = await import('../udsClient.js')

await expect(sendToUdsSocket(path, 'hello')).rejects.toThrow(
'Failed to connect to peer',
)
})

test('sendUdsMessage fails closed before connecting without an auth token', async () => {
await expect(
sendUdsMessage(socketPath('no-auth-token'), { type: 'text', data: 'x' }),
Expand Down
47 changes: 47 additions & 0 deletions src/utils/__tests__/udsResponseReader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,28 @@ describe('attachUdsResponseReader', () => {
expect(socket.ended).toBe(true)
})

test('continues scanning when blank and valid frames share one chunk', () => {
const socket = new FakeSocket()
let settled = false
let settledError: Error | undefined

attachUdsResponseReader(asSocket(socket), {
maxFrameBytes: 128,
onSettled: error => {
settled = true
settledError = error
},
})

socket.emitData(
Buffer.from(`\n${JSON.stringify({ type: 'response' })}\n`),
)

expect(settled).toBe(true)
expect(settledError).toBeUndefined()
expect(socket.ended).toBe(true)
})

test('rejects receiver error frames', () => {
const socket = new FakeSocket()
let settledError: Error | undefined
Expand All @@ -116,6 +138,31 @@ describe('attachUdsResponseReader', () => {
expect(socket.destroyed).toBe(true)
})

test('ignores unrelated receiver frames until a terminal response arrives', () => {
const socket = new FakeSocket()
let settled = false
let settledError: Error | undefined

attachUdsResponseReader(asSocket(socket), {
maxFrameBytes: 128,
onSettled: error => {
settled = true
settledError = error
},
})

socket.emitData(
Buffer.from(
`${JSON.stringify({ type: 'notification', data: 'queued' })}\n`,
),
)
expect(settled).toBe(false)

socket.emitData(Buffer.from(`${JSON.stringify({ type: 'response' })}\n`))
expect(settled).toBe(true)
expect(settledError).toBeUndefined()
})

test('uses custom socket error formatting', () => {
const socket = new FakeSocket()
let settledError: Error | undefined
Expand Down
Loading