diff --git a/apps/web/src/hooks/useAcpSlashCommands.test.tsx b/apps/web/src/hooks/useAcpSlashCommands.test.tsx index 2aef1e58e..6fa4cab51 100644 --- a/apps/web/src/hooks/useAcpSlashCommands.test.tsx +++ b/apps/web/src/hooks/useAcpSlashCommands.test.tsx @@ -79,6 +79,33 @@ describe('useAcpSlashCommands', () => { expect(container?.textContent).toBe(''); }); + it('falls back to an empty list when the response omits availableCommands', async () => { + // A live ACP session entry that hasn't populated `availableCommands` yet + // is serialized without the field entirely (JSON drops `undefined`), + // even though the response type declares it non-optional. Regression + // test for #164: this must not leave `commands` as `undefined`. + apiMocks.getCached.mockResolvedValue({ + source: 'thread', + commandsUpdatedAt: 0, + sessionMeta: { + availableModes: [], + currentModeId: null, + availableModels: [], + currentModelId: null, + configOptions: [], + selections: {}, + sessionInfo: null, + usage: null, + updatedAt: 0, + }, + }); + + await renderHarness(); + + expect(apiMocks.getCached).toHaveBeenCalledOnce(); + expect(container?.textContent).toBe(''); + }); + it('refreshes the GET cache on later slash-menu intent', async () => { apiMocks.getCached .mockResolvedValueOnce({ diff --git a/apps/web/src/hooks/useAcpSlashCommands.ts b/apps/web/src/hooks/useAcpSlashCommands.ts index 77b24262e..f53e34d2d 100644 --- a/apps/web/src/hooks/useAcpSlashCommands.ts +++ b/apps/web/src/hooks/useAcpSlashCommands.ts @@ -70,7 +70,12 @@ export function useAcpSlashCommands({ profileId, ); if (!isCurrent()) return; - setCommands(response.availableCommands); + // `availableCommands` is dropped from the JSON body entirely when the + // server's live session entry hasn't populated it yet, so `response` + // can arrive without the field despite the response type saying + // otherwise. Falling back to `[]` keeps `commands` an array so every + // downstream `.length` read stays safe. + setCommands(response.availableCommands ?? []); setError(null); lastFetchedAtRef.current = Date.now(); } catch (value) {