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
|
- `/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
|
- `/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
|
- `/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
|
- `/help` — show all supported commands
|
||||||
- `/clear` — reset the conversation to just the system prompt
|
- `/clear` — reset the conversation to just the system prompt
|
||||||
- `/quit` — exit
|
- `/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).
|
1. Read system prompt from `system.txt` (default if missing).
|
||||||
2. Read model parameters from `model.cfg` (`key=value` format).
|
2. Read model parameters from `model.cfg` (`key=value` format).
|
||||||
3. Prepare a new message list with the system prompt (`role: "system"`).
|
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.
|
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 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`.
|
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.
|
6. Append user prompt to `messages` (`role: "user"`), run `AL(cfg, messages)`, and go to step 5.
|
||||||
|
|
||||||
### Agentic loop (`AL(cfg, messages)`) function
|
### Agentic loop (`AL(cfg, messages)`) function
|
||||||
|
|||||||
@@ -1241,7 +1241,18 @@ func main() {
|
|||||||
fmt.Println(c("Error: file '"+p+"' not found.", 31))
|
fmt.Println(c("Error: file '"+p+"' not found.", 31))
|
||||||
os.Exit(1)
|
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 {
|
if msgs, err = AL(&cfg, msgs, sp, 0); err != nil {
|
||||||
fmt.Println(c("[error: "+err.Error()+"]", 31))
|
fmt.Println(c("[error: "+err.Error()+"]", 31))
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -1338,9 +1349,18 @@ func main() {
|
|||||||
fmt.Println(c("Usage: /cfg <param> [val]", 31))
|
fmt.Println(c("Usage: /cfg <param> [val]", 31))
|
||||||
}
|
}
|
||||||
continue
|
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":
|
case u == "/help":
|
||||||
fmt.Println(c("Bantam commands:", 1, 36))
|
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])
|
fmt.Println(c(fmt.Sprintf(" %-15s", kv[0]), 1, 32) + kv[1])
|
||||||
}
|
}
|
||||||
continue
|
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