fix(desktop): preserve bounded bot hydration budget

This commit is contained in:
Shaq Wu
2026-08-25 08:57:34 +10:00
committed by Teknium
parent 791e2ae325
commit 77b5bb39e7
4 changed files with 80 additions and 2 deletions
@@ -5634,6 +5634,12 @@ async function openStoredBotChat(owner, storedId, summary) {
const hasAuthoritativeCount =
typeof summary?.message_count === 'number' && Number.isFinite(summary.message_count)
const expectHistory = hasAuthoritativeCount ? summary.message_count > 0 : true
// Current SDKs export the Bot-specific budget. The fallback preserves
// compatibility with older hosts and isolated plugin test harnesses.
const hydrationTimeoutMs =
typeof sdk !== 'undefined' && Number.isFinite(sdk.BOT_CHAT_SESSION_HYDRATION_TIMEOUT_MS)
? sdk.BOT_CHAT_SESSION_HYDRATION_TIMEOUT_MS
: 60_000
// A profile backend that just woke up can lose the hydration-timeout race
// even though the session is fine (hermes-agent#89617) — clicking Retry
@@ -5655,6 +5661,7 @@ async function openStoredBotChat(owner, storedId, summary) {
awaitHydration: true,
expectHistory,
forceResume: true,
hydrationTimeoutMs,
keepAllProfilesScope: true,
workspaceMode: 'bots',
workspaceOwnerKey: ownerKey,
@@ -77,6 +77,11 @@ test('pref defaults OFF and persists via ctx.storage under activity-toasts', ()
assert.match(source, /storage\?\.get\?\.\('activity-toasts'\)/)
})
test('Bot Chat opens pass the exported 60-second hydration budget', () => {
assert.match(source, /sdk\.BOT_CHAT_SESSION_HYDRATION_TIMEOUT_MS/)
assert.match(source, /hydrationTimeoutMs,/)
})
test('activity in the hidden canonical Bot Chat still badges (the "6d ago" class)', () => {
// The canonical Bot Chat is hidden from session lists, so last_session
// never advances when a DM lands there — only canonical_session does.
+6 -1
View File
@@ -311,7 +311,12 @@ const $activeConnectionId = computed($connection, connection => {
return connection.mode === 'local' ? 'local' : null
})
const DEFAULT_SESSION_HYDRATION_TIMEOUT_MS = 20_000
/** Ordinary session opens fail fast when their gateway or socket is dead. */
export const DEFAULT_SESSION_HYDRATION_TIMEOUT_MS = 20_000
/** Cold Bot profiles get a larger per-attempt budget to start their backend
* and paint durable history. Bot Mode opts into one retry, so its effective
* ceiling is two bounded attempts rather than an unbounded wait. */
export const BOT_CHAT_SESSION_HYDRATION_TIMEOUT_MS = 60_000
let openSessionGeneration = 0
export interface PluginOpenSessionOptions {
+62 -1
View File
@@ -104,7 +104,9 @@ vi.mock('@/store/gateway', async () => {
}
})
const { host } = await import('./index')
const { BOT_CHAT_SESSION_HYDRATION_TIMEOUT_MS, DEFAULT_SESSION_HYDRATION_TIMEOUT_MS, host } =
await import('./index')
const { openSession: openSessionCore } = await import('@/app/open-session')
const { deleteProfile, hermesApi } = await import('@/hermes')
@@ -834,6 +836,65 @@ describe('profile-aware plugin session opens', () => {
expect($gatewaySwapTarget.get()).toBeNull()
})
it('keeps the ordinary session hydration default at 20 seconds', async () => {
vi.useFakeTimers()
try {
expect(DEFAULT_SESSION_HYDRATION_TIMEOUT_MS).toBe(20_000)
$activeGatewayProfile.set('hyoseob')
const outcome = host
.openSession('slow-bot-chat', {
profile: 'hyoseob',
awaitHydration: true,
expectHistory: true
})
.then(
() => 'resolved',
error => String(error)
)
await vi.advanceTimersByTimeAsync(19_999)
expect(setResumeExhaustedSessionId).not.toHaveBeenCalled()
await vi.advanceTimersByTimeAsync(1)
expect(await outcome).toMatch(/timed out loading/i)
expect(setResumeExhaustedSessionId).toHaveBeenCalledWith('slow-bot-chat')
} finally {
vi.useRealTimers()
}
})
it('honors the exported 60-second Bot Chat hydration override', async () => {
vi.useFakeTimers()
try {
expect(BOT_CHAT_SESSION_HYDRATION_TIMEOUT_MS).toBe(60_000)
$activeGatewayProfile.set('hyoseob')
const outcome = host
.openSession('slow-bot-chat', {
profile: 'hyoseob',
awaitHydration: true,
expectHistory: true,
hydrationTimeoutMs: BOT_CHAT_SESSION_HYDRATION_TIMEOUT_MS
})
.then(
() => 'resolved',
error => String(error)
)
await vi.advanceTimersByTimeAsync(BOT_CHAT_SESSION_HYDRATION_TIMEOUT_MS - 1)
expect(setResumeExhaustedSessionId).not.toHaveBeenCalled()
await vi.advanceTimersByTimeAsync(1)
expect(await outcome).toMatch(/timed out loading/i)
expect(setResumeExhaustedSessionId).toHaveBeenCalledWith('slow-bot-chat')
} finally {
vi.useRealTimers()
}
})
it('retries a Bot Chat hydration timeout once without ever arming the Retry surface (#89617)', async () => {
$activeGatewayProfile.set('hyoseob')