Files
opsxdb/server/internal/ai/session.go
T
logan 5416e86379 feat(ai): session harness contract — incomplete, run_id, confirm bind
Add docs/16 for session state machine, incomplete/resume, queue semantics,
and agent-loop/harness/MCP gap review. Wire RunOutcome on max-rounds,
run_id through audit, TrimMessages/Redact hooks, ConfirmBindHash on write
confirm path; session file skeleton without HTTP yet. No push.
2026-10-15 13:58:10 +08:00

90 lines
2.2 KiB
Go

package ai
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"time"
)
// Session 桌面本地会话(目标态落盘;HTTP 路由可后挂)。
type Session struct {
ID string `json:"id"`
Title string `json:"title,omitempty"`
Status string `json:"status"` // idle|running|incomplete|completed|failed|cancelled
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
Messages []Message `json:"messages"`
Runs []SessionRun `json:"runs,omitempty"`
}
// SessionRun 一次运行摘要。
type SessionRun struct {
RunID string `json:"runId"`
Outcome string `json:"outcome"`
IncompleteReason string `json:"incompleteReason,omitempty"`
Resumable bool `json:"resumable"`
StepsUsed int `json:"stepsUsed"`
StepsBudget int `json:"stepsBudget"`
FinishedAt string `json:"finishedAt"`
}
func sessionsDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(home, ".opsxdb", "ai-sessions"), nil
}
// SessionPath 返回会话文件路径。
func SessionPath(id string) (string, error) {
dir, err := sessionsDir()
if err != nil {
return "", err
}
return filepath.Join(dir, id+".json"), nil
}
// SaveSession 脱敏后写入磁盘。
func SaveSession(s Session) error {
if s.ID == "" {
return fmt.Errorf("session id required")
}
s.Messages = RedactMessages(s.Messages)
s.UpdatedAt = time.Now().Format(time.RFC3339)
if s.CreatedAt == "" {
s.CreatedAt = s.UpdatedAt
}
path, err := SessionPath(s.ID)
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
return err
}
b, err := json.MarshalIndent(s, "", " ")
if err != nil {
return err
}
return os.WriteFile(path, b, 0600)
}
// LoadSession 读会话;不存在返回 os.ErrNotExist。
func LoadSession(id string) (Session, error) {
path, err := SessionPath(id)
if err != nil {
return Session{}, err
}
b, err := os.ReadFile(path)
if err != nil {
return Session{}, err
}
var s Session
if err := json.Unmarshal(b, &s); err != nil {
return Session{}, err
}
return s, nil
}