From 17ef9761405549cb15fe31bfd47bdbf5a290f6f8 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Mon, 8 Jun 2026 00:24:25 +0100 Subject: [PATCH] Expose Assistant fixture commands --- .../v6/internal/subsystems/ai-runtime.md | 8 ++ .../v6/internal/subsystems/api-contracts.md | 4 + .../src/api/__tests__/aiChat.test.ts | 18 +++- .../src/api/aiChatDevStreamFixture.ts | 9 +- .../AI/Chat/SlashCommandAutocomplete.tsx | 7 +- .../AI/Chat/__tests__/AIChat.test.tsx | 50 +++++++--- .../AssistantCommandHelpDialog.test.tsx | 11 +++ .../SlashCommandAutocomplete.test.tsx | 32 ++++-- .../__tests__/assistantSlashCommands.test.ts | 41 +++++++- .../AI/Chat/assistantSlashCommands.ts | 97 ++++++++++++++++--- .../src/components/AI/Chat/index.tsx | 49 ++++++++++ .../release_control/subsystem_lookup_test.py | 2 +- 12 files changed, 283 insertions(+), 45 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/ai-runtime.md b/docs/release-control/v6/internal/subsystems/ai-runtime.md index e7ad1fbd3..4090f976b 100644 --- a/docs/release-control/v6/internal/subsystems/ai-runtime.md +++ b/docs/release-control/v6/internal/subsystems/ai-runtime.md @@ -697,6 +697,14 @@ timers when the stream already has a newer workflow/tool state. the real `provider_retry` workflow state, including attempt metadata and `retry_after_ms`, so browser proof of retry countdown behavior does not depend on external provider availability or API spend. + Dev/test fixture prompts are also part of the Assistant command-discovery + surface: slash autocomplete and Assistant command help must expose an + insertable `/fixture` command in development/test builds, search it by + canonical fixture names such as `provider-retry`, and submit completed + `/fixture ` prompts through the normal chat send path so + `maybeRunAIChatDevStreamFixture` remains the single local execution boundary. + Production command surfaces must omit that dev command instead of advertising + a fixture mode that the runtime will not intercept. The local `stream-idle` fixture must exercise the real `stream_idle` workflow state after selected-provider startup so browser proof of visible idle liveness does not depend on making a real provider pause on demand. diff --git a/docs/release-control/v6/internal/subsystems/api-contracts.md b/docs/release-control/v6/internal/subsystems/api-contracts.md index 64656cab7..c04d8c555 100644 --- a/docs/release-control/v6/internal/subsystems/api-contracts.md +++ b/docs/release-control/v6/internal/subsystems/api-contracts.md @@ -134,6 +134,10 @@ Assistant local stream fixtures are part of the same frontend API contract: `frontend-modern/src/api/aiChatDevStreamFixture.ts` may short-circuit only explicit `/fixture ...` prompts in development or test mode, must emit the same typed stream event sequence as live chat, and must never open a provider request. +That helper also owns the exported fixture-name and alias catalog consumed by +Assistant command discovery; frontend command surfaces may search and insert +`/fixture` from those exports, but they must not duplicate the fixture registry +or advertise fixture commands in production. Fixtures used for visible stream proof must pace status/tool/content events enough for the browser to paint the intermediate state, including keeping the `/fixture tool-burst` running tool row visible before the matching `tool_end`; diff --git a/frontend-modern/src/api/__tests__/aiChat.test.ts b/frontend-modern/src/api/__tests__/aiChat.test.ts index f894d58cd..26d0337c6 100644 --- a/frontend-modern/src/api/__tests__/aiChat.test.ts +++ b/frontend-modern/src/api/__tests__/aiChat.test.ts @@ -16,7 +16,11 @@ vi.mock('@/utils/logger', () => ({ })); import { AIChatAPI, createAIChatStreamPaintCheckpointPredicate } from '@/api/aiChat'; -import { maybeRunAIChatDevStreamFixture } from '@/api/aiChatDevStreamFixture'; +import { + AI_CHAT_DEV_STREAM_FIXTURE_ALIAS_NAMES, + AI_CHAT_DEV_STREAM_FIXTURE_NAMES, + maybeRunAIChatDevStreamFixture, +} from '@/api/aiChatDevStreamFixture'; import { apiFetch, apiFetchJSON } from '@/utils/apiClient'; import { logger } from '@/utils/logger'; @@ -302,10 +306,7 @@ describe('AIChatAPI', () => { expect(onEvent.mock.calls.map(([event]) => event.type)).toEqual(['session']); await vi.advanceTimersByTimeAsync(25); - expect(onEvent.mock.calls.map(([event]) => event.type)).toEqual([ - 'session', - 'workflow_state', - ]); + expect(onEvent.mock.calls.map(([event]) => event.type)).toEqual(['session', 'workflow_state']); await vi.advanceTimersByTimeAsync(25); expect(onEvent.mock.calls.map(([event]) => event.type)).toEqual([ @@ -552,6 +553,13 @@ describe('AIChatAPI', () => { }); }); + it('exports fixture names and aliases for command discovery', () => { + expect(AI_CHAT_DEV_STREAM_FIXTURE_NAMES).toContain('provider-retry'); + expect(AI_CHAT_DEV_STREAM_FIXTURE_NAMES).toContain('send-hold'); + expect(AI_CHAT_DEV_STREAM_FIXTURE_NAMES).not.toContain('/fixture provider-retry'); + expect(AI_CHAT_DEV_STREAM_FIXTURE_ALIAS_NAMES).toEqual(['burst-tool', 'queued-follow-up']); + }); + it('runs the pending-tool dev stream fixture without opening a provider request', async () => { const onEvent = vi.fn(); diff --git a/frontend-modern/src/api/aiChatDevStreamFixture.ts b/frontend-modern/src/api/aiChatDevStreamFixture.ts index 9b58aceb1..eb1d886d7 100644 --- a/frontend-modern/src/api/aiChatDevStreamFixture.ts +++ b/frontend-modern/src/api/aiChatDevStreamFixture.ts @@ -26,9 +26,14 @@ const AI_CHAT_DEV_STREAM_FIXTURE_ALIASES: Record< '/fixture queued-follow-up': '/fixture queue-drain', }; -const availableFixtureNames = AI_CHAT_DEV_STREAM_FIXTURE_PROMPTS.map((prompt) => +export const AI_CHAT_DEV_STREAM_FIXTURE_NAMES = AI_CHAT_DEV_STREAM_FIXTURE_PROMPTS.map((prompt) => prompt.replace('/fixture ', ''), ); +export const AI_CHAT_DEV_STREAM_FIXTURE_ALIAS_NAMES = Object.keys( + AI_CHAT_DEV_STREAM_FIXTURE_ALIASES, +).map((prompt) => prompt.replace('/fixture ', '')); + +const availableFixtureNames = AI_CHAT_DEV_STREAM_FIXTURE_NAMES; const DEFAULT_DEV_FIXTURE_STEP_DELAY_MS = 140; const TEST_FIXTURE_STEP_DELAY_MS = 0; @@ -656,7 +661,7 @@ const buildLongOutputFixtureEvents = (model?: string): AIChatStreamEvent[] => [ { type: 'content', data: { - text: 'The long-output fixture kept a bounded plain-text tool preview visible while preserving the full output in details.', + text: 'The long-output fixture kept the full tool output available in details without flooding the transcript.', }, }, { diff --git a/frontend-modern/src/components/AI/Chat/SlashCommandAutocomplete.tsx b/frontend-modern/src/components/AI/Chat/SlashCommandAutocomplete.tsx index cffba9326..101ce0673 100644 --- a/frontend-modern/src/components/AI/Chat/SlashCommandAutocomplete.tsx +++ b/frontend-modern/src/components/AI/Chat/SlashCommandAutocomplete.tsx @@ -3,6 +3,7 @@ import ActivityIcon from 'lucide-solid/icons/activity'; import ClockIcon from 'lucide-solid/icons/clock'; import CopyIcon from 'lucide-solid/icons/copy'; import DownloadIcon from 'lucide-solid/icons/download'; +import FlaskConicalIcon from 'lucide-solid/icons/flask-conical'; import GitForkIcon from 'lucide-solid/icons/git-fork'; import KeyRoundIcon from 'lucide-solid/icons/key-round'; import Minimize2Icon from 'lucide-solid/icons/minimize-2'; @@ -47,6 +48,8 @@ export const AssistantSlashCommandIcon = (props: { action: AssistantSlashCommand return