mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-08-04 16:22:48 +08:00
fix(live-atc): stop ATC from repeating/looping and hanging on silent frequencies
- Neutralize the local engine's autonomous auto-advance (evaluateAutoTransitions/ evaluateSimpleAutoFlow) — the Python backend now drives state exclusively via moveToSilent; the old walker could self-answer pilot states and race the backend, producing loops. - Dedupe applyBackendDecision's ATC log entry + TTS: moveToSilent no longer logs say_tpl for backend-driven auto-advanced states (suppressSay), and a lastAppliedSay guard drops a repeated decision from overlapping sources (transmit reply, telemetry tick, silence timeout). - Cap consecutive silence-timeout re-fires on the same state at 2 instead of re-arming forever. - Pause telemetry forwarding while a pilot transmission is in flight, and guard against overlapping telemetry POSTs. - Add request timeouts to TTS (20s) and backend transmit/telemetry/timeout (30s)/createSession (60s) calls so a hung request can no longer freeze the whole session. Verified live against the Python backend: a full clearance→taxi chain now logs each ATC line exactly once instead of 2-3x.
This commit is contained in:
@@ -204,6 +204,17 @@ export default function useCommunicationsEngine() {
|
||||
const ready = ref(false)
|
||||
const lastDecisionTrace = ref<LLMDecisionTrace | null>(null)
|
||||
|
||||
// The pre-backend engine used to walk states on its own (single-eligible-
|
||||
// transition auto-advance, timed auto_transitions). /live-atc now gets state
|
||||
// changes exclusively from the Python backend via moveToSilent(); this
|
||||
// walker is neutralized there (never opted in) so it can't race the backend
|
||||
// or self-answer pilot states. Kept (not deleted) for the flow editor/debug
|
||||
// tooling that may still want it.
|
||||
const autonomousAutoAdvanceEnabled = ref(false)
|
||||
function setAutonomousAutoAdvance(enabled: boolean) {
|
||||
autonomousAutoAdvanceEnabled.value = enabled
|
||||
}
|
||||
|
||||
const flowSnapshots = reactive<Record<string, FlowSnapshot>>({})
|
||||
|
||||
const states = computed<Record<string, RuntimeDecisionState>>(() => tree.value?.states ?? {})
|
||||
@@ -1068,8 +1079,11 @@ export default function useCommunicationsEngine() {
|
||||
|
||||
if (!nextId || !states.value[nextId]) break
|
||||
|
||||
// Advance to the next state (moveTo handles logging, actions, handoffs)
|
||||
moveTo(nextId)
|
||||
// Advance to the next state. moveToSilent handles logging, actions and
|
||||
// handoffs but — unlike moveTo — does not itself schedule further
|
||||
// auto-transitions; this walker's own while-loop is the only driver,
|
||||
// so the backend (which owns state after the initial walk) never races it.
|
||||
moveToSilent(nextId)
|
||||
}
|
||||
|
||||
return messages
|
||||
@@ -1162,6 +1176,7 @@ export default function useCommunicationsEngine() {
|
||||
let pendingSimpleAutoTransition: { from: string; to: string } | null = null
|
||||
|
||||
function evaluateSimpleAutoFlow(loopGuard = 0) {
|
||||
if (!autonomousAutoAdvanceEnabled.value) return
|
||||
if (!ready.value || loopGuard > 20) return
|
||||
if (pendingSimpleAutoTransition?.from === currentStateId.value) {
|
||||
return
|
||||
@@ -1217,6 +1232,7 @@ export default function useCommunicationsEngine() {
|
||||
}
|
||||
|
||||
function evaluateAutoTransitions(loopGuard = 0) {
|
||||
if (!autonomousAutoAdvanceEnabled.value) return
|
||||
if (!ready.value || loopGuard > 8) return
|
||||
const state = currentState.value
|
||||
if (!state) return
|
||||
@@ -1301,7 +1317,7 @@ export default function useCommunicationsEngine() {
|
||||
// Like moveTo but does NOT schedule auto-transitions — used when the backend
|
||||
// is driving state and we only need to sync the local cursor + side-effects
|
||||
// (actions, handoffs, communication log) without the engine trying to advance further.
|
||||
function moveToSilent(stateId: string) {
|
||||
function moveToSilent(stateId: string, opts: { suppressSay?: boolean } = {}) {
|
||||
ensureTree()
|
||||
if (!states.value[stateId]) {
|
||||
console.warn(`[Engine] Unknown state for silent move: ${stateId}`)
|
||||
@@ -1332,7 +1348,10 @@ export default function useCommunicationsEngine() {
|
||||
}
|
||||
}
|
||||
|
||||
const sayTplSilent = stateSayTpl(s)
|
||||
// suppressSay: the caller (applyBackendDecision) already appends the
|
||||
// backend-rendered say to the log itself — logging it again here would
|
||||
// duplicate the ATC line for every auto-advanced/say-carrying state.
|
||||
const sayTplSilent = opts.suppressSay ? null : stateSayTpl(s)
|
||||
if (sayTplSilent) {
|
||||
speak(s.role, sayTplSilent, s.id!)
|
||||
}
|
||||
@@ -1562,6 +1581,7 @@ export default function useCommunicationsEngine() {
|
||||
// Flow Control
|
||||
moveTo,
|
||||
moveToSilent,
|
||||
setAutonomousAutoAdvance,
|
||||
|
||||
// Utilities
|
||||
normalizeATCText,
|
||||
|
||||
Reference in New Issue
Block a user