388d4257ee
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>
56 lines
1.9 KiB
Go
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
|
|
}
|