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
352 changes: 306 additions & 46 deletions apps/desktop/src/plugins/hermes-bots/plugin.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -1901,6 +1901,7 @@ test('older backends without pending_clarify never mirror a question', () => {

test('answerGroupClarify routes clarify.respond and clears the mirror', async () => {
const gc = load(() => '(pass)')
gc.updateGroupChat('Core', room => room)
const member = { name: 'research', title: '' }

gc.syncGroupClarify('Core', member, { session_id: 'runtime-research', pending_clarify: CLARIFY_PAYLOAD })
Expand All @@ -1914,6 +1915,7 @@ test('answerGroupClarify routes clarify.respond and clears the mirror', async ()

test('answerGroupClarify sends one respond per batch question, in order', async () => {
const gc = load(() => '(pass)')
gc.updateGroupChat('Core', room => room)
const member = { name: 'research', title: '' }
const batch = {
request_id: 'req-batch-1',
Expand Down Expand Up @@ -2023,6 +2025,7 @@ test('an approval without a server choice set falls back to once/deny', () => {

test('answerGroupClarify routes approvals through approval.respond with session + choice', async () => {
const gc = load(() => '(pass)')
gc.updateGroupChat('Core', room => room)
const member = { name: 'research', title: '' }

gc.syncGroupClarify('Core', member, { session_id: 'rt-research-1', pending_approval: APPROVAL_PAYLOAD })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,10 @@ test('an ordinary newer-send epoch bump WITHOUT a hold does not abandon the poll

test('Stop button: workspace renders it while the room is running and wires it to stopGroupThread', () => {
const workspace = pluginSource.slice(pluginSource.indexOf('function GroupChatWorkspace'))
// Visible while a round is running…
assert.match(workspace, /room\.running \|\| roomClarifies\.length \|\| room\.turns\?\.length\)\s*\?\s*jsx\('button'/, 'Stop button is gated on room.running')
assert.match(workspace, /groupRoomCanStop\(room, roomClarifies\)\s*\?\s*jsx\('button'/, 'Stop button uses the shared room custody predicate')
const predicate = pluginSource.slice(pluginSource.indexOf('function groupRoomCanStop'), pluginSource.indexOf('function captureGroupDrive'))
assert.match(predicate, /room\?\.running \|\| clarifies\.length \|\| room\?\.turns\?\.length/, 'running work, question cards and pending turns expose Stop')
assert.match(predicate, /Object\.values\(room\?\.stranded \|\| \{\}\)\.some\(marker => groupTurnDeliveryKey\(marker\?\.delivery\)\)/, 'durable accepted custody also exposes Stop')
// …and wired to the real primitive, not a per-member interrupt spray.
assert.match(workspace, /stopGroupThread\(/, 'button calls the stopGroupThread primitive')
assert.doesNotMatch(workspace, /stopAllBots/, 'the #94570 interrupt-only shell was rewired, not kept')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { registerHooks } from 'node:module'

// Import the whole real plugin with inert renderer dependencies. The loader
// exposes its existing private engine seam; tests make no source-text assertions.
// Import the unmodified shipped plugin; mock only its explicit dependencies.
const fixtures = new Map()
globalThis.__groupTurnTestFixtures = fixtures
let sequence = 0
Expand Down Expand Up @@ -37,25 +36,6 @@ registerHooks({
}
}
return nextResolve(specifier, context)
},
load(url, context, nextLoad) {
const result = nextLoad(url, context)
const target = new URL(url)
if (target.pathname === pluginURL.pathname && target.searchParams.has('groupTurnFixture')) {
return {
...result,
source: `const { Date, setTimeout, clearTimeout } = globalThis.__groupTurnTestFixtures.get(${JSON.stringify(target.searchParams.get('groupTurnFixture'))});\nconst fixturePorts = globalThis.__groupTurnTestFixtures.get(${JSON.stringify(target.searchParams.get('groupTurnFixture'))});\n const document = fixturePorts.document ?? globalThis.document;\n const setInterval = fixturePorts.setInterval ?? globalThis.setInterval;\n const clearInterval = fixturePorts.clearInterval ?? globalThis.clearInterval;\n${result.source}\nexport { runGroupChatMemberTurn, runGroupChatRounds,
harvestStrandedGroupReply, stopGroupChatServerSync, currentGroupActivity, groupActivityLabel,
$groupChats, $groupClarify, $botAttention, appendGroupChatEntry, syncGroupClarify, answerGroupClarify,
groupChatSyncSnapshot, groupChatSyncEntryKey, stopGroupThread,
mergeRemoteGroupChatSnapshotIntoRooms, durableGroupChatRooms, sendToGroupChat,
groupRoomCoordinators, groupRuntimeSessionOwners, groupMemberKey, updateGroupChat,
groupBlockedMembers, GroupBlockedNotice, CreateGroupChatDialog, createFreshGroupChat,
groupComposerDraftKey, groupComposerDraftSnapshot, updateGroupComposerDraft };\n
export const groupRecoveryTestAPI = {\n createFreshGroupChat: typeof createFreshGroupChat === 'function' ? createFreshGroupChat : undefined,\n GroupBlockedNotice: typeof GroupBlockedNotice === 'function' ? GroupBlockedNotice : undefined,\n groupBlockedMembers: typeof groupBlockedMembers === 'function' ? groupBlockedMembers : undefined,\n updateGroupComposerDraft, GroupChatWorkspace, CreateGroupChatDialog, groupComposerDraftSnapshot, groupComposerDraftKey };\n export function bindGroupTurnTestStorage(storage) { pluginCtx = { storage }; }\n`
}
}
return result
}
})

Expand All @@ -65,7 +45,12 @@ export async function importGroupTurnPlugin(fixture) {
const url = new URL(pluginURL)
url.searchParams.set('groupTurnFixture', id)
try {
return await import(url.href)
const module = await import(url.href)
const runtime = module.default.groupTurnRuntime
runtime.bindGroupTurnPorts(fixture)
return { ...module, ...runtime,
bindGroupTurnTestStorage: runtime.bindGroupTurnStorage,
groupRecoveryTestAPI: runtime }
} finally {
fixtures.delete(id)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@ afterEach(() => {
gatewayState.set('closed')
vi.clearAllMocks()
vi.useRealTimers()
plugin.groupTurnRuntime.bindGroupTurnPorts(globalThis)
})

describe('Bot Mode hidden-session reconciliation lifecycle', () => {
it('uses persisted REST on load/reconnect and stops with plugin disposal', async () => {
vi.useFakeTimers()
plugin.groupTurnRuntime.bindGroupTurnPorts(globalThis)
const disposers: Array<() => void> = []

plugin.register(createPluginContext(plugin.id, dispose => disposers.push(dispose)))
Expand Down
Loading
Loading