session: remove command dispatch from Submit
Session.Submit now forwards directly to Agent.Submit. Slash commands are agent-level concerns handled by Agent.HandleCommand. Session-level operations (save, resume, kill, rename, cwd) are ctl verbs issued via the ctl file — they are NOT slash commands and do not flow through the prompt path. This eliminates the artificial session/agent command routing and makes the prompt path clean: everything in the prompt goes to the agent.
This commit is contained in:
parent
8e4cdaa4bd
commit
0b3d04c17c
|
|
@ -180,14 +180,10 @@ func (ag *Agent) cmdAgent(_ context.Context, args []string) {
|
|||
ag.emit(infoEvent(fmt.Sprintf("error: agent %q: %v", name, err)))
|
||||
return
|
||||
}
|
||||
d := ag.newDispatcher()
|
||||
if d == nil {
|
||||
ag.emit(infoEvent("error: no dispatcher configured"))
|
||||
return
|
||||
}
|
||||
disp := ag.newDispatcher()
|
||||
env := []string{"OLLIE_SESSION_ID=" + ag.sessionID, "OLLIE_UNAME=" + ag.id}
|
||||
env = append(env, ag.promptEnvExtra...)
|
||||
rt := BuildRuntime(cfg, d, ag.cwd, env, ag.baseLayers...)
|
||||
rt := BuildRuntime(cfg, disp, ag.cwd, env, ag.baseLayers...)
|
||||
if rt.CfgBackend != "" {
|
||||
newBe, err := ag.newBackend(rt.CfgBackend)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -1,201 +0,0 @@
|
|||
package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"ollie/agent"
|
||||
)
|
||||
|
||||
func (a *Session) handleCommand(ctx context.Context, input string) bool {
|
||||
if !strings.HasPrefix(input, "/") {
|
||||
return false
|
||||
}
|
||||
|
||||
parts := strings.Fields(input)
|
||||
if len(parts) == 0 {
|
||||
return false
|
||||
}
|
||||
cmd := parts[0]
|
||||
args := parts[1:]
|
||||
|
||||
// Try agent-level commands first (they access agent internals directly).
|
||||
if a.r.HandleCommand(ctx, input) {
|
||||
return true
|
||||
}
|
||||
|
||||
listFromHandler := func(name string) {
|
||||
if h := a.listHandlers[name]; h != nil {
|
||||
for _, item := range h() {
|
||||
a.emit(agent.Event{Role: "info", Content: " " + item + "\n"})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type cmdFn func([]string)
|
||||
cmds := map[string]cmdFn{
|
||||
"/sessions": func(args []string) {
|
||||
allFlag := len(args) > 0 && args[0] == "-a"
|
||||
type sessionFile struct {
|
||||
id string
|
||||
path string
|
||||
}
|
||||
var files []sessionFile
|
||||
appendSessionFiles := func(dir string) {
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for i := len(entries) - 1; i >= 0; i-- {
|
||||
e := entries[i]
|
||||
if e.IsDir() || !strings.HasSuffix(e.Name(), ".json") {
|
||||
continue
|
||||
}
|
||||
files = append(files, sessionFile{
|
||||
id: strings.TrimSuffix(e.Name(), ".json"),
|
||||
path: filepath.Join(dir, e.Name()),
|
||||
})
|
||||
}
|
||||
}
|
||||
appendSessionFiles(a.sessionsDir)
|
||||
appendSessionFiles(filepath.Join(a.sessionsDir, "active"))
|
||||
|
||||
cwd := a.CWD()
|
||||
found := false
|
||||
for _, file := range files {
|
||||
data, readErr := os.ReadFile(file.path)
|
||||
if readErr != nil {
|
||||
continue
|
||||
}
|
||||
var ps agent.PersistedAgent
|
||||
if json.Unmarshal(data, &ps) != nil {
|
||||
continue
|
||||
}
|
||||
if !allFlag && ps.CWD != "" && ps.CWD != cwd {
|
||||
continue
|
||||
}
|
||||
marker := " "
|
||||
if file.id == a.id {
|
||||
marker = "* "
|
||||
}
|
||||
goal := ""
|
||||
for _, msg := range ps.Messages {
|
||||
if msg.Role == "user" {
|
||||
goal = msg.Content
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(goal) > 60 {
|
||||
goal = goal[:60] + "..."
|
||||
}
|
||||
a.emit(agent.Event{Role: "info", Content: marker + fmt.Sprintf("%-24s [%s] %q", file.id, ps.Agent, goal) + "\n"})
|
||||
found = true
|
||||
}
|
||||
if !found {
|
||||
a.emit(agent.Event{Role: "info", Content: "no sessions for " + cwd + "\n"})
|
||||
}
|
||||
},
|
||||
|
||||
"/save": func(args []string) {
|
||||
if !a.r.HasHistory() {
|
||||
a.emit(agent.Event{Role: "info", Content: "error: no active session\n"})
|
||||
return
|
||||
}
|
||||
if len(args) == 0 {
|
||||
a.emit(agent.Event{Role: "info", Content: "error: /save requires a name\n"})
|
||||
return
|
||||
}
|
||||
name := args[0]
|
||||
path := a.sessionsDir + "/" + name + ".json"
|
||||
if err := a.r.SaveTo(path, name, a.CWD()); err != nil {
|
||||
a.emit(agent.Event{Role: "info", Content: "error: " + err.Error() + "\n"})
|
||||
return
|
||||
}
|
||||
a.emit(agent.Event{Role: "info", Content: "saved: " + path + "\n"})
|
||||
},
|
||||
|
||||
"/resume": func(args []string) {
|
||||
if len(args) == 0 {
|
||||
a.emit(agent.Event{Role: "info", Content: "error: /resume requires a session id or name\n"})
|
||||
return
|
||||
}
|
||||
if a.IsRunning() {
|
||||
a.emit(agent.Event{Role: "info", Content: "error: cannot resume while agent is running\n"})
|
||||
return
|
||||
}
|
||||
name := args[0]
|
||||
path := a.sessionsDir + "/" + name + ".json"
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
a.emit(agent.Event{Role: "info", Content: fmt.Sprintf("error: %v\n", err)})
|
||||
return
|
||||
}
|
||||
var ps agent.PersistedAgent
|
||||
if err := json.Unmarshal(data, &ps); err != nil {
|
||||
a.emit(agent.Event{Role: "info", Content: fmt.Sprintf("error: %v\n", err)})
|
||||
return
|
||||
}
|
||||
a.r.Restore(&ps)
|
||||
a.emit(agent.Event{Role: "info", Content: fmt.Sprintf("resumed session %s (%d messages)\n", name, len(ps.Messages))})
|
||||
},
|
||||
|
||||
"/cwd": func(args []string) {
|
||||
if len(args) == 0 {
|
||||
a.emit(agent.Event{Role: "info", Content: "cwd: " + a.CWD() + "\n"})
|
||||
return
|
||||
}
|
||||
dir := strings.Join(args, " ")
|
||||
if err := a.SetCWD(dir); err != nil {
|
||||
a.emit(agent.Event{Role: "info", Content: "error: " + err.Error() + "\n"})
|
||||
return
|
||||
}
|
||||
a.emit(agent.Event{Role: "info", Content: "cwd: " + dir + "\n"})
|
||||
},
|
||||
|
||||
"/skills": func(args []string) { listFromHandler("skills") },
|
||||
"/tools": func(args []string) { listFromHandler("tools") },
|
||||
|
||||
"/help": func(args []string) {
|
||||
lines := []string{
|
||||
"Available commands:",
|
||||
" /agents - list available agent configs",
|
||||
" /sessions [-a] - list saved sessions (current cwd; -a for all)",
|
||||
" /save <name> - save session as a named checkpoint",
|
||||
" /resume <id> - resume a saved session",
|
||||
" /agent [name] - show or switch active agent",
|
||||
" /backend [type] - show current backend, or switch to <type>",
|
||||
" /model [name] - show current model, or switch to <name>",
|
||||
" /models - list available models",
|
||||
" /maxsteps [n] - show or set max tool-call steps (0 = unlimited)",
|
||||
" /skills - list available skills",
|
||||
" /tools - list available tools",
|
||||
" /cwd [path] - show or change working directory",
|
||||
" /i <prompt> - inject prompt into the running turn",
|
||||
" /irw <prompt> - rewrite the pending inject",
|
||||
" /compact - summarize conversation and compact context",
|
||||
" /context - show context size and message breakdown",
|
||||
" /cost - show last turn and session cost",
|
||||
" /usage - show token usage and context percentage",
|
||||
" /history - dump bounded message history",
|
||||
" /clear - clear session",
|
||||
" /sp - show rendered system prompt",
|
||||
" /help - show this help",
|
||||
}
|
||||
for _, l := range lines {
|
||||
a.emit(agent.Event{Role: "info", Content: l + "\n"})
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
fn, ok := cmds[cmd]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
a.emit(agent.Event{Role: "info", Content: "\n"})
|
||||
fn(args)
|
||||
return true
|
||||
}
|
||||
|
|
@ -185,15 +185,13 @@ func (a *Session) SetEnv(key, value string) {
|
|||
a.r.SetEnv(key, value)
|
||||
}
|
||||
|
||||
// Submit processes user input: dispatches commands, delegates turns to agent.
|
||||
// Submit forwards user input to the agent for processing.
|
||||
// Slash commands are handled by the agent; session-level operations
|
||||
// are issued via the ctl file, not via prompt.
|
||||
func (a *Session) Submit(ctx context.Context, input string) {
|
||||
a.log.Debug("Submit() input_len=%d running=%v", len(input), a.IsRunning())
|
||||
if input == "" {
|
||||
return
|
||||
}
|
||||
if a.handleCommand(ctx, input) {
|
||||
return
|
||||
}
|
||||
a.r.Submit(ctx, input)
|
||||
}
|
||||
|
||||
|
|
|
|||
Reference in New Issue