Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
28 changes: 28 additions & 0 deletions cli/src/claude/claudeLocalLauncher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,32 @@ describe('claudeLocalLauncher message filtering', () => {

expect(sentMessages).toHaveLength(2)
})

it('filters out isMeta messages (e.g. skill injections)', async () => {
const { session, sentMessages } = createSessionStub()
await claudeLocalLauncher(session as never)

harness.scannerOnMessage!({
type: 'user',
isMeta: true,
uuid: '1',
message: { content: [{ type: 'text', text: '# Skill content...' }] }
})

expect(sentMessages).toHaveLength(0)
})

it('filters out isCompactSummary messages', async () => {
const { session, sentMessages } = createSessionStub()
await claudeLocalLauncher(session as never)

harness.scannerOnMessage!({
type: 'assistant',
isCompactSummary: true,
uuid: '1',
message: { content: 'compacted context' }
})

expect(sentMessages).toHaveLength(0)
})
})
5 changes: 5 additions & 0 deletions cli/src/claude/claudeLocalLauncher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export async function claudeLocalLauncher(session: Session): Promise<'switch' |
if (message.type === 'summary') {
return
}
// Filter out internal meta messages (e.g. skill injections) and
// compact summaries to avoid them appearing in the web UI
if (message.isMeta || message.isCompactSummary) {
return
}
// Filter out invisible system messages (e.g. init, stop_hook_summary)
// to avoid them showing as raw JSON in the web UI
if (!isClaudeChatVisibleMessage(message)) {
Expand Down
51 changes: 51 additions & 0 deletions cli/src/claude/utils/OutgoingMessageQueue.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { OutgoingMessageQueue } from './OutgoingMessageQueue'

describe('OutgoingMessageQueue message filtering', () => {
let sent: Array<Record<string, unknown>>
let queue: OutgoingMessageQueue

beforeEach(() => {
sent = []
queue = new OutgoingMessageQueue((msg) => { sent.push(msg) })
})

afterEach(() => {
queue.destroy()
})

it('sends normal messages', async () => {
queue.enqueue({ type: 'assistant', uuid: '1' })
queue.enqueue({ type: 'user', uuid: '2' })
await queue.flush()

expect(sent).toHaveLength(2)
})

it('filters out system messages', async () => {
queue.enqueue({ type: 'system', subtype: 'init', uuid: '1' })
queue.enqueue({ type: 'assistant', uuid: '2' })
await queue.flush()

expect(sent).toHaveLength(1)
expect(sent[0]).toMatchObject({ type: 'assistant' })
})

it('filters out isMeta messages', async () => {
queue.enqueue({ type: 'user', isMeta: true, uuid: '1' })
queue.enqueue({ type: 'assistant', uuid: '2' })
await queue.flush()

expect(sent).toHaveLength(1)
expect(sent[0]).toMatchObject({ type: 'assistant' })
})

it('filters out isCompactSummary messages', async () => {
queue.enqueue({ type: 'assistant', isCompactSummary: true, uuid: '1' })
queue.enqueue({ type: 'user', uuid: '2' })
await queue.flush()

expect(sent).toHaveLength(1)
expect(sent[0]).toMatchObject({ type: 'user' })
})
})
2 changes: 1 addition & 1 deletion cli/src/claude/utils/OutgoingMessageQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export class OutgoingMessageQueue {

// Send if not already sent
if (!item.sent) {
if (item.logMessage.type !== 'system') {
if (item.logMessage.type !== 'system' && !item.logMessage.isMeta && !item.logMessage.isCompactSummary) {
this.sendFunction(item.logMessage);
}
item.sent = true;
Expand Down
Loading