9p: reject unrecognized ctl commands

ctl now validates the first word of input against a closed set of
known commands. Unrecognized input returns an error instead of being
silently forwarded as a prompt to the agent.
This commit is contained in:
Levi Neely 2026-04-13 15:44:14 +02:00
parent fdcf048a70
commit b048aac109
2 changed files with 16 additions and 7 deletions

View File

@ -109,7 +109,7 @@ echo "rn my-name" > ~/mnt/ollie/s/<session-id>/ctl # rename session
echo "model qwen3:8b" > ~/mnt/ollie/s/<session-id>/ctl echo "model qwen3:8b" > ~/mnt/ollie/s/<session-id>/ctl
``` ```
`ctl` accepts `stop`, `kill`, `rn <name>`, or any command supported by the agent (the `/` prefix is added automatically). `ctl` accepts only recognized commands: `stop`, `kill`, `rn <name>`, `compact`, `clear`, `backend`, `model`, `models`, `agents`, `agent`, `sessions`, `cwd`, `skills`, `tools`, `mcp`, `context`, `usage`, `history`, `irw`, `help`. The `/` prefix is added automatically. Unrecognized input is rejected with an error.
### Switch backend, model, or agent ### Switch backend, model, or agent
@ -119,7 +119,7 @@ echo qwen3:8b > ~/mnt/ollie/s/<session-id>/model
echo myagent > ~/mnt/ollie/s/<session-id>/agent echo myagent > ~/mnt/ollie/s/<session-id>/agent
``` ```
Writes to `backend`, `model`, and `agent` are rejected with an error when the agent is not idle. Writes via `ctl` are asynchronous and cannot return errors; if the agent is running, the command is silently rejected. Check `state` to confirm the change took effect. Writes to `backend`, `model`, and `agent` are rejected with an error when the agent is not idle. Writes to `ctl` return an error for unrecognized commands; recognized commands are dispatched asynchronously. Check `state` to confirm the change took effect.
### Kill a session ### Kill a session

View File

@ -1039,19 +1039,28 @@ func (s *Server) handleWrite(path, input string) error {
return nil return nil
case "ctl": case "ctl":
switch { cmd := strings.Fields(input)
case input == "stop": if len(cmd) == 0 {
return fmt.Errorf("empty ctl command")
}
switch cmd[0] {
case "stop":
sess.core.Interrupt(agent.ErrInterrupted) sess.core.Interrupt(agent.ErrInterrupted)
case input == "kill": case "kill":
s.killSession(sessID) s.killSession(sessID)
case strings.HasPrefix(input, "rn "): case "rn":
if name := strings.TrimSpace(input[3:]); name != "" { if name := strings.TrimSpace(input[3:]); name != "" {
if err := s.renameSession(sessID, name); err != nil { if err := s.renameSession(sessID, name); err != nil {
fmt.Fprintf(os.Stderr, "olliesrv: rename: %v\n", err) fmt.Fprintf(os.Stderr, "olliesrv: rename: %v\n", err)
} }
} }
default: case "compact", "clear", "backend", "model", "models",
"agents", "agent", "sessions", "cwd", "skills",
"tools", "mcp", "context", "usage", "history",
"irw", "help":
sess.core.Submit(sess.ctx, "/"+input, publish) sess.core.Submit(sess.ctx, "/"+input, publish)
default:
return fmt.Errorf("unknown ctl command: %s", cmd[0])
} }
return nil return nil