Files
Maven/cmd/mavend/followup.go
T
kami 388d4257ee maven: wire dialogue slot carry-over into the voice path (task 6)
The dialogue library (internal/dialogue, task 6) shipped tested but unwired.
Wire it: reactiveHandler now holds a 2-min SessionStore, and each turn fills
its missing slots from a prior same-intent, non-expired turn via InheritSlots
before acting, then records itself for the next follow-up. Single-user box →
one session slot (voiceDialogueID).

Guardrails (followUpMerge, unit-tested): only same-intent turns inherit (a new
intent is a fresh command); clarify turns and expired/nil priors never inherit;
InheritSlots fills gaps only, so a fully-slotted turn is untouched; the fact
Value (router-only) survives the dialogue.Slots round-trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 12:15:17 +04:00

56 lines
1.9 KiB
Go

package main
import (
"time"
"github.com/kami/maven/internal/dialogue"
"github.com/kami/maven/internal/router"
)
// voiceDialogueID — the single dialogue-session key. This is a single-user box
// (ponytail), so one slot suffices; a second speaker would need per-speaker ids,
// which waits on voice-print attribution (see PROGRESS multi-user deferral).
const voiceDialogueID = "voice"
// toDialogueSlots projects the router's slots onto the dialogue layer's subset
// (everything except the fact Value, which the dialogue layer doesn't carry).
func toDialogueSlots(s router.Slots) dialogue.Slots {
return dialogue.Slots{
Time: s.Time,
HasTime: s.HasTime,
Key: s.Key,
HasKey: s.HasKey,
Text: s.Text,
Fn: s.Fn,
Args: s.Args,
HasFn: s.HasFn,
}
}
// applyDialogueSlots writes inherited dialogue slots back onto router slots,
// preserving router-only fields (Value) the dialogue layer never touched.
func applyDialogueSlots(base router.Slots, d dialogue.Slots) router.Slots {
base.Time, base.HasTime = d.Time, d.HasTime
base.Key, base.HasKey = d.Key, d.HasKey
base.Text = d.Text
base.Fn, base.Args, base.HasFn = d.Fn, d.Args, d.HasFn
return base
}
// followUpMerge fills the current turn's missing slots from a prior same-intent,
// non-expired session — the multi-turn seam. A different intent is a fresh
// command, not a follow-up, so it's returned untouched; a clarify turn resolved
// nothing, so it never inherits. InheritSlots only fills gaps, so a fully-slotted
// current turn is unaffected.
func followUpMerge(prev *dialogue.Session, dec router.Decision, now time.Time) router.Decision {
if prev == nil || dec.Clarify || prev.IsExpired(now) {
return dec
}
if prev.Intent != dialogue.Intent(dec.Intent) {
return dec
}
merged := dialogue.InheritSlots(prev.Slots, toDialogueSlots(dec.Slots))
dec.Slots = applyDialogueSlots(dec.Slots, merged)
return dec
}