990a4a99e9
Two defects in one logged line, both of which put a working capability out of reach of every utterance. The resident model rewrites as it routes, and on the way it transliterates: "перезапусти muzick indexer" came back as "перезагрузить музик индексер", so Nexus was asked to resolve a service nobody has ever named. entityReferenceText takes the longest Latin run out of his own words, but only when the Text slot has lost every Latin letter the utterance had — an English turn and a Russian entity name are both left alone, and reversing the transliteration is not attempted. The second half: the stage-3 gate thins an act that matched no allowlisted fn, and that question was the whole turn, so handleHexisAct never ran. Hexis is where an act with no local fn belongs, so it gets one chance before she asks, and a "" back still leaves her asking. With no ecosystem wired nothing changes. Capability matching reads the phrase as the haystack when there is no fn, because no capability name contains "restart status muzick indexer". Authority is untouched: ambiguity still stops, a mutating capability still goes through the spoken confirm.
60 lines
2.0 KiB
Go
60 lines
2.0 KiB
Go
package main
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/kami/maven/internal/router"
|
|
)
|
|
|
|
// latinRun matches a run of Latin-script words — the shape a service, host or
|
|
// project name takes in a Russian sentence. Digits, dot, dash and underscore
|
|
// ride along because "muzick-indexer" and "nginx.conf" are one name, not two.
|
|
var latinRun = regexp.MustCompile(`[A-Za-z][A-Za-z0-9._-]*(?:\s+[A-Za-z][A-Za-z0-9._-]*)*`)
|
|
|
|
// hasLatin reports whether s carries a Latin letter.
|
|
func hasLatin(s string) bool {
|
|
for _, r := range s {
|
|
if unicode.In(r, unicode.Latin) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// entityReferenceText is the name Nexus is asked to resolve.
|
|
//
|
|
// Normally that is the router's Text slot, which is the verb phrase the model
|
|
// wrote. But the resident model rewrites a Russian utterance as it routes, and
|
|
// on the way it transliterates: "перезапусти muzick indexer" came back as
|
|
// "перезагрузить музик индексер" (Vikunja #476). Nexus is then asked for a
|
|
// service nobody has ever named, so the act cannot resolve its target even
|
|
// with every gate open.
|
|
//
|
|
// The recovery is deliberately narrow. Only when the utterance holds a Latin
|
|
// run and the model's Text holds none has a name certainly been rewritten —
|
|
// then the longest Latin run in his own words is the reference. Anything else
|
|
// keeps the Text slot, so an English utterance and a Russian entity name are
|
|
// both untouched. Un-transliterating the Cyrillic back is not attempted: the
|
|
// surface form he said is right there, and guessing at a reverse mapping would
|
|
// invent a second name to be wrong about.
|
|
func entityReferenceText(dec router.Decision) string {
|
|
text := dec.Slots.Text
|
|
if hasLatin(text) || !hasLatin(dec.Utterance) {
|
|
return text
|
|
}
|
|
longest := ""
|
|
for _, m := range latinRun.FindAllString(dec.Utterance, -1) {
|
|
if len(m) > len(longest) {
|
|
longest = m
|
|
}
|
|
}
|
|
longest = strings.TrimSpace(longest)
|
|
// A single stray letter is not a name.
|
|
if len(longest) < 2 {
|
|
return text
|
|
}
|
|
return longest
|
|
}
|