From b048aac1093f885e7e6da06e37910c69487d1386 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Mon, 13 Apr 2026 15:44:14 +0200 Subject: [PATCH] 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. --- README.md | 4 ++-- internal/p9/server.go | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0c4ebab..ed3475f 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ echo "rn my-name" > ~/mnt/ollie/s//ctl # rename session echo "model qwen3:8b" > ~/mnt/ollie/s//ctl ``` -`ctl` accepts `stop`, `kill`, `rn `, or any command supported by the agent (the `/` prefix is added automatically). +`ctl` accepts only recognized commands: `stop`, `kill`, `rn `, `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 @@ -119,7 +119,7 @@ echo qwen3:8b > ~/mnt/ollie/s//model echo myagent > ~/mnt/ollie/s//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 diff --git a/internal/p9/server.go b/internal/p9/server.go index 6fe8561..28a10c7 100644 --- a/internal/p9/server.go +++ b/internal/p9/server.go @@ -1039,19 +1039,28 @@ func (s *Server) handleWrite(path, input string) error { return nil case "ctl": - switch { - case input == "stop": + cmd := strings.Fields(input) + if len(cmd) == 0 { + return fmt.Errorf("empty ctl command") + } + switch cmd[0] { + case "stop": sess.core.Interrupt(agent.ErrInterrupted) - case input == "kill": + case "kill": s.killSession(sessID) - case strings.HasPrefix(input, "rn "): + case "rn": if name := strings.TrimSpace(input[3:]); name != "" { if err := s.renameSession(sessID, name); err != nil { 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) + default: + return fmt.Errorf("unknown ctl command: %s", cmd[0]) } return nil