fix(worker): постмортем получает транскрипт сессий dev/reviewer по session_id
Раньше постмортем-агенту передавался только session_id, без содержимого сессии — анализировать было нечего. Теперь Runner умеет читать сообщения сессии (Messages → renderTranscript) через SessionMessages, а postMortem подставляет реальные шаги агента (сообщения/tool-вызовы) в промпт. Пустая/недоступная сессия помечается явно. Сбои чтения не фатальны.
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
// Пути v2 начинаются с префикса /api (см. README, минимальная версия opencode):
|
||||
// - POST /api/session создать сессию {model:{...}} → {data: Session.Info}
|
||||
// - POST /api/session/{id}/prompt отправить промпт {prompt:{text}} →
|
||||
// НЕБЛОКИРУЮЩЕ (admit) → {data: Admitted}
|
||||
// НЕБЛОКИРУЮЩЕ (admit) → {data: Admitted}
|
||||
// - GET /api/session/{id}/message?order=desc → {data:[Message,...]}
|
||||
// - POST /api/session/{id}/interrupt прервать активный ответ (204)
|
||||
// - GET /api/session/active активные дренажи → {data:{sessionID:...}}
|
||||
@@ -349,6 +350,59 @@ func assistantVerdict(msgs []v2Message, since int64) (texts []string, usedReason
|
||||
return reasoning, len(reasoning) > 0
|
||||
}
|
||||
|
||||
// transcriptRole возвращает человекочитаемую подпись роли сообщения сессии.
|
||||
func transcriptRole(t string) string {
|
||||
switch t {
|
||||
case "user":
|
||||
return "Пользователь"
|
||||
case "assistant":
|
||||
return "Ассистент"
|
||||
case "tool":
|
||||
return "Инструмент"
|
||||
case "system":
|
||||
return "Система"
|
||||
default:
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
||||
// finishLabel — подпись для finish-reason (пусто/: опускаем).
|
||||
func finishLabel(f string) string {
|
||||
if f == "" || f == "stop" {
|
||||
return ""
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// renderTranscript форматирует сообщения сессии (API отдаёт новыми первыми)
|
||||
// в хронологическом порядке: роль, текст (и tool-вызовы), с обрезкой.
|
||||
func renderTranscript(msgs []v2Message) string {
|
||||
var b strings.Builder
|
||||
for i := len(msgs) - 1; i >= 0; i-- {
|
||||
m := &msgs[i]
|
||||
b.WriteString("\n== " + transcriptRole(m.Type))
|
||||
if m.Type == "assistant" {
|
||||
if m.Model != nil {
|
||||
b.WriteString(" (" + m.Model.String() + ")")
|
||||
}
|
||||
if fl := finishLabel(m.Finish); fl != "" {
|
||||
b.WriteString(" finish=" + fl)
|
||||
}
|
||||
}
|
||||
b.WriteString(" ==\n")
|
||||
for _, p := range m.Content {
|
||||
if p.Text == "" {
|
||||
continue
|
||||
}
|
||||
b.WriteString(p.Text)
|
||||
if !strings.HasSuffix(p.Text, "\n") {
|
||||
b.WriteString("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func truncateStr(s string, n int) string {
|
||||
if len(s) <= n {
|
||||
return s
|
||||
|
||||
@@ -90,6 +90,30 @@ func (r *Runner) Run(ctx context.Context, prompt, cwd, agent, sessionID string)
|
||||
return r.awaitVerdict(ctx, c, sid, agent, prompt)
|
||||
}
|
||||
|
||||
// SessionMessages возвращает транскрипт сессии (шаги агента: сообщения,
|
||||
// тексты, tool-вызовы) в хронологическом порядке для постмортем-анализа.
|
||||
// sessionID пустой → пустая строка без ошибки. Ошибки чтения возвращаются
|
||||
// как есть — вызывающий (постмортем) решает, логировать и продолжить.
|
||||
func (r *Runner) SessionMessages(ctx context.Context, cwd, sessionID string) (string, error) {
|
||||
r.defaults()
|
||||
if r.Pool == nil {
|
||||
return "", fmt.Errorf("opencode: Pool не задан (API-режим обязателен)")
|
||||
}
|
||||
if sessionID == "" {
|
||||
return "", nil
|
||||
}
|
||||
srv, err := r.Pool.Ensure(ctx, cwd)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
c := &Client{BaseURL: srv.Addr(), Password: srv.Password, Directory: srv.Dir, Debug: r.Debug}
|
||||
msgs, err := c.Messages(ctx, sessionID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return renderTranscript(msgs), nil
|
||||
}
|
||||
|
||||
// settlePolls — сколько подряд опросов должно подтвердить завершение ответа,
|
||||
// прежде чем считать вердикт финальным (устойчивость к гонке между удалением
|
||||
// сессии из активных дренажей и финализацией последнего сообщения).
|
||||
|
||||
Reference in New Issue
Block a user