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