diff --git a/AGENTS.md b/AGENTS.md index ce1e4dc..9181129 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,8 +10,8 @@ ## Key Files - `/shared/utils/communicationsEngine.ts` — Core state machine composable (used by `/pm` live ATC). Drives local cursor and TTS; Python backend owns the authoritative state. - `/app/composables/useRadioBackend.ts` — Typed wrapper around the Python backend REST API (`createSession`, `transmit`, `deleteSession`, `fetchFlows`) -- `/server/utils/openai.ts` — Legacy LLM decision router (`routeDecision()`). No longer called by `/pm`; may still be used by other routes. -- `/server/services/decisionFlowService.ts` — Builds runtime decision trees from MongoDB (used by Nuxt `/api/decision-flows/runtime`; `/pm` now fetches directly from the Python backend) +- `/server/utils/openai.ts` — `getOpenAIClient()` only (TTS via `/api/atc/say`, STT via `/api/atc/ptt`). The old `routeDecision()` LLM router has been removed; routing lives in the Python backend. +- `/server/services/decisionFlowService.ts` — Builds/edits MongoDB-backed decision trees for the flow editor (`/server/api/editor/flows*`). There is no Nuxt `/api/decision-flows/runtime` route — `/pm` fetches the runtime flow directly from the Python backend. - `/app/pages/pm.vue` — Live ATC page (speech-to-text, PTT, text input) - `/app/pages/classroom.vue` — Classroom learning mode (separate system, does NOT use communicationsEngine) diff --git a/CLAUDE.md b/CLAUDE.md index 233beab..e02e9c4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,26 +5,59 @@ - **H3 server** handlers in `/server` - **Shared types/utils** in `/shared` - MongoDB models in `/server/models` +- **Python backend** — a separate repo, `OpenSquawk-LiveATC-api`, owns the authoritative + Live ATC (`/pm`) session state and routing decisions. It runs as its own service + (default `http://127.0.0.1:8000`). This Nuxt app owns STT, TTS, client audio, auth, + the flow editor, and account/admin features. + +> The LLM decision routing that used to live in this repo (`routeDecision()`, +> `/api/llm/decide`, a Nuxt `/api/decision-flows/runtime` route) has been removed. +> Decision routing now happens in the Python backend. Don't reintroduce it here. ## Key Files -- `/shared/utils/communicationsEngine.ts` — Core state machine composable (used by `/pm` live ATC) -- `/server/utils/openai.ts` — LLM decision router (`routeDecision()`) -- `/server/services/decisionFlowService.ts` — Builds runtime decision trees from MongoDB -- `/app/pages/pm.vue` — Live ATC page (speech-to-text, PTT, text input) -- `/app/pages/classroom.vue` — Classroom learning mode (separate system, does NOT use communicationsEngine) +- `/app/composables/useRadioBackend.ts` — Typed client for the Python backend REST API + (`createSession`, `transmit`, `deleteSession`, `fetchFlows`). This is how `/pm` talks + to the decision engine. +- `/shared/utils/communicationsEngine.ts` — Local state-machine composable used by `/pm`. + It mirrors the flow for cursor tracking and TTS scheduling; the **Python backend owns the + authoritative state**. `fetchRuntimeTree()` loads the flow definition from the backend's + `/api/decision-flows/runtime`. +- `/server/services/decisionFlowService.ts` — Builds/edits MongoDB-backed decision trees. + Used **only by the flow editor** (`/server/api/editor/flows*`), not by `/pm`. +- `/server/utils/openai.ts` — `getOpenAIClient()` only. Used for TTS (`/api/atc/say`) and + STT (`/api/atc/ptt`). It is **not** a decision router anymore. +- `/app/pages/pm.vue` — Live ATC page (speech-to-text, PTT, text input). +- `/app/pages/classroom.vue` — Classroom learning mode (separate system, does NOT use + communicationsEngine). ## Live ATC Flow (/pm) -1. User inputs (PTT or text) → `handlePilotTransmission()` -2. `processPilotTransmission()` logs the pilot message -3. `buildLLMContext()` builds candidates from `nextCandidates` -4. POST `/api/llm/decide` → `routeDecision()` selects next state -5. `applyLLMDecision()` moves to next state, updates vars/flags -6. `collectAtcStatesUntilPilotTurn()` advances through ATC/system states -7. Each ATC `say_tpl` is spoken via TTS (`scheduleControllerSpeech`) +1. `startMonitoring()` → `fetchRuntimeTree('icao_atc_decision_tree', radioBackendUrl)` + loads the flow definition from the Python backend into the local engine (cursor / TTS). +2. `startMonitoring()` → `radioBackend.createSession(...)` creates the authoritative + server-side session and stores `backendSessionId`. +3. User input (PTT or text) → `handlePilotTransmission()`. + PTT audio is transcribed first via Nuxt `POST /api/atc/ptt` (Whisper). +4. `radioBackend.transmit(backendSessionId, transcript)` → the Python backend runs regex + routing, readback evaluation, side effects, and flow chaining; it returns + `next_state_id`, `controller_say_template`, `auto_advanced_states`, `flags`, + and `session_complete`. +5. `moveToSilent(stateId)` is called for each auto-advanced state and the final state — + advances the local cursor without re-triggering auto-transitions. +6. `scheduleControllerSpeech(...)` speaks the ATC reply via TTS (`/api/atc/say`). ## Decision Tree States -States have `role: 'pilot' | 'atc' | 'system'`. ATC states have `say_tpl` (what controller says). Pilot states have `utterance_tpl` (expected pilot response). Transitions: `next`, `ok_next`, `bad_next`, `timer_next`. +States have `role: 'pilot' | 'atc' | 'system'`. The local engine handles two template +schemas transparently (via `stateSayTpl()` / `stateUtteranceTpl()`): +- Legacy (MongoDB editor): `say_tpl`, `utterance_tpl`, vars as `{var}` +- Python backend YAML: `say_template`, `expected_pilot_template`, vars as `{{var}}` + +Both `{{var}}` and `{var}` are rendered by `renderTpl()`. Transition fields seen across +schemas: `next`, `ok_next`, `bad_next`, `timer_next`, `auto_transitions`. + +## Environment Variables +- `NUXT_PUBLIC_RADIO_BACKEND_URL` — URL of the Python backend (default `http://127.0.0.1:8000`) ## Commands -- `bun run dev` — dev server -- Decision trees are stored in MongoDB and fetched via `/api/decision-flows/runtime` +- `yarn dev` — dev server (Nuxt) +- `yarn test` — runs the `tsx --test` suite (`tests/`, `server/`, `shared/`) +- Python backend: see `OpenSquawk-LiveATC-api/README.md` diff --git a/shared/types/llm.ts b/shared/types/llm.ts index 43f940c..e3f9b92 100644 --- a/shared/types/llm.ts +++ b/shared/types/llm.ts @@ -1,15 +1,5 @@ import type { DecisionNodeCondition, DecisionNodeTrigger } from './decision' -export interface LLMDecisionInput { - state_id: string - state: any - candidates: Array<{ id: string; state: any; flow?: string }> - variables: Record - flags: Record - pilot_utterance: string - flow_slug?: string -} - export type FlowActivationMode = 'main' | 'parallel' | 'linear' export interface FlowActivationInstruction { @@ -17,14 +7,6 @@ export interface FlowActivationInstruction { mode?: FlowActivationMode } -export interface ActiveNodeSummary { - flow: string - state: string - role?: string - say_tpl?: string - controller_say_tpl?: string -} - export interface CandidateTraceEntry { id: string flow: string @@ -81,17 +63,6 @@ export interface DecisionCandidateTimeline { autoSelected?: CandidateTraceEntry | null } -export interface LLMDecision { - next_state: string - updates?: Record - flags?: Record - controller_say_tpl?: string - off_schema?: boolean - radio_check?: boolean - activate_flow?: string | FlowActivationInstruction - resume_previous?: boolean -} - export interface LLMDecisionTraceCall { stage: 'readback-check' | 'decision' request: Record @@ -116,10 +87,3 @@ export interface LLMDecisionTrace { reason?: string } } - -export interface LLMDecisionResult { - decision: LLMDecision - trace?: LLMDecisionTrace - active_nodes?: ActiveNodeSummary[] - pilot_intent?: string | null -}