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
26 changes: 26 additions & 0 deletions apps/desktop/src/plugins/hermes-bots/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -4683,6 +4683,32 @@ function botBackendProfileScope(route, fallbackProfile = 'default') {

/** Gateway RPC on the bot's OWN source. Source-scoped rows always use the
* explicit descriptor, including a registered local source. */
/** The (connection, profile) route of the ACTIVE bot, or null when the
* active gateway is the plain local/legacy one. Restored from 9b7ab9d65a:
* the #90006 reconciliation merge (0404020f7b) dropped this definition
* while keeping both call sites, so every roster fetch threw ReferenceError
* and useRoster's retry masked it as a permanent "Waking up ..." spinner. */
async function activeBotRoute() {
if (typeof host.profileRoutes !== 'function') {
return null
}

const profile = String(host.state.profile?.get?.() || 'default').trim() || 'default'
const connectionId = String(
host.state.connectionId?.get?.() ||
(typeof host.activeConnectionId === 'function' ? host.activeConnectionId() : '') ||
'local'
).trim() || 'local'
const routes = await host.profileRoutes()
const route = routes.find(candidate => candidate?.connectionId === connectionId && candidate?.profile === profile)

if (!route && typeof host.agents === 'function') {
throw new Error(`No route for active bot ${connectionId}::${profile}`)
}

return route || null
}

async function requestForBot(bot, method, params = {}) {
const route = botConnectionRoute(bot)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
import vm from 'node:vm'

const source = readFileSync(new URL('../plugin.js', import.meta.url), 'utf8')

// Extract the REAL function. This extraction is itself the regression guard:
// the #90006 reconciliation merge (0404020f7b) dropped this definition while
// keeping both call sites (useRoster's queryFn and sweepBotProfileSessions),
// so every roster fetch threw ReferenceError and react-query's retry masked
// it as a permanent "Waking up ..." spinner — the Bots tab never populated.
// Nothing else can catch that class of loss here: plugin.js is outside the
// eslint file set (*.{ts,tsx} only), outside tsc, and the vm tests exercise
// slices rather than the whole module.
function loadActiveBotRoute(host) {
const start = source.indexOf('async function activeBotRoute()')
assert.notEqual(start, -1, 'activeBotRoute must be DEFINED in plugin.js, not just called')
const end = source.indexOf('\n}', start) + 2
const context = { host }
vm.createContext(context)
vm.runInContext(`${source.slice(start, end)}\nglobalThis.__fn = activeBotRoute`, context)

return context.__fn
}

function hostWith({ profile, connectionId, routes, agents }) {
return {
profileRoutes: routes ? async () => routes : undefined,
agents,
activeConnectionId: () => connectionId,
state: {
profile: { get: () => profile },
connectionId: { get: () => connectionId }
}
}
}

test('resolves the active (connection, profile) pair to its route', async () => {
const route = { connectionId: 'mini', profile: 'researcher' }
const fn = loadActiveBotRoute(hostWith({ profile: 'researcher', connectionId: 'mini', routes: [route] }))

assert.equal(await fn(), route)
})

test('legacy host without profileRoutes resolves to null (local path)', async () => {
const fn = loadActiveBotRoute({ state: {} })

assert.equal(await fn(), null)
})

test('a multi-source host with no route for the active bot fails loudly', async () => {
const fn = loadActiveBotRoute(
hostWith({ profile: 'researcher', connectionId: 'mini', routes: [], agents: async () => [] })
)

await assert.rejects(fn(), /No route for active bot mini::researcher/)
})

test('a single-source host with no matching route falls back to null', async () => {
const fn = loadActiveBotRoute(hostWith({ profile: 'default', connectionId: 'local', routes: [] }))

assert.equal(await fn(), null)
})
Loading