fix: idle-таймер сбрасывается по live-стриму LLM + дефолт 5м; агенты opencode в agents/ с mode: primary
All checks were successful
CI / test (push) Successful in 56s
CI / build-and-package (amd64, linux) (push) Successful in 42s
CI / build-and-package (amd64, windows) (push) Successful in 40s

- opencode ищет кастомных агентов в поддиректории agents/ (как .opencode),
  а не в корне OPENCODE_CONFIG_DIR → распаковка теперь в <dir>/agents/*.md
- markdown-агентам добавлен mode: primary (иначе subagent по умолчанию)
- idle-детекция сбрасывает таймер при live-строках (text/tool/agent/reasoning),
  а не только по росту БД → «LLM думает и стримит» не считается зависанием
- дефолт idle_timeout поднят с 2м до 5м
This commit is contained in:
Hermes
2026-08-17 10:57:19 +05:00
parent da775ac227
commit 9c5f38698c
10 changed files with 75 additions and 12 deletions

View File

@@ -10,6 +10,7 @@ import (
"os/exec"
"strings"
"sync"
"sync/atomic"
"time"
_ "modernc.org/sqlite" // чисто-Go драйвер, без CGO → один статический бинарь
@@ -30,7 +31,7 @@ type Runner struct {
DBPath string // путь к opencode.db (idle-детекция активности)
Config string // путь к opencode.json (OPENCODE_CONFIG)
ConfigDir string // путь к каталогу с агентами (OPENCODE_CONFIG_DIR)
IdleTimeout time.Duration // нет новых сообщений в БД → зависание
IdleTimeout time.Duration // нет активных live-строк в стриме И сообщений в БД → завис
HardTimeout time.Duration // общий лимит на запуск
PollInterval time.Duration
@@ -43,7 +44,7 @@ func (r *Runner) defaults() {
r.Bin = "opencode"
}
if r.IdleTimeout == 0 {
r.IdleTimeout = 2 * time.Minute
r.IdleTimeout = 5 * time.Minute
}
if r.HardTimeout == 0 {
r.HardTimeout = 20 * time.Minute
@@ -143,6 +144,11 @@ func (r *Runner) Run(ctx context.Context, prompt, cwd, agent, sessionID string)
var buf []string
var mu sync.Mutex
done := make(chan struct{})
// liveSeq — кол-во распознанных live-строк (text/tool/agent/reasoning) в
// NDJSON-потоке. Инкрементится из goroutine чтения; поллинг сравнивает,
// чтобы сбросить idle-таймер «пока LLM стримит» (а не только по БД).
var liveSeq atomic.Uint64
prevLive := liveSeq.Load()
// Живое наблюдение сессии (если задано через WithLive в контексте).
liveReg, liveTask := liveFromContext(ctx)
if liveReg != nil && liveTask != 0 {
@@ -157,8 +163,10 @@ func (r *Runner) Run(ctx context.Context, prompt, cwd, agent, sessionID string)
mu.Lock()
buf = append(buf, line)
mu.Unlock()
if liveReg != nil {
if st := parseLiveStep(line); st != nil {
if st := parseLiveStep(line); st != nil {
// «пульс» LLM: что-то стримится/вызывается — сбрасываем idle
liveSeq.Add(1)
if liveReg != nil {
liveReg.Observe(liveTask, *st)
}
}
@@ -186,6 +194,12 @@ pollLoop:
break pollLoop
}
now := time.Now()
// «Пульс» LLM: если с прошлого поллинга появились live-строки
// (text/tool/agent/reasoning) — LLM реально работает, сбрасываем idle.
if cur := liveSeq.Load(); cur != prevLive {
prevLive = cur
lastProgress = now
}
ts, ok := r.maxDirMsgTS(ctx, cwd)
if ok && ts > baseline {
lastProgress = now