diff --git a/internal/capture/capture.go b/internal/capture/capture.go index 4607d67..3d1bfb1 100644 --- a/internal/capture/capture.go +++ b/internal/capture/capture.go @@ -626,6 +626,9 @@ func windowBytes(f audio.Format, window time.Duration) int64 { if bps <= 0 || f.SampleRate <= 0 || window <= 0 { return 0 } - per := int64(window.Seconds()) * bytesPerSecond(f) + // Fractional seconds count. Truncating the window to whole seconds turned + // any sub-second window into zero bytes, which the caller reads as "no + // window" and answers by handing the transcriber the entire meeting at once. + per := int64(window.Seconds() * float64(bytesPerSecond(f))) return per - per%bps } diff --git a/internal/capture/summarize.go b/internal/capture/summarize.go index 9dfa399..388a2cd 100644 --- a/internal/capture/summarize.go +++ b/internal/capture/summarize.go @@ -193,9 +193,7 @@ func ChunkText(text string, maxRunes int) []string { // Oversized sentence: emit what is buffered, then cut this one on // word boundaries. flush() - for _, piece := range splitWords(sr, maxRunes) { - out = append(out, piece) - } + out = append(out, splitWords(sr, maxRunes)...) continue } if len(cur)+len(sr) > maxRunes { diff --git a/internal/dialogue/clarify.go b/internal/dialogue/clarify.go index 474cd67..9337095 100644 --- a/internal/dialogue/clarify.go +++ b/internal/dialogue/clarify.go @@ -37,8 +37,8 @@ type PendingQuestion struct { // request rather than alone: "завтра" names a day for an hour said earlier. WhenText string Asked time.Time - TTL time.Duration - Attempts int // questions already asked + TTL time.Duration + Attempts int // questions already asked // MaxAttempts caps Attempts. 0 ⇒ DefaultMaxAttempts. MaxAttempts int } @@ -104,11 +104,14 @@ type ClarifyStore struct { // reply can carry. const MaxStackDepth = 2 +// DefaultClarifyTTL — how long a parked question stays his answer to give. +// Short, like confirmTTL in voice.go: a clarifying question is a same-breath +// gesture, and a stale one should not eat a later utterance. +const DefaultClarifyTTL = 90 * time.Second + func NewClarifyStore(defaultTTL time.Duration) *ClarifyStore { if defaultTTL <= 0 { - // Short, like confirmTTL in voice.go: a clarifying question is a - // same-breath gesture, a stale one should not eat a later utterance. - defaultTTL = 90 * time.Second + defaultTTL = DefaultClarifyTTL } return &ClarifyStore{ stacks: make(map[string][]*PendingQuestion), @@ -151,9 +154,15 @@ func (s *ClarifyStore) Push(id string, q *PendingQuestion) *PendingQuestion { return dropped } -// Peek returns the live question on top, or nil when there is none. Expired -// entries below it are left alone: TakeExpired is what reports those, and -// dropping one here would be the silent death this store is careful about. +// Peek returns the live question on top, or nil when there is none. An expired +// top takes the whole stack with it, exactly as Pop does: the clock that killed +// it has been running for everything underneath too. +// +// That drop is silent, which is the death this store is otherwise careful +// about, so TakeExpired has to run BEFORE Peek on a turn — it is what counts the +// dropped questions and tells him they are gone. cmd/mavend/voice.go calls +// clarifyExpiredNotice first for that reason, and reordering the two makes the +// notice unreachable rather than wrong. func (s *ClarifyStore) Peek(id string, now time.Time) *PendingQuestion { s.mu.RLock() stack := s.stacks[id] diff --git a/internal/dialogue/session.go b/internal/dialogue/session.go index d71cbf1..32c9051 100644 --- a/internal/dialogue/session.go +++ b/internal/dialogue/session.go @@ -87,9 +87,14 @@ type SessionStore struct { persist SessionPersister // may be nil: memory only (tests, no-store paths) } +// DefaultSessionTTL — how long a turn stays available to inherit from. Longer +// than DefaultClarifyTTL because this is not a question waiting on an answer: +// it is the last thing said, and a follow-up may land after a real pause. +const DefaultSessionTTL = 2 * time.Minute + func NewSessionStore(defaultTTL time.Duration) *SessionStore { if defaultTTL <= 0 { - defaultTTL = 2 * time.Minute + defaultTTL = DefaultSessionTTL } return &SessionStore{ sessions: make(map[string]*Session),