mavend: centralize action validation boundary (slice 4)
This commit is contained in:
+20
-19
@@ -33,27 +33,28 @@ func (h *reactiveHandler) actionAct(ctx context.Context, dec router.Decision) st
|
||||
// and where the resolution came from (route or matcher).
|
||||
candidate := h.resolveAction(ctx, dec)
|
||||
|
||||
// Write the candidate's resolved values back into Slots so the existing
|
||||
// branches (task-status, Praxis, Hexis, proposeGap, tool.Executor) work
|
||||
// unchanged. This is the mechanical adjustment that preserves all existing
|
||||
// behavior without redesigning those branches.
|
||||
if candidate.ActionResolved() {
|
||||
dec.Slots.Fn = candidate.Fn
|
||||
dec.Slots.Args = candidate.Args
|
||||
dec.Slots.HasFn = true
|
||||
// Structural validation: is this candidate complete enough to proceed?
|
||||
// Unresolved (Fn empty) flows to proposeGap; invalid (Fn present but
|
||||
// malformed) is refused; valid proceeds to execution.
|
||||
validation := router.ValidateActionCandidate(candidate)
|
||||
noteActionValidation(ctx, validation)
|
||||
|
||||
if !validation.Unresolved && !validation.Valid {
|
||||
// Resolved but structurally malformed: refuse execution.
|
||||
return phraser.A(phraser.ActFail, nil)
|
||||
}
|
||||
|
||||
// The board is Maven's own store, so a spoken status change is answered here
|
||||
// and never offered to an ecosystem client (Vikunja #512). First, because
|
||||
// task_status is on no allowlist and no capability registry: reaching either
|
||||
// of them would answer a turn about his own task list with a gap.
|
||||
if dec.Slots.Fn == router.TaskStatusFn {
|
||||
return h.resolveTaskStatus(ctx, dec)
|
||||
if candidate.Fn == router.TaskStatusFn {
|
||||
return h.resolveTaskStatus(ctx, dec, candidate)
|
||||
}
|
||||
|
||||
// Praxis ecosystem tools: intercept before the system command executor.
|
||||
if h.ecosystem != nil && h.ecosystem.praxis != nil && dec.Slots.HasFn {
|
||||
if reply := h.handlePraxisAct(ctx, dec); reply != "" {
|
||||
if h.ecosystem != nil && h.ecosystem.praxis != nil && candidate.ActionResolved() {
|
||||
if reply := h.handlePraxisAct(ctx, dec, candidate); reply != "" {
|
||||
return reply
|
||||
}
|
||||
}
|
||||
@@ -61,23 +62,23 @@ func (h *reactiveHandler) actionAct(ctx context.Context, dec router.Decision) st
|
||||
// Hexis ecosystem action: if ecosystem is configured and we have a verb
|
||||
// + entity text, try to resolve the entity and execute via Hexis.
|
||||
if h.ecosystem != nil && h.ecosystem.hexis != nil && router.ActHasEntityTarget(dec) {
|
||||
if reply := h.handleHexisAct(ctx, dec); reply != "" {
|
||||
if reply := h.handleHexisAct(ctx, dec, candidate); reply != "" {
|
||||
return reply
|
||||
}
|
||||
}
|
||||
|
||||
// HasFn still false ⇒ no allowlist match: scaffold a 'proposed' tool
|
||||
// Unresolved candidate ⇒ no allowlist match: scaffold a 'proposed' tool
|
||||
// the user can enable on the authed surface ("earn the right to ask").
|
||||
if !dec.Slots.HasFn {
|
||||
if !candidate.ActionResolved() {
|
||||
return h.proposeGap(ctx, dec)
|
||||
}
|
||||
out, err := h.tools.Exec(ctx, dec.Slots.Fn, dec.Slots.Args, false)
|
||||
out, err := h.tools.Exec(ctx, candidate.Fn, candidate.Args, false)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, tool.ErrNeedsConfirm):
|
||||
// destructive: park it and ask. The next utterance answers.
|
||||
phrase := actPhrase(dec.Slots.Fn, dec.Slots.Args)
|
||||
h.park(dec.Slots.Fn, dec.Slots.Args, phrase)
|
||||
phrase := actPhrase(candidate.Fn, candidate.Args)
|
||||
h.park(candidate.Fn, candidate.Args, phrase)
|
||||
return phraser.A(phraser.ActConfirm, map[string]string{"name": phrase})
|
||||
case errors.Is(err, tool.ErrUnknownTarget):
|
||||
// The verb reached a tool and the tail did not reach a target, so
|
||||
@@ -112,7 +113,7 @@ func (h *reactiveHandler) actionAct(ctx context.Context, dec router.Decision) st
|
||||
// where a human types them.
|
||||
return phraser.A(phraser.ActNeedsArgs, nil)
|
||||
}
|
||||
log.Printf("voice: tool %s: %v", dec.Slots.Fn, err)
|
||||
log.Printf("voice: tool %s: %v", candidate.Fn, err)
|
||||
if out != "" {
|
||||
return phraser.A(phraser.ActFailOut, map[string]string{"out": firstLine(out)})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user