added command execution via !
This commit is contained in:
@@ -60,6 +60,7 @@ All implementations read the same `model.cfg` and `system.txt` from the current
|
||||
- `/load <id>` — load a saved session (exact id or unique prefix) and continue from there
|
||||
- `/compact` — summarize the conversation with the LLM and compact the context down to just the system message plus the summary
|
||||
- `/cfg <param> [val]` — inspect or update a configuration parameter in `model.cfg` live
|
||||
- `!<cmd>` — execute a shell command directly through `shell_exec` without adding the result to the conversation context (Go port)
|
||||
- `/help` — show all supported commands
|
||||
- `/clear` — reset the conversation to just the system prompt
|
||||
- `/quit` — exit
|
||||
@@ -92,8 +93,8 @@ Using these rules, everyone can build their own copy of Bantam from scratch in l
|
||||
1. Read system prompt from `system.txt` (default if missing).
|
||||
2. Read model parameters from `model.cfg` (`key=value` format).
|
||||
3. Prepare a new message list with the system prompt (`role: "system"`).
|
||||
4. Read the first command-line parameter. If non-empty, read user prompt from the specified file, append to `messages` (`role: "user"`), run `AL(cfg, messages)`, and exit.
|
||||
5. Read user prompt from standard input (with `readline` line editing and history in `~/.bantam_history`; **Ctrl+J** inserts a real newline into the line being edited). If equal to `/quit` or EOF, exit. If equal to `/clear`, reset `messages` to step 3 and return to step 5. If equal to `/save`, write the whole `messages` array to `~/.bantam/sessions/<id>.json` (with an auto-generated summary) and return to step 5. If equal to `/list`, print saved sessions and their summaries and return to step 5. If starting with `/load`, replace `messages` with the saved session's messages (by exact id or unique prefix) and return to step 5. If equal to `/compact`, ask the LLM to summarize the conversation, replace `messages` with `[system, summary-user-message]`, and return to step 5. If starting with `/cfg`, display the current value (`/cfg <param>`) or update `model.cfg` live (`/cfg <param> <val>`) and return to step 5. If equal to `/help`, print the command list and return to step 5. After every user turn and on exit, auto-save `messages` to `~/.bantam/sessions/autosave.json`.
|
||||
4. Read the first command-line parameter. If non-empty, read user prompt from the specified file. If prefixed with `!`, execute the shell command directly via `shell_exec` and exit. Otherwise, append to `messages` (`role: "user"`), run `AL(cfg, messages)`, and exit.
|
||||
5. Read user prompt from standard input (with `readline` line editing and history in `~/.bantam_history`; **Ctrl+J** inserts a real newline into the line being edited). If equal to `/quit` or EOF, exit. If equal to `/clear`, reset `messages` to step 3 and return to step 5. If equal to `/save`, write the whole `messages` array to `~/.bantam/sessions/<id>.json` (with an auto-generated summary) and return to step 5. If equal to `/list`, print saved sessions and their summaries and return to step 5. If starting with `/load`, replace `messages` with the saved session's messages (by exact id or unique prefix) and return to step 5. If equal to `/compact`, ask the LLM to summarize the conversation, replace `messages` with `[system, summary-user-message]`, and return to step 5. If starting with `/cfg`, display the current value (`/cfg <param>`) or update `model.cfg` live (`/cfg <param> <val>`) and return to step 5. If starting with `!`, execute the command directly via `shell_exec` without adding the result to `messages` and return to step 5. If equal to `/help`, print the command list and return to step 5. After every user turn and on exit, auto-save `messages` to `~/.bantam/sessions/autosave.json`.
|
||||
6. Append user prompt to `messages` (`role: "user"`), run `AL(cfg, messages)`, and go to step 5.
|
||||
|
||||
### Agentic loop (`AL(cfg, messages)`) function
|
||||
|
||||
@@ -1241,7 +1241,18 @@ func main() {
|
||||
fmt.Println(c("Error: file '"+p+"' not found.", 31))
|
||||
os.Exit(1)
|
||||
}
|
||||
msgs = append(msgs, Message{Role: "user", Content: strp(strings.TrimSpace(string(data)))})
|
||||
u := strings.TrimSpace(string(data))
|
||||
if strings.HasPrefix(u, "!") {
|
||||
cmd := strings.TrimSpace(strings.TrimPrefix(u, "!"))
|
||||
if cmd != "" {
|
||||
astr, _ := json.Marshal(map[string]string{"command": cmd})
|
||||
fmt.Println(c(fmt.Sprintf("[tool call: shell_exec(%s)]", string(astr)), 33))
|
||||
res := shell(cmd, cfg.ShellTimeout)
|
||||
fmt.Println(c("[tool result: shell_exec]", 32) + "\n" + c(res, 2))
|
||||
}
|
||||
return
|
||||
}
|
||||
msgs = append(msgs, Message{Role: "user", Content: strp(u)})
|
||||
if msgs, err = AL(&cfg, msgs, sp, 0); err != nil {
|
||||
fmt.Println(c("[error: "+err.Error()+"]", 31))
|
||||
os.Exit(1)
|
||||
@@ -1338,9 +1349,18 @@ func main() {
|
||||
fmt.Println(c("Usage: /cfg <param> [val]", 31))
|
||||
}
|
||||
continue
|
||||
case strings.HasPrefix(u, "!"):
|
||||
cmd := strings.TrimSpace(strings.TrimPrefix(u, "!"))
|
||||
if cmd != "" {
|
||||
astr, _ := json.Marshal(map[string]string{"command": cmd})
|
||||
fmt.Println(c(fmt.Sprintf("[tool call: shell_exec(%s)]", string(astr)), 33))
|
||||
res := shell(cmd, cfg.ShellTimeout)
|
||||
fmt.Println(c("[tool result: shell_exec]", 32) + "\n" + c(res, 2))
|
||||
}
|
||||
continue
|
||||
case u == "/help":
|
||||
fmt.Println(c("Bantam commands:", 1, 36))
|
||||
for _, kv := range [][2]string{{"/quit", "exit"}, {"/clear", "reset to system prompt"}, {"/save", "save session"}, {"/list", "list sessions"}, {"/load <id>", "load session"}, {"/compact", "compact context"}, {"/cfg <k> [v]", "get/set config"}, {"/help", "show help"}} {
|
||||
for _, kv := range [][2]string{{"/quit", "exit"}, {"/clear", "reset to system prompt"}, {"/save", "save session"}, {"/list", "list sessions"}, {"/load <id>", "load session"}, {"/compact", "compact context"}, {"/cfg <k> [v]", "get/set config"}, {"!<cmd>", "run shell command directly"}, {"/help", "show help"}} {
|
||||
fmt.Println(c(fmt.Sprintf(" %-15s", kv[0]), 1, 32) + kv[1])
|
||||
}
|
||||
continue
|
||||
|
||||
@@ -1589,5 +1589,21 @@ func TestRenderMDNoColorFallback(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirectShellExecution(t *testing.T) {
|
||||
cmd := "echo direct_exec_test"
|
||||
res := shell(cmd, 10)
|
||||
if !strings.HasPrefix(res, "direct_exec_test") || !strings.Contains(res, "exit: 0") {
|
||||
t.Errorf("direct shell exec failed, got %q", res)
|
||||
}
|
||||
|
||||
// Verify that command stripping preserves arguments
|
||||
u := "! echo hello world"
|
||||
stripped := strings.TrimSpace(strings.TrimPrefix(u, "!"))
|
||||
if stripped != "echo hello world" {
|
||||
t.Errorf("stripped command = %q, want %q", stripped, "echo hello world")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user