Walk a chain of confirm resolvers instead of three copied blocks
This commit is contained in:
+85
-74
@@ -63,89 +63,100 @@ func (h *reactiveHandler) resolveConfirm(ctx context.Context, text string) (stri
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
// Check routine proposal first (newer feature; checked before tool confirm
|
||||
// so a routine confirm doesn't get eaten by a stale tool pending).
|
||||
pr := h.pendingRoutine
|
||||
if pr != nil && !h.now().After(pr.expiry) {
|
||||
for _, r := range h.confirmResolvers(ctx) {
|
||||
if !r.claim() {
|
||||
continue
|
||||
}
|
||||
// The slot is already cleared by claim(): every branch below drops the
|
||||
// pending, including the unclear one — a confirm that can't be
|
||||
// answered clearly is safer abandoned than left armed.
|
||||
switch classifyConfirm(text) {
|
||||
case confirmYes:
|
||||
h.pendingRoutine = nil
|
||||
// Only record the acceptance. The tick loop reads accepted
|
||||
// routines and nudges on their own interval. Building a reminder
|
||||
// here made a routine fire exactly once (Vikunja #366).
|
||||
if err := h.dataStore.AcceptProposedRoutine(ctx, pr.routineID, h.now()); err != nil {
|
||||
log.Printf("voice: accept proposed routine: %v", err)
|
||||
return "не получилось запомнить рутину.", true
|
||||
}
|
||||
return "буду напоминать.", true
|
||||
return r.yes(), true
|
||||
case confirmNo:
|
||||
h.pendingRoutine = nil
|
||||
if err := h.dataStore.DismissProposedRoutine(ctx, pr.routineID); err != nil {
|
||||
log.Printf("voice: dismiss proposed routine: %v", err)
|
||||
}
|
||||
return "хорошо, не буду.", true
|
||||
return r.no(), true
|
||||
default:
|
||||
// unclear: abandon the routine proposal, route normally.
|
||||
h.pendingRoutine = nil
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
// Clear expired routine if it existed.
|
||||
if pr != nil {
|
||||
h.pendingRoutine = nil
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// Check pending Hexis execution confirm. Bound to the exact capability +
|
||||
// target that was proposed; a stray "да" can only run that, nothing else.
|
||||
if hx := h.pendingHexis; hx != nil {
|
||||
if h.now().After(hx.expiry) {
|
||||
h.pendingHexis = nil
|
||||
} else {
|
||||
switch classifyConfirm(text) {
|
||||
case confirmYes:
|
||||
h.pendingHexis = nil
|
||||
return h.execHexis(ctx, hx.capabilityID, hx.capName, hx.entityID, hx.displayName), true
|
||||
case confirmNo:
|
||||
h.pendingHexis = nil
|
||||
return "отменила.", true
|
||||
default:
|
||||
h.pendingHexis = nil
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
}
|
||||
// confirmResolver — one parked-confirm slot in the chain. claim() reports
|
||||
// whether this slot holds a live pending, taking it (and dropping an expired
|
||||
// one) as it goes; yes/no then run the answer. Only ever called with h.mu held.
|
||||
type confirmResolver struct {
|
||||
claim func() bool
|
||||
yes func() string
|
||||
no func() string
|
||||
}
|
||||
|
||||
// Check tool confirm (existing behavior).
|
||||
p := h.pending
|
||||
if p == nil {
|
||||
return "", false
|
||||
}
|
||||
if h.now().After(p.expiry) {
|
||||
h.pending = nil
|
||||
return "", false
|
||||
}
|
||||
switch classifyConfirm(text) {
|
||||
case confirmYes:
|
||||
h.pending = nil
|
||||
out, err := h.tools.Exec(ctx, p.fn, p.args, true) // confirmed
|
||||
if err != nil {
|
||||
log.Printf("voice: tool %s (confirmed): %v", p.fn, err)
|
||||
if out != "" {
|
||||
return "не получилось выполнить команду: " + firstLine(out), true
|
||||
}
|
||||
return "не получилось выполнить команду.", true
|
||||
}
|
||||
if out != "" {
|
||||
return "готово: " + firstLine(out), true
|
||||
}
|
||||
return "готово.", true
|
||||
case confirmNo:
|
||||
h.pending = nil
|
||||
return "отменила.", true
|
||||
default:
|
||||
// unclear answer: abandon the confirm, route this utterance normally.
|
||||
h.pending = nil
|
||||
return "", false
|
||||
// confirmResolvers builds the ordered chain resolveConfirm walks. Order is
|
||||
// deliberate: the routine proposal is checked before the tool confirm so a
|
||||
// routine confirm doesn't get eaten by a stale tool pending.
|
||||
func (h *reactiveHandler) confirmResolvers(ctx context.Context) []confirmResolver {
|
||||
var pr *pendingRoutineConfirm
|
||||
var hx *pendingHexisExec
|
||||
var p *pendingAct
|
||||
|
||||
return []confirmResolver{
|
||||
// Routine proposal.
|
||||
{
|
||||
claim: func() bool {
|
||||
pr, h.pendingRoutine = h.pendingRoutine, nil
|
||||
return pr != nil && !h.now().After(pr.expiry)
|
||||
},
|
||||
yes: func() string {
|
||||
// Only record the acceptance. The tick loop reads accepted
|
||||
// routines and nudges on their own interval. Building a
|
||||
// reminder here made a routine fire exactly once (Vikunja #366).
|
||||
if err := h.dataStore.AcceptProposedRoutine(ctx, pr.routineID, h.now()); err != nil {
|
||||
log.Printf("voice: accept proposed routine: %v", err)
|
||||
return "не получилось запомнить рутину."
|
||||
}
|
||||
return "буду напоминать."
|
||||
},
|
||||
no: func() string {
|
||||
if err := h.dataStore.DismissProposedRoutine(ctx, pr.routineID); err != nil {
|
||||
log.Printf("voice: dismiss proposed routine: %v", err)
|
||||
}
|
||||
return "хорошо, не буду."
|
||||
},
|
||||
},
|
||||
// Hexis execution confirm. Bound to the exact capability + target that
|
||||
// was proposed; a stray "да" can only run that, nothing else.
|
||||
{
|
||||
claim: func() bool {
|
||||
hx, h.pendingHexis = h.pendingHexis, nil
|
||||
return hx != nil && !h.now().After(hx.expiry)
|
||||
},
|
||||
yes: func() string {
|
||||
return h.execHexis(ctx, hx.capabilityID, hx.capName, hx.entityID, hx.displayName)
|
||||
},
|
||||
no: func() string { return "отменила." },
|
||||
},
|
||||
// Tool confirm.
|
||||
{
|
||||
claim: func() bool {
|
||||
p, h.pending = h.pending, nil
|
||||
return p != nil && !h.now().After(p.expiry)
|
||||
},
|
||||
yes: func() string {
|
||||
out, err := h.tools.Exec(ctx, p.fn, p.args, true) // confirmed
|
||||
if err != nil {
|
||||
log.Printf("voice: tool %s (confirmed): %v", p.fn, err)
|
||||
if out != "" {
|
||||
return "не получилось выполнить команду: " + firstLine(out)
|
||||
}
|
||||
return "не получилось выполнить команду."
|
||||
}
|
||||
if out != "" {
|
||||
return "готово: " + firstLine(out)
|
||||
}
|
||||
return "готово."
|
||||
},
|
||||
no: func() string { return "отменила." },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user