a side query suspends the flow instead of ending it (V-561)

V-560 classified the side query correctly and then dropped the request behind
it, saying "Прошлую просьбу отпускаю." The owner rejected that on sight: he
asked about the weather in the middle of setting a reminder, and being told the
reminder was let go reports a loss he did not ask for. It had not been lost —
there was simply nowhere to put it.

There is now. ClarifyStore grew a bounded stack in V-559 and nothing called
Push; this is the caller it was built for. A side query leaves the question
parked exactly as it is, the words are answered as themselves, and the question
comes back on the end of the same reply — one utterance, two acts.

The resumed question is not the first one again. "Когда?" works in the same
breath as "напомни позвонить маме" and does not work after a turn about Rome, so
the deck has a second form per slot that names the request: "На какое время
поставить напоминание?". No attempt is spent, because he answered the side query
and not the parked question, and charging a retry for a turn that was never an
answer is the V-554 shape.

clarifyDropped stays for new_request and cancel, where something really does
die. Two things can now die at once, so TakeExpired reports a count instead of a
bool and the expiry notice has a plural wording — "прошлую просьбу" when two
were lost would be a lie about the number.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-06 01:23:19 +04:00
parent 70b32af8a7
commit c8a5b5416e
5 changed files with 236 additions and 32 deletions
+32
View File
@@ -50,6 +50,38 @@ var clarifyQuestionVariants = map[dialogue.Slot][]string{
},
}
// clarifyResumedVariants — the wording for a question coming BACK after a side
// query took the turn away from it (Vikunja #561).
//
// It is not the first question again. "Когда?" works in the same breath as
// "напомни позвонить маме", because the thing it is about was just said. After
// a turn about the weather in Rome it does not: he has been thinking about
// something else, and a bare "Когда?" asks him to remember what she is holding.
// So the resumed form names the request — "напоминание", "заметка" — and the
// first form stays short.
//
// One wording per slot, not a rotation and not an attempt ladder. A resume does
// not spend an attempt (that is the point of suspending rather than re-asking),
// so there is no attempt number to vary on, and this line is heard once per
// interruption rather than repeatedly.
//
// Persona holds: infinitive, so no gender agreement, "ты" nowhere needed, no pet
// names.
var clarifyResumedVariants = map[dialogue.Slot]string{
dialogue.SlotTime: "На какое время поставить напоминание?",
dialogue.SlotText: "Так о чём напомнить?",
dialogue.SlotKey: "Так что записать?",
dialogue.SlotFn: "Так какое действие выполнить?",
}
// clarifyResumedFor gives the resumed wording for a slot. ("", false) when the
// slot has none, and the caller then resumes nothing rather than inventing a
// question — a flow it cannot re-ask is one it must not claim to be holding.
func clarifyResumedFor(slot dialogue.Slot) (string, bool) {
q, ok := clarifyResumedVariants[slot]
return q, ok
}
// actNotRecognized is what an act she cannot run gets (Vikunja #556).
//
// The deck used to ask "Что сделать?" instead. That question has no answer he