an act with a target the system cannot have does not run (V-634)

V-633 gave tools spoken aliases, so a Russian act reaches a tool. It resolves
the verb only: the rest of the sentence became argv. "перезагрузи роутер" ran
as systemctl restart роутер, which is a real tool, a real word and a target
that cannot exist on this box. She then reported systemctl's own confusion as
if she had tried something sensible, and on a destructive row she spent a
confirm turn on it first.

The executor now refuses, ahead of the confirm gate, and names the word it
could not place. The check is the script and not a word list: a unit, a
container, a host and a path are ASCII here, so a Cyrillic argv element means
the alias match swallowed the verb and handed on the next word.

Process rows only. An MCP argument is not a target — a task title is Russian
and always was — and a house row drops the spoken args already.

It does not try to guess the right target. Identity is Nexus's, and a target
Nexus resolves reaches Hexis through handleHexisAct before this executor is
asked.
This commit is contained in:
2026-08-06 20:05:34 +04:00
parent c8f74c39d6
commit d94ed2e630
5 changed files with 139 additions and 1 deletions
+23
View File
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"log"
"strings"
"github.com/kami/maven/internal/mcp"
"github.com/kami/maven/internal/phraser"
@@ -60,6 +61,13 @@ func (h *reactiveHandler) actionAct(ctx context.Context, dec router.Decision) st
phrase := actPhrase(dec.Slots.Fn, dec.Slots.Args)
h.park(dec.Slots.Fn, dec.Slots.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
// nothing ran. Saying which word she could not place is the whole
// answer: he either renames it or gives the row an alias that
// carries the target, and both are one turn away (V-634).
return phraser.A(phraser.ActUnknownTarget,
map[string]string{"name": unknownTargetOf(err, dec.Slots.Args)})
case errors.Is(err, tool.ErrNeedsAuthedSurface):
// Irreversible (internal/tool/risk.go). A confirm turn would not
// help: everything that proposed this act — the STT, the router,
@@ -93,3 +101,18 @@ func (h *reactiveHandler) actionAct(ctx context.Context, dec router.Decision) st
}
return phraser.A(phraser.ActDone, nil)
}
// unknownTargetOf pulls the word the executor could not place out of its error,
// falling back to the first arg. The word is what makes the reply usable, and a
// reply naming no word would send him to the log.
func unknownTargetOf(err error, args []string) string {
if _, rest, ok := strings.Cut(err.Error(), `"`); ok {
if word, _, ok := strings.Cut(rest, `"`); ok && word != "" {
return word
}
}
if len(args) > 0 {
return args[0]
}
return ""
}