fix(ai-vault): humanize runtime scanner errors

This commit is contained in:
Brennan Benson
2026-08-24 01:47:50 -07:00
parent cc732bbf00
commit 26b63141ac
2 changed files with 32 additions and 6 deletions
@@ -79,6 +79,14 @@ function makeDispatcher(): RpcDispatcher {
return new RpcDispatcher({ runtime, methods: AI_VAULT_METHODS })
}
function makeFailingDispatcher(error: Error): RpcDispatcher {
const runtime = {
getRuntimeId: () => 'test-runtime',
listAiVaultSessions: vi.fn().mockRejectedValue(error)
} as unknown as OrcaRuntimeService
return new RpcDispatcher({ runtime, methods: AI_VAULT_METHODS })
}
describe('aiVault.resolveSessionTitles handler', () => {
beforeEach(() => {
resolveAiVaultSessionTitlesInWorker.mockReset()
@@ -229,6 +237,17 @@ describe('aiVault.listSessions handler + shared cache', () => {
expect(response).toMatchObject({ ok: true, result: makeResult() })
})
it('humanizes scanner supervision errors for remote clients', async () => {
const dispatcher = makeFailingDispatcher(new Error('AI Vault service restart circuit is open.'))
await expect(
dispatcher.dispatch(makeRequest('aiVault.listSessions', { limit: 500 }))
).resolves.toMatchObject({
ok: false,
error: { message: 'Session scanning paused after repeated failures. Refresh to try again.' }
})
})
it('passes only the first 64 scopePaths to the scanner when a request exceeds the cap', async () => {
const dispatcher = makeDispatcher()
const scopePaths = Array.from({ length: 65 }, (_, index) => `/p/${index}`)
+13 -6
View File
@@ -5,6 +5,7 @@ import { restampAiVaultListResult } from '../../../ai-vault/session-list-results
import { AI_VAULT_AGENTS, AI_VAULT_SCOPE_PATHS_MAX_COUNT } from '../../../../shared/ai-vault-types'
import { AI_VAULT_SESSION_TITLE_REQUEST_MAX_COUNT } from '../../../../shared/ai-vault-session-title'
import { LOCAL_EXECUTION_HOST_ID, parseExecutionHostId } from '../../../../shared/execution-host'
import { describeAiVaultScanError } from '../../../../shared/ai-vault-scan-error-message'
// Why: bound limit + scopePaths so a client cannot force an unbounded scan.
// Each scopePath is a host-local match prefix (validated/capped, never used for
@@ -83,12 +84,18 @@ export const AI_VAULT_METHODS: RpcMethod[] = [
name: 'aiVault.listSessions',
params: AiVaultListSessionsParams,
handler: async (params, { runtime }) => {
const result = await runtime.listAiVaultSessions({
limit: params.unlimited ? undefined : params.limit,
unlimited: params.unlimited,
force: params.force,
scopePaths: params.scopePaths
})
let result
try {
result = await runtime.listAiVaultSessions({
limit: params.unlimited ? undefined : params.limit,
unlimited: params.unlimited,
force: params.force,
scopePaths: params.scopePaths
})
} catch (error) {
const raw = error instanceof Error ? error.message : String(error)
throw new Error(describeAiVaultScanError(raw))
}
// Why: web clients consume this response directly (no parent-side retag),
// so sessions must come back stamped as the runtime host they addressed.
return params.executionHostId