Skip to content
Closed
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
5 changes: 2 additions & 3 deletions apps/desktop/src/plugins/hermes-bots/group-round-members.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { recordGroupActivity } from './group-activity'
import {
$groupChats,
appendGroupChatEntry,
GROUP_CHAT_HISTORY_LIMIT,
GROUP_CHAT_MAX_CONTINUATIONS,
GROUP_CHAT_MAX_MESSAGES,
groupThreadOf,
Expand All @@ -12,7 +11,7 @@ import {
} from './group-chat'
import type { GroupChatRoom } from './group-chat'
import { groupMemberKey } from './group-membership'
import { buildGroupChatTurnPrompt, formatGroupChatLine } from './group-round-prompt'
import { buildGroupChatTurnPrompt, formatGroupDeltaLines } from './group-round-prompt'
import { isGroupPassText, runGroupChatMemberTurn } from './group-turns'
import type { Attachment, GroupMember, GroupMessage } from './types'

Expand Down Expand Up @@ -94,7 +93,7 @@ function prepareGroupRoundMember(context: GroupRoundMemberContext, member: Group
groupName: context.group,
members,
viewer: member,
deltaLines: delta.slice(-GROUP_CHAT_HISTORY_LIMIT).map((e: GroupMessage) => formatGroupChatLine(e, member, context.group))
deltaLines: formatGroupDeltaLines(delta, member, context.group)
})

// Images riding this delta (user attachments — member entries don't
Expand Down
19 changes: 18 additions & 1 deletion apps/desktop/src/plugins/hermes-bots/group-round-prompt.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { botMentionTag } from './data'
import { groupSpeakerLabel } from './group-chat'
import { GROUP_CHAT_HISTORY_LIMIT, groupSpeakerLabel } from './group-chat'
import { groupMemberKey } from './group-membership'
import type { GroupMember, GroupMessage, GroupMessageAuthor } from './types'

Expand Down Expand Up @@ -50,6 +50,23 @@ export function formatGroupChatLine(entry: GroupMessage, viewer: GroupChatLineVi
return `${groupSpeakerLabel(entry.from.name, group)}${suffix}${source}: ${relabelMemberControlFrames(entry.text)}${attached}`
}

/** #114341: a member's turn renders only the last GROUP_CHAT_HISTORY_LIMIT
* delta lines while the watermark commit advances past the whole tail, so
* the head of an over-long delta is never delivered on any later turn
* either. Mark the cut — without it a member has no way to know its view
* of the room is partial (typically missing the very user instruction
* that started the exchange). */
export function formatGroupDeltaLines(delta: GroupMessage[], viewer: GroupChatLineViewer, group?: null | string) {
const omitted = delta.length - GROUP_CHAT_HISTORY_LIMIT
const lines = delta.slice(-GROUP_CHAT_HISTORY_LIMIT).map(entry => formatGroupChatLine(entry, viewer, group))

if (omitted > 0) {
lines.unshift(`… ${omitted} earlier room message${omitted === 1 ? '' : 's'} omitted since your last turn`)
}

return lines
}

function viewerNameOf(viewer: GroupChatLineViewer): string {
return typeof viewer === 'string' ? viewer : viewer?.name || ''
}
Expand Down
43 changes: 43 additions & 0 deletions apps/desktop/src/plugins/hermes-bots/group-rounds.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,49 @@ describe('turn prompt', () => {
expect(prompt).toMatch(/never thin out real content/i)
expect(prompt).toMatch(/Keep chatter short/i)
})

// #114341: the rendered window is capped at GROUP_CHAT_HISTORY_LIMIT while
// the watermark commit advances past the whole tail — the head of an
// over-long delta is never delivered on any later turn either, so the cut
// has to be visible in the prompt.
it('marks the head of an over-long delta as omitted', async () => {
await loadRoom()
const { formatGroupDeltaLines } = await import('./group-round-prompt')
const { GROUP_CHAT_HISTORY_LIMIT } = await import('./group-chat')

const delta: GroupMessage[] = Array.from({ length: GROUP_CHAT_HISTORY_LIMIT + 6 }, (_, i) => ({
at: i,
from: { kind: 'user', name: 'User' },
id: `m${i}`,
text: `line ${i}`
}))

const lines = formatGroupDeltaLines(delta, 'builder', null)

expect(lines).toHaveLength(GROUP_CHAT_HISTORY_LIMIT + 1)
expect(lines[0]).toBe('… 6 earlier room messages omitted since your last turn')
expect(lines[1]).toContain('line 6')
expect(lines.at(-1)).toContain(`line ${GROUP_CHAT_HISTORY_LIMIT + 5}`)
})

it('adds no omission marker when the delta fits the window', async () => {
await loadRoom()
const { formatGroupDeltaLines } = await import('./group-round-prompt')
const { GROUP_CHAT_HISTORY_LIMIT } = await import('./group-chat')

const delta: GroupMessage[] = Array.from({ length: GROUP_CHAT_HISTORY_LIMIT }, (_, i) => ({
at: i,
from: { kind: 'user', name: 'User' },
id: `m${i}`,
text: `line ${i}`
}))

const lines = formatGroupDeltaLines(delta, 'builder', null)

expect(lines).toHaveLength(GROUP_CHAT_HISTORY_LIMIT)
expect(lines.every(line => !line.includes('omitted'))).toBe(true)
expect(lines[0]).toContain('line 0')
})
})

describe('attachments', () => {
Expand Down