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.
90 lines
2.2 KiB
Go
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
|
|
}
|