Files
Maven/cmd/mavend/actions.go
T
claude fe489dff6d Merge task/467 so the sweep tail can reach the three mechanisms (V-528)
attentionq.go, repair.go and internal/router/complaint.go carry the last
hand-written Russian patterns of the V-522 sweep, and they live on task/467.
internal/lexicon, internal/morph and cmd/mavend/topics.go live here. One of
the two had to move.

Four conflicts, and one of them is a real collision rather than a mechanical
one. Both branches wrote the narrative stage 0 rule. This side had
NarrativeQueryGrammars, plural, with the rest-of-day rule beside it and the
verb alternation built from the lexicon; task/467 had NarrativeQueryGrammar,
singular, which extracts the topic into Slots.Text, refuses a bare "расскажи",
and excludes the shapes that are chat ("расскажи о себе", "историю на ночь").
Resolved by keeping this side's container and this side's lexicon-built
pattern, and taking every behaviour only the other side had: the topic slot,
the empty-topic refusal, chatNarrativeTopics, and its wiring position after
TaskCaptureGrammar so "запиши" still beats "расскажи".

The rest: queryFeeds keeps task/467's conditional claim (V-474 supersedes the
unconditional one), rank.go keeps Spoken and drops pluralTasksRU because
say.CountWord is the one copy of Russian count agreement, and vendor/ was
re-vendored — the merged modules.txt claimed replaces for nexus and praxis
that neither go.mod has.

Routing fixture 58/82, unchanged from both sides.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 19:00:32 +04:00

80 lines
3.4 KiB
Go

// actionTable dispatches applyAction's per-intent bodies. Each of the 7
// intents (fact, reminder, note, query, act, chat, system) has one handler
// here with the signature:
//
// func(h *reactiveHandler, ctx context.Context, dec router.Decision) string
//
// same contract as applyAction itself: "" means "let the Replier phrase the
// reply", a non-empty string OVERRIDES it. This is a straight extraction of
// applyAction's old switch cases (formerly ~300 lines in voice.go) — no
// reordering of side effects, no new abstractions inside a handler.
//
// What does NOT belong in this table, because it is not per-intent:
//
// - the dec.Clarify short-circuit ("" when the router's stage-3 fired) —
// stays in applyAction, before dispatch, since it applies to every
// intent identically.
// - the destructive-act confirm gate (park / resolveConfirm / confirmTTL)
// and the enabled-tool allowlist. Both live entirely inside
// actionAct/handleAct in actions_act.go, exactly where they lived in the old
// switch's IntentAct case — they are act-specific (a fact or a note
// can't be destructive), not shared across intents, so they do not need
// to move to a separate layer. The important invariant, preserved
// as-is: applyAction runs identically whether dec came from a fresh
// route or from a completed clarify answer (see finishClarified in
// clarify.go and its comment "filling in an argument never grants
// authority") — a handler must never special-case a clarify-completed
// decision to skip the confirm gate or the allowlist.
// - detectPattern and dialogue-session bookkeeping (rememberTurn,
// followUpMerge) run in the callers (runTurn,
// finishClarified), not per-intent, and are untouched by this slice.
//
// Each handler lives in actions_<intent>.go; the small ones (chat, system)
// and the table itself stay here.
//
// Adding an intent: write its handler in its own file, add one line to
// actionHandlers. Do not grow applyAction's switch back.
package main
import (
"context"
"log"
"github.com/kami/maven/internal/phraser"
"github.com/kami/maven/internal/router"
)
// actionHandlers is the per-intent dispatch table used by applyAction.
var actionHandlers = map[router.Intent]func(*reactiveHandler, context.Context, router.Decision) string{
router.IntentFact: (*reactiveHandler).actionFact,
router.IntentReminder: (*reactiveHandler).actionReminder,
router.IntentAct: (*reactiveHandler).actionAct,
router.IntentChat: (*reactiveHandler).actionChat,
router.IntentSystem: (*reactiveHandler).actionSystem,
router.IntentNote: (*reactiveHandler).actionNote,
router.IntentQuery: (*reactiveHandler).actionQuery,
}
func (h *reactiveHandler) actionChat(ctx context.Context, dec router.Decision) string {
// Conversational: build history from dialogue session (prior user turns)
// and let the LLM respond from general knowledge + context.
if h.phraser == nil {
return "поговорили."
}
history := h.chatHistory()
// The phraser hands back its own fallback text alongside the error, so the
// turn survives a dead server and the failure still reaches the log.
reply, err := h.phraser.PhraseChat(ctx, dec.Utterance, history)
if err != nil {
log.Printf("voice: chat: %v", err)
}
if reply == "" {
return phraser.ChatFallback()
}
return reply
}
func (h *reactiveHandler) actionSystem(ctx context.Context, dec router.Decision) string {
return h.replySystem(ctx, dec)
}