Persist renamed sessions and profiles
This commit is contained in:
parent
39ce817f51
commit
8fc47622c8
|
|
@ -363,11 +363,15 @@ func (ag *Agent) wireToolsChanged() {
|
|||
}
|
||||
|
||||
// SaveFull persists the full session state (history + metadata) to the given path.
|
||||
func (ag *Agent) SaveFull(path, sessionID, cwd, remote string) error {
|
||||
if ag.history == nil {
|
||||
return fmt.Errorf("no active session")
|
||||
func (ag *Agent) SaveFull(path, sessionID, sessionName, cwd, remote string) error {
|
||||
// Switching profiles intentionally clears the conversation history, but
|
||||
// the profile and display name still need to be persisted. Save an empty
|
||||
// history in that case rather than dropping the entire metadata snapshot.
|
||||
history := ag.history
|
||||
if history == nil {
|
||||
history = &History{}
|
||||
}
|
||||
return ag.history.saveToFull(path, sessionID, ag.Name(), ag.profile,
|
||||
return history.saveToFull(path, sessionID, sessionName, ag.Name(), ag.profile,
|
||||
ag.runtime.Backend.Name(), ag.runtime.Backend.Model(), cwd, remote)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -204,6 +204,14 @@ func (ag *Agent) cmdAgent(_ context.Context, args []string) {
|
|||
ag.profile = name
|
||||
ag.history = nil
|
||||
ag.wireToolsChanged()
|
||||
// Profile switches clear history, so mark the session dirty and flush its
|
||||
// metadata now rather than waiting for a future turn (which may never occur).
|
||||
if ag.saveSession != nil {
|
||||
ag.saveSession()
|
||||
}
|
||||
if ag.flushSave != nil {
|
||||
ag.flushSave()
|
||||
}
|
||||
ag.notifyChange()
|
||||
for _, msg := range rt.Messages {
|
||||
ag.emit(infoEvent(msg))
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ Be concise. Capture what another LLM needs to seamlessly continue.`
|
|||
// PersistedAgent is the on-disk format for a saved session.
|
||||
type PersistedAgent struct {
|
||||
ID string `json:"id"`
|
||||
// SessionName is the mutable display name of the containing session.
|
||||
SessionName string `json:"sessionName,omitempty"`
|
||||
Agent string `json:"agent,omitempty"`
|
||||
Profile string `json:"profile,omitempty"`
|
||||
Backend string `json:"backend,omitempty"`
|
||||
|
|
@ -97,7 +99,7 @@ func (ts *TaskState) render() string {
|
|||
|
||||
// saveTo writes the full message history to path as JSON.
|
||||
func (s *History) saveTo(path, id, displayName, cwd string) error {
|
||||
return s.saveToFull(path, id, displayName, "", "", "", cwd, "")
|
||||
return s.saveToFull(path, id, "", displayName, "", "", "", cwd, "")
|
||||
}
|
||||
|
||||
// saveToFull writes session state including backend/model info.
|
||||
|
|
@ -118,9 +120,10 @@ func sanitizeMessages(msgs []backend.Message) []backend.Message {
|
|||
return out
|
||||
}
|
||||
|
||||
func (s *History) saveToFull(path, id, displayName, profile, backendName, modelName, cwd, remote string) error {
|
||||
func (s *History) saveToFull(path, id, sessionName, displayName, profile, backendName, modelName, cwd, remote string) error {
|
||||
ps := PersistedAgent{
|
||||
ID: id,
|
||||
SessionName: sessionName,
|
||||
Agent: displayName,
|
||||
Profile: profile,
|
||||
Backend: backendName,
|
||||
|
|
|
|||
|
|
@ -265,6 +265,7 @@ func listAgents(ctx HandlerCtx) ([]FsNodeDecl, error) {
|
|||
return nil
|
||||
}
|
||||
a.SetName(newName)
|
||||
persistSession(globalRootState, s.Name())
|
||||
return nil
|
||||
}),
|
||||
),
|
||||
|
|
@ -281,6 +282,7 @@ func listAgents(ctx HandlerCtx) ([]FsNodeDecl, error) {
|
|||
)
|
||||
agentDir.Rename = func(newName string) error {
|
||||
a.SetName(newName)
|
||||
persistSession(globalRootState, s.Name())
|
||||
return nil
|
||||
}
|
||||
out = append(out, agentDir)
|
||||
|
|
|
|||
|
|
@ -186,6 +186,9 @@ func Rename(rs *rootState, old, new string) error {
|
|||
rs.sessions[new] = sess
|
||||
delete(rs.sessions, old)
|
||||
rs.mu.Unlock()
|
||||
// Persist the updated session and agent display names immediately. The
|
||||
// agent's own autosave does not know the fs-level session display name.
|
||||
persistSession(rs, new)
|
||||
rs.cfg.Log.Info("renamed session %s -> %s", old, new)
|
||||
PostEvent("S rename session/" + old + " session/" + new)
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@ func persistSession(rs *rootState, id string) {
|
|||
dir := activeSessionsDir(rs)
|
||||
os.MkdirAll(dir, 0700)
|
||||
path := filepath.Join(dir, sess.id+".json")
|
||||
if err := sess.Core.SaveSession(path); err != nil {
|
||||
ag := sess.Core.Agent()
|
||||
if err := ag.SaveFull(path, sess.id, sess.Name(), sess.Core.CWD(), ""); err != nil {
|
||||
rs.cfg.Log.Error("persist session %s: %v", id, err)
|
||||
}
|
||||
}
|
||||
|
|
@ -114,13 +115,14 @@ func restoreSession(rs *rootState, ps *agent.PersistedAgent) error {
|
|||
if cwd == "" {
|
||||
cwd, _ = os.Getwd()
|
||||
}
|
||||
// Profile and agent display name are independent persisted fields. Never
|
||||
// use the agent name (or its immutable ID) as a profile fallback: doing so
|
||||
// makes restored sessions report an ID such as "eda74c4e8fb4" as the
|
||||
// active profile.
|
||||
profile := ps.Profile
|
||||
if profile == "" {
|
||||
profile = ps.Agent
|
||||
if profile == "" {
|
||||
profile = "default"
|
||||
}
|
||||
}
|
||||
sessID := ps.ID
|
||||
|
||||
cfg := LoadAgentConfig(rs.cfg.AgentsDir, profile, nil)
|
||||
|
|
@ -265,6 +267,9 @@ func restoreSession(rs *rootState, ps *agent.PersistedAgent) error {
|
|||
|
||||
sessionCtx, sessionCancel := context.WithCancel(rs.cfg.Ctx)
|
||||
sess := NewSession(sessID, core, sessionCtx, sessionCancel)
|
||||
if ps.SessionName != "" {
|
||||
sess.SetName(ps.SessionName)
|
||||
}
|
||||
sessPtr = sess
|
||||
sess.uname = uname
|
||||
sess.SetAgentLog(uname, al)
|
||||
|
|
@ -302,6 +307,11 @@ func restoreSession(rs *rootState, ps *agent.PersistedAgent) error {
|
|||
}
|
||||
}
|
||||
rs.cfg.Log.Info("restored session %s: cfg=%v autoload=%d tools=%d (backend=%s model=%s agent=%s)",
|
||||
sessID, cfg != nil, func() int { if cfg != nil { return len(cfg.AutoLoad) }; return -1 }(), toolCount, backendName, modelName, profile)
|
||||
sessID, cfg != nil, func() int {
|
||||
if cfg != nil {
|
||||
return len(cfg.AutoLoad)
|
||||
}
|
||||
return -1
|
||||
}(), toolCount, backendName, modelName, profile)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -382,7 +382,7 @@ func (a *Session) flushSave() {
|
|||
return
|
||||
}
|
||||
if ag := a.Agent(); ag != nil {
|
||||
if err := ag.SaveFull(path, a.id, a.CWD(), a.remote); err != nil {
|
||||
if err := ag.SaveFull(path, a.id, "", a.CWD(), a.remote); err != nil {
|
||||
a.log.Error("session save: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -392,7 +392,7 @@ func (a *Session) flushSave() {
|
|||
// Returns nil if there is no agent (empty session).
|
||||
func (a *Session) SaveSession(path string) error {
|
||||
if ag := a.Agent(); ag != nil {
|
||||
return ag.SaveFull(path, a.id, a.CWD(), a.remote)
|
||||
return ag.SaveFull(path, a.id, "", a.CWD(), a.remote)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue