bf6ccf9aea
Ordering is computed, not generated. Asking a 1.7B which of his tasks
matters most produces a fluent opinion with no basis in anything, and a
confidently wrong priority is worse than none — same posture as the
behaviour profile in internal/memory, which counts instead of summarising.
internal/tasks is a pure package (no ipc, no store, no cgo) holding the
score, the order and the Russian rendering, so the spoken list and the
/tasks page cannot drift. Four signals, all of them things he stated:
deadline (overdue > today > tomorrow > this week), stated urgency, age
with a cap so nothing rots at the bottom, and confirmed work always
ahead of mail-derived candidates. A task with no due date and no weight
scores nothing and carries no reason string — inventing a "потому что"
about a priority he never set is the failure mode this avoids.
Capture now picks up urgency he says out loud ("добавь в задачи срочно
оплатить интернет"), stripping the marker from the task text, and the web
add form offers the same three rungs. Ranking is a read: it sorts and
renders, never writes, schedules or announces.
84 lines
3.1 KiB
Go
84 lines
3.1 KiB
Go
package main
|
||
|
||
import (
|
||
"context"
|
||
"log"
|
||
|
||
"github.com/kami/maven/internal/ipc"
|
||
"github.com/kami/maven/internal/router"
|
||
"github.com/kami/maven/internal/store"
|
||
"github.com/kami/maven/internal/tasks"
|
||
)
|
||
|
||
// Task capture on the voice/chat path (Vikunja #130).
|
||
//
|
||
// Two halves, both deliberately small:
|
||
//
|
||
// - captureTaskFromNote runs at the top of actionNote. An utterance that
|
||
// explicitly files a task ("добавь в задачи купить молоко") goes to the task
|
||
// store instead of the note store. Anything without an explicit marker is
|
||
// still a note — see router.ParseTaskCapture for why "надо бы поспать" must
|
||
// not become a task.
|
||
// - queryTasks is a query source that reads the list back.
|
||
//
|
||
// Nothing here speaks unprompted. Tasks are answered when asked about; no tick
|
||
// rule reads the table.
|
||
|
||
// captureTaskFromNote claims the turn when the utterance explicitly files a
|
||
// task, returning the reply. ("", false) hands the turn back to the note path.
|
||
func (h *reactiveHandler) captureTaskFromNote(ctx context.Context, dec router.Decision) (string, bool) {
|
||
cap, ok := router.ParseTaskCapture(dec.Utterance)
|
||
if !ok {
|
||
return "", false
|
||
}
|
||
resp, err := h.api.CaptureTask(ctx, ipc.CaptureTaskReq{
|
||
Text: cap.Text,
|
||
Source: "tap:voice",
|
||
Status: store.TaskOpen, // he stated it himself — not a candidate
|
||
Weight: cap.Weight, // 0 unless he said "срочно" / "важно"
|
||
Ts: h.now(),
|
||
})
|
||
if err != nil {
|
||
log.Printf("voice: capture task: %v", err)
|
||
return "не получилось записать задачу.", true
|
||
}
|
||
if !resp.Created {
|
||
return "это уже в списке.", true
|
||
}
|
||
return "записала: " + cap.Text, true
|
||
}
|
||
|
||
// queryTasks — "какие у меня задачи?", "что мне нужно сделать?".
|
||
//
|
||
// Reads the live set and recites it in priority order (Vikunja #129). The order
|
||
// is computed by internal/tasks from what he told her — deadlines, the urgency
|
||
// he stated, how long a task has been sitting — never asked of the model. The
|
||
// rendering is the package's too, so the spoken list and the /tasks page can
|
||
// never disagree about what comes first.
|
||
func (h *reactiveHandler) queryTasks(ctx context.Context, t *queryTurn) (string, bool) {
|
||
if !router.IsTaskListQuery(t.dec.Utterance) {
|
||
return "", false
|
||
}
|
||
live, err := h.api.ListTasks(ctx, "live")
|
||
if err != nil {
|
||
log.Printf("voice: list tasks: %v", err)
|
||
return "не получилось посмотреть задачи.", true
|
||
}
|
||
return tasks.FormatRU(tasks.Rank(taskItems(live), h.now())), true
|
||
}
|
||
|
||
// taskItems maps wire rows onto the ranker's input. Written here rather than in
|
||
// internal/tasks so the ranker stays a pure package with no ipc (and therefore
|
||
// no store, and therefore no cgo) dependency — the same posture as
|
||
// internal/morning and internal/memory.
|
||
func taskItems(ts []ipc.Task) []tasks.Item {
|
||
out := make([]tasks.Item, len(ts))
|
||
for i, t := range ts {
|
||
out[i] = tasks.Item{
|
||
ID: t.ID, Text: t.Text, Status: t.Status,
|
||
Created: t.CreatedTs, Due: t.Due, Weight: t.Weight,
|
||
}
|
||
}
|
||
return out
|
||
}
|