session rename: eventwait file, broadcast on create/delete/rename, ollie-9p mv

This commit is contained in:
Ollie Agent 2026-08-01 16:14:49 +02:00
parent c18d4560de
commit 8ae8a6ead4
6 changed files with 75 additions and 4 deletions

View File

@ -116,6 +116,8 @@ func dial() *client.Fsys {
} }
address = ns + "/ollie" address = ns + "/ollie"
} }
// Strip plan9port-style unix! prefix if present
address = strings.TrimPrefix(address, "unix!")
aname := os.Getenv("OLLIE_UNAME") aname := os.Getenv("OLLIE_UNAME")
if aname == "" { if aname == "" {
@ -128,6 +130,11 @@ func dial() *client.Fsys {
aname = "none" aname = "none"
} }
} }
// If OLLIE_SESSION_ID is set, we're inside a session and must use OLLIE_UNAME.
// If it's not set, we're outside (e.g. GUI) and use $USER.
// The GUI calls renameSession which spawns ollie-9p without OLLIE_SESSION_ID,
// so it falls through to $USER. That's correct — the server accepts $USER
// as a valid attach identity for non-session operations.
fsys, err := mountWithAname(address, aname) fsys, err := mountWithAname(address, aname)
if err != nil { if err != nil {

View File

@ -260,6 +260,7 @@ func Create(rs *rootState, args []string) (string, error) {
rs.cfg.Log.Info("new session %s (backend=%s model=%s agent=%s)", rs.cfg.Log.Info("new session %s (backend=%s model=%s agent=%s)",
sessID, core.Agent().BackendName(), core.Agent().ModelName(), core.Agent().Name()) sessID, core.Agent().BackendName(), core.Agent().ModelName(), core.Agent().Name())
broadcastEvents()
return sessID, nil return sessID, nil
} }

View File

@ -57,8 +57,9 @@ var Perms = map[string]Perm{
DirMode: 0755, DirMode: 0755,
FileMode: 0444, FileMode: 0444,
Files: map[string]os.FileMode{ Files: map[string]os.FileMode{
"new": 0666, "new": 0666,
"idx": 0444, "idx": 0444,
"eventwait": 0444,
}, },
}, },
PathSessionDir: {DirMode: 0755}, PathSessionDir: {DirMode: 0755},

View File

@ -31,6 +31,7 @@ func Kill(rs *rootState, id string) {
removePersistedAgent(rs, id) removePersistedAgent(rs, id)
rs.cfg.Log.Info("killed session %s", id) rs.cfg.Log.Info("killed session %s", id)
} }
broadcastEvents()
} }
// KillFromRoot is the public API for callers with *Tree. // KillFromRoot is the public API for callers with *Tree.
@ -66,6 +67,7 @@ func Rename(rs *rootState, old, new string) error {
rs.mu.Unlock() rs.mu.Unlock()
rs.cfg.Log.Info("renamed session %s -> %s", old, new) rs.cfg.Log.Info("renamed session %s -> %s", old, new)
broadcastEvents()
return nil return nil
} }

View File

@ -17,7 +17,45 @@ import (
"ollie/toolsrv" "ollie/toolsrv"
) )
var rootFileOrder = []string{"new", "idx"} // eventBroadcaster manages notification of session index changes.
// The GUI can tail session/eventwait to detect creates, deletes, and renames.
var (
evMu sync.Mutex
evChans []chan struct{}
)
func broadcastEvents() {
evMu.Lock()
for _, ch := range evChans {
select {
case ch <- struct{}{}:
default:
}
}
evMu.Unlock()
}
// eventRegister returns a channel that fires on the next session index change.
func eventRegister() chan struct{} {
ch := make(chan struct{}, 1)
evMu.Lock()
evChans = append(evChans, ch)
evMu.Unlock()
return ch
}
func eventRelease(ch chan struct{}) {
evMu.Lock()
for i, c := range evChans {
if c == ch {
evChans = append(evChans[:i], evChans[i+1:]...)
break
}
}
evMu.Unlock()
}
var rootFileOrder = []string{"new", "idx", "eventwait"}
// FileMode returns the mode for a fixed session file, // FileMode returns the mode for a fixed session file,
// or 0 and false if the name is not a fixed file. // or 0 and false if the name is not a fixed file.
@ -295,6 +333,7 @@ func rootOpen(rs *rootState, name string) (File, error) {
notBlocking := func(context.Context, string) ([]byte, string, error) { notBlocking := func(context.Context, string) ([]byte, string, error) {
return nil, "", fmt.Errorf("blocking read not supported") return nil, "", fmt.Errorf("blocking read not supported")
} }
parts := strings.SplitN(name, "/", 3) parts := strings.SplitN(name, "/", 3)
// Proc file: {id}/proc/{pid} // Proc file: {id}/proc/{pid}
@ -328,6 +367,27 @@ func rootOpen(rs *rootState, name string) (File, error) {
} }
switch name { switch name {
case "eventwait":
return &FileConfig{
StatFn: func() (os.FileInfo, error) {
return &SyntheticFileInfo{Name_: "eventwait", Mode_: Perms[PathSessions].Files["eventwait"]}, nil
},
ReadFn: func() ([]byte, error) {
return []byte(buildIndex(rs)), nil
},
WriteFn: func([]byte) error { return fmt.Errorf("eventwait: read-only") },
BlockingReadFn: func(ctx context.Context, _ string) ([]byte, string, error) {
ch := eventRegister()
defer eventRelease(ch)
select {
case <-ch:
return []byte(buildIndex(rs)), "", nil
case <-ctx.Done():
return nil, "", ctx.Err()
}
},
IsBlocking_: true,
}, nil
case "new": case "new":
return &FileConfig{ return &FileConfig{
StatFn: func() (os.FileInfo, error) { StatFn: func() (os.FileInfo, error) {

2
kde

@ -1 +1 @@
Subproject commit cd0f92e16de666f23885614c23d7fa33dab8ad9f Subproject commit 76f97c42ebbba47e96d99554da88d4625db2d378