session: split immutable ID from mutable Name; add id/name files to 9P
This commit is contained in:
parent
7403e1f5bb
commit
89f0ea4179
|
|
@ -113,9 +113,14 @@ func agentPath(sid string) string {
|
||||||
|
|
||||||
// --- session list window ---
|
// --- session list window ---
|
||||||
|
|
||||||
|
type sessionEntry struct {
|
||||||
|
id string // full UUID
|
||||||
|
name string // display name
|
||||||
|
}
|
||||||
|
|
||||||
type sessionList struct {
|
type sessionList struct {
|
||||||
win *acme.Win
|
win *acme.Win
|
||||||
allSIDs []string
|
entries []sessionEntry
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,42 +139,43 @@ func newSessionList() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sl *sessionList) refresh() {
|
func (sl *sessionList) refresh() {
|
||||||
data, err := read9p("session/idx")
|
fsys := dial9p()
|
||||||
|
fid, err := fsys.Open("session", plan9.OREAD)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sl.win.Errf("read idx: %v", err)
|
sl.win.Errf("list sessions: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
dirs, err := fid.Dirreadall()
|
||||||
|
fid.Close()
|
||||||
|
if err != nil {
|
||||||
|
sl.win.Errf("list sessions: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sl.win.Addr(",")
|
sl.win.Addr(",")
|
||||||
sl.win.Write("data", nil)
|
sl.win.Write("data", nil)
|
||||||
|
|
||||||
raw := strings.TrimSpace(string(data))
|
|
||||||
if raw == "" {
|
|
||||||
sl.win.Ctl("clean")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := strings.Split(raw, "\n")
|
|
||||||
sort.Strings(lines)
|
|
||||||
|
|
||||||
sl.mu.Lock()
|
sl.mu.Lock()
|
||||||
sl.allSIDs = nil
|
sl.entries = nil
|
||||||
var display strings.Builder
|
var display strings.Builder
|
||||||
for _, l := range lines {
|
for _, d := range dirs {
|
||||||
if s := strings.TrimSpace(l); s != "" {
|
if d.Mode&plan9.DMDIR == 0 {
|
||||||
id, rest, _ := strings.Cut(s, "\t")
|
continue
|
||||||
sl.allSIDs = append(sl.allSIDs, id)
|
|
||||||
short := id
|
|
||||||
if idx := strings.IndexByte(id, '-'); idx > 0 {
|
|
||||||
short = id[:idx]
|
|
||||||
}
|
|
||||||
display.WriteString(short)
|
|
||||||
if rest != "" {
|
|
||||||
display.WriteByte('\t')
|
|
||||||
display.WriteString(rest)
|
|
||||||
}
|
|
||||||
display.WriteByte('\n')
|
|
||||||
}
|
}
|
||||||
|
id := d.Name
|
||||||
|
name := id
|
||||||
|
if d, err := read9p("session/" + id + "/name"); err == nil {
|
||||||
|
if n := strings.TrimSpace(string(d)); n != "" {
|
||||||
|
name = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sl.entries = append(sl.entries, sessionEntry{id: id, name: name})
|
||||||
|
display.WriteString(name)
|
||||||
|
display.WriteByte('\n')
|
||||||
}
|
}
|
||||||
|
sort.Slice(sl.entries, func(i, j int) bool {
|
||||||
|
return sl.entries[i].name < sl.entries[j].name
|
||||||
|
})
|
||||||
sl.mu.Unlock()
|
sl.mu.Unlock()
|
||||||
|
|
||||||
sl.win.Write("body", []byte(display.String()))
|
sl.win.Write("body", []byte(display.String()))
|
||||||
|
|
@ -183,13 +189,9 @@ func (sl *sessionList) resolveFullSID(text string) string {
|
||||||
}
|
}
|
||||||
sl.mu.Lock()
|
sl.mu.Lock()
|
||||||
defer sl.mu.Unlock()
|
defer sl.mu.Unlock()
|
||||||
for _, sid := range sl.allSIDs {
|
for _, e := range sl.entries {
|
||||||
short := sid
|
if e.name == text || e.id == text {
|
||||||
if idx := strings.IndexByte(sid, '-'); idx > 0 {
|
return e.id
|
||||||
short = sid[:idx]
|
|
||||||
}
|
|
||||||
if short == text || sid == text {
|
|
||||||
return sid
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
|
|
@ -252,18 +254,11 @@ func openChat(sid string) {
|
||||||
log.Printf("open chat %s: %v", sid, err)
|
log.Printf("open chat %s: %v", sid, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
short := sid
|
winName := "ollie/sessions"
|
||||||
if idx := strings.IndexByte(sid, '-'); idx > 0 {
|
if data, err := read9p("session/" + sid + "/name"); err == nil {
|
||||||
short = sid[:idx]
|
name := strings.TrimSpace(string(data))
|
||||||
}
|
if name != "" {
|
||||||
winName := "ollie/sessions/" + short
|
winName = name + "/+Ollie"
|
||||||
if data, err := read9p("session/idx"); err == nil {
|
|
||||||
for _, line := range strings.Split(string(data), "\n") {
|
|
||||||
fields := strings.Split(line, "\t")
|
|
||||||
if len(fields) >= 3 && fields[0] == sid {
|
|
||||||
winName = fields[2] + "/+Ollie"
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.Name("%s", winName)
|
w.Name("%s", winName)
|
||||||
|
|
|
||||||
|
|
@ -70,9 +70,12 @@ func Create(rs *rootState, args []string) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sessID := name
|
sessID := coresession.NewSessionID()
|
||||||
if sessID == "" {
|
if name == "" {
|
||||||
sessID = coresession.NewSessionID()
|
name = sessID
|
||||||
|
if idx := strings.IndexByte(sessID, '-'); idx > 0 {
|
||||||
|
name = sessID[:idx]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rs.mu.RLock()
|
rs.mu.RLock()
|
||||||
|
|
@ -246,6 +249,7 @@ func Create(rs *rootState, args []string) (string, error) {
|
||||||
|
|
||||||
sessionCtx, sessionCancel := context.WithCancel(rs.cfg.Ctx)
|
sessionCtx, sessionCancel := context.WithCancel(rs.cfg.Ctx)
|
||||||
sess := NewSession(sessID, core, sessionCtx, sessionCancel)
|
sess := NewSession(sessID, core, sessionCtx, sessionCancel)
|
||||||
|
sess.SetName(name)
|
||||||
sessPtr = sess
|
sessPtr = sess
|
||||||
sess.AgentLog = al
|
sess.AgentLog = al
|
||||||
sess.proc = proc
|
sess.proc = proc
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,18 @@ func (h *sessionHelper) fileSpec(name string, mode os.FileMode) FileSpec {
|
||||||
fs := FileSpec{Name: name, Mode: mode}
|
fs := FileSpec{Name: name, Mode: mode}
|
||||||
|
|
||||||
switch name {
|
switch name {
|
||||||
|
case "id":
|
||||||
|
fs.Read = func() ([]byte, error) { return []byte(h.sess.id), nil }
|
||||||
|
case "name":
|
||||||
|
fs.Read = func() ([]byte, error) { return []byte(h.sess.Name()), nil }
|
||||||
|
fs.Write = func(data []byte) error {
|
||||||
|
newName := strings.TrimSpace(string(data))
|
||||||
|
if newName == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
h.sess.SetName(newName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
case "env":
|
case "env":
|
||||||
fs.Read = func() ([]byte, error) { return []byte(h.envContent()), nil }
|
fs.Read = func() ([]byte, error) { return []byte(h.envContent()), nil }
|
||||||
case "ctl":
|
case "ctl":
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,8 @@ var Perms = map[string]Perm{
|
||||||
"toolsrv.loaded": 0444,
|
"toolsrv.loaded": 0444,
|
||||||
"toolsrv.rev": 0444,
|
"toolsrv.rev": 0444,
|
||||||
"tools": 0666,
|
"tools": 0666,
|
||||||
|
"name": 0666,
|
||||||
|
"id": 0444,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,33 +39,19 @@ func KillFromRoot(root *Tree, id string) {
|
||||||
Kill(state(root), id)
|
Kill(state(root), id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rename renames a session.
|
// Rename renames a session by setting its Name. The immutable ID stays unchanged.
|
||||||
func Rename(rs *rootState, old, new string) error {
|
func Rename(rs *rootState, old, new string) error {
|
||||||
rs.mu.Lock()
|
rs.mu.RLock()
|
||||||
sess, ok := rs.sessions[old]
|
sess, ok := rs.sessions[old]
|
||||||
|
rs.mu.RUnlock()
|
||||||
if !ok {
|
if !ok {
|
||||||
rs.mu.Unlock()
|
|
||||||
return fmt.Errorf("session not found: %s", old)
|
return fmt.Errorf("session not found: %s", old)
|
||||||
}
|
}
|
||||||
if _, exists := rs.sessions[new]; exists {
|
|
||||||
rs.mu.Unlock()
|
|
||||||
return fmt.Errorf("session already exists: %s", new)
|
|
||||||
}
|
|
||||||
if sess.Core.Agent().IsRunning() {
|
if sess.Core.Agent().IsRunning() {
|
||||||
rs.mu.Unlock()
|
|
||||||
return fmt.Errorf("cannot rename while agent is running")
|
return fmt.Errorf("cannot rename while agent is running")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sess.Core.SetSessionID(new); err != nil {
|
sess.SetName(new)
|
||||||
rs.mu.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sess.id = new
|
|
||||||
rs.sessions[new] = sess
|
|
||||||
delete(rs.sessions, old)
|
|
||||||
rs.mu.Unlock()
|
|
||||||
|
|
||||||
rs.cfg.Log.Info("renamed session %s -> %s", old, new)
|
rs.cfg.Log.Info("renamed session %s -> %s", old, new)
|
||||||
broadcastEvents()
|
broadcastEvents()
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -486,17 +486,18 @@ func buildIndex(rs *rootState) []byte {
|
||||||
ids = append(ids, id)
|
ids = append(ids, id)
|
||||||
}
|
}
|
||||||
sort.Strings(ids)
|
sort.Strings(ids)
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
sess := rs.sessions[id]
|
sess := rs.sessions[id]
|
||||||
sess.mu.RLock()
|
sess.mu.RLock()
|
||||||
st := sess.Core.Agent().State()
|
st := sess.Core.Agent().State()
|
||||||
cwd := sess.Core.CWD()
|
cwd := sess.Core.CWD()
|
||||||
be := sess.Core.Agent().BackendName()
|
be := sess.Core.Agent().BackendName()
|
||||||
model := sess.Core.Agent().ModelName()
|
model := sess.Core.Agent().ModelName()
|
||||||
ag := sess.Core.Agent().Name()
|
ag := sess.Core.Agent().Name()
|
||||||
sess.mu.RUnlock()
|
name := sess.name
|
||||||
fmt.Fprintf(&sb, "%s\t%s\t%s\t%s\t%s\t%s\n", id, st, cwd, be, model, ag)
|
sess.mu.RUnlock()
|
||||||
}
|
fmt.Fprintf(&sb, "%s\t%s\t%s\t%s\t%s\t%s\n", name, st, cwd, be, model, ag)
|
||||||
|
}
|
||||||
rs.mu.RUnlock()
|
rs.mu.RUnlock()
|
||||||
return []byte(sb.String())
|
return []byte(sb.String())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ import (
|
||||||
// Agent-scoped state (log, plan, etc.) lives in AgentLog.
|
// Agent-scoped state (log, plan, etc.) lives in AgentLog.
|
||||||
type Session struct {
|
type Session struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
id string
|
id string // immutable UUID, set at creation, never changes
|
||||||
|
name string // mutable friendly name, defaults to id
|
||||||
uname string // immutable user principal (numeric UID), set at creation
|
uname string // immutable user principal (numeric UID), set at creation
|
||||||
Core *session.Session
|
Core *session.Session
|
||||||
SessionCtx context.Context
|
SessionCtx context.Context
|
||||||
|
|
@ -53,7 +54,11 @@ func NewAgentLog(remote string) *AgentLog {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSession(id string, core *session.Session, ctx context.Context, cancel context.CancelFunc) *Session {
|
func NewSession(id string, core *session.Session, ctx context.Context, cancel context.CancelFunc) *Session {
|
||||||
sess := &Session{id: id, Core: core, SessionCtx: ctx, cancel: cancel}
|
name := id
|
||||||
|
if idx := strings.IndexByte(id, '-'); idx > 0 {
|
||||||
|
name = id[:idx]
|
||||||
|
}
|
||||||
|
sess := &Session{id: id, name: name, Core: core, SessionCtx: ctx, cancel: cancel}
|
||||||
return sess
|
return sess
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,6 +97,16 @@ func (sess *Session) InvalidateModelsCache() {
|
||||||
|
|
||||||
func (sess *Session) RunnableID() string { return sess.id }
|
func (sess *Session) RunnableID() string { return sess.id }
|
||||||
func (sess *Session) Uname() string { return sess.uname }
|
func (sess *Session) Uname() string { return sess.uname }
|
||||||
|
func (sess *Session) Name() string {
|
||||||
|
sess.mu.RLock()
|
||||||
|
defer sess.mu.RUnlock()
|
||||||
|
return sess.name
|
||||||
|
}
|
||||||
|
func (sess *Session) SetName(name string) {
|
||||||
|
sess.mu.Lock()
|
||||||
|
defer sess.mu.Unlock()
|
||||||
|
sess.name = name
|
||||||
|
}
|
||||||
|
|
||||||
func (sess *Session) Cancel() {
|
func (sess *Session) Cancel() {
|
||||||
sess.cancel()
|
sess.cancel()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue