feat: наблюдение за живой opencode-сессией (/status N показывает что делает агент)
All checks were successful
CI / test (push) Successful in 54s
CI / build-and-package (amd64, linux) (push) Successful in 35s
CI / build-and-package (amd64, windows) (push) Successful in 43s

LiveRegistry собирает live-шаги из stdout процесса opencode по taskID
(через контекст, без изменения интерфейса Runner). /status N для running-задачи
прикладывает живое состояние: активный субагент, давность последнего шага,
последние text-шаги агента. Ничего не перезапускается и не убивается —
только наблюдение по запросу.
This commit is contained in:
Hermes
2026-08-17 08:05:15 +05:00
parent 2255917949
commit da775ac227
9 changed files with 329 additions and 26 deletions

View File

@@ -10,11 +10,20 @@ import (
// Core — ядро Ratatoskr: машина состояний поверх storage.
type Core struct {
Store *storage.Storage
Decide Decider
MaxTurns int // D3
MaxConfirmCycles int // D4
// Store — хранилище задач и трасс.
Store *storage.Storage
Decide Decider
MaxTurns int // D3
MaxConfirmCycles int // D4
MaxQuestionsPerTurn int
// Live — опциональный просмотр живой сессии задачи (для /status N).
Live LiveProber
}
// LiveProber — абстракция за журналом живых сессий (реализация — *opencode.LiveRegistry).
// Возвращает текстовое описание текущего состояния сессии задачи и ok=была ли активна.
type LiveProber interface {
Probe(taskID int64) (description string, ok bool)
}
// New создаёт Core с дефолтными лимитами.
@@ -85,7 +94,7 @@ func (c *Core) handleCommand(ctx context.Context, taskID int64, text string) (Re
return c.handleContinue(ctx, rest)
default:
return Result{
Reply: "Неизвестная команда. Доступно: /start /cancel /skip /retry N /status N",
Reply: "Неизвестная команда. Доступно: /start /cancel /skip /retry N /status N",
Action: "send",
TaskID: taskID,
}, nil
@@ -171,7 +180,7 @@ func (c *Core) handleRetry(ctx context.Context, rest string) (Result, error) {
id, ok := parseTaskID(rest)
if !ok {
return Result{
Reply: "Укажите номер задачи: `/retry 5`.",
Reply: "Укажите номер задачи: `/retry 5`.",
Action: "send",
}, nil
}
@@ -199,7 +208,7 @@ func (c *Core) handleStatus(ctx context.Context, rest string) (Result, error) {
id, ok := parseTaskID(rest)
if !ok {
return Result{
Reply: "Укажите номер задачи: `/status 5`.",
Reply: "Укажите номер задачи: `/status 5`.",
Action: "send",
}, nil
}
@@ -207,8 +216,15 @@ func (c *Core) handleStatus(ctx context.Context, rest string) (Result, error) {
if err != nil {
return c.notFoundReply(ctx, id, err)
}
reply := "Задача #" + itoa(id) + ": " + string(task.Status)
// Для выполняемой задачи прикладываем живое состояние сессии (если настроено).
if task.Status == storage.StatusRunning && c.Live != nil {
if desc, ok := c.Live.Probe(id); ok && desc != "" {
reply += "\n" + desc
}
}
return Result{
Reply: "Задача #" + itoa(id) + ": " + string(task.Status),
Reply: reply,
Action: "send",
TaskID: id,
Status: task.Status,
@@ -220,7 +236,7 @@ func (c *Core) handleContinue(ctx context.Context, rest string) (Result, error)
id, ok := parseTaskID(rest)
if !ok {
return Result{
Reply: "Укажите номер задачи: `/continue 5`.",
Reply: "Укажите номер задачи: `/continue 5`.",
Action: "send",
}, nil
}
@@ -229,7 +245,7 @@ func (c *Core) handleContinue(ctx context.Context, rest string) (Result, error)
return c.notFoundReply(ctx, id, err)
}
return Result{
Reply: "Задача #" + itoa(id) + " в статусе " + string(task.Status) +
Reply: "Задача #" + itoa(id) + " в статусе " + string(task.Status) +
". Резюм сессии — пока не реализован.",
Action: "send",
TaskID: id,
@@ -449,4 +465,4 @@ func itoa(n int64) string {
buf[i] = '-'
}
return string(buf[i:])
}
}