feat(commands): add /exec command and ! prefix shell execution
Register /exec as a built-in command so shell commands can be run from pico (AI) mode without switching sessions. Also makes ! a universal shell-exec prefix: unknown !foo commands fall back to /exec instead of passing through to the agent. - pkg/commands/cmd_exec.go: new /exec command definition - pkg/commands/runtime.go: add ExecCmd callback to Runtime - pkg/commands/builtin.go: register execCommand() - pkg/commands/executor.go: ! prefix fallback to /exec for unknown commands - pkg/agent/loop.go: wire ExecCmd callback in buildCommandsRuntime() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a93f5a80cd
commit
d17233e195
5 changed files with 59 additions and 0 deletions
|
|
@ -1957,6 +1957,14 @@ func (al *AgentLoop) buildCommandsRuntime(agent *AgentInstance, opts *processOpt
|
|||
return agent.TotalPromptTokens.Load(), agent.TotalCompletionTokens.Load(), agent.TotalRequests.Load()
|
||||
},
|
||||
|
||||
// Shell command execution — wraps executeCmdMode with session context
|
||||
ExecCmd: func(ctx context.Context, command string) (string, error) {
|
||||
if agent == nil {
|
||||
return "", fmt.Errorf("no agent available")
|
||||
}
|
||||
return al.executeCmdMode(ctx, agent, command, opts.SessionKey, opts.Channel, opts.ChatID)
|
||||
},
|
||||
|
||||
// One-shot AI query for /hipico (stays in modeCmd, separate session key)
|
||||
RunOneShot: func(ctx context.Context, message string) (string, error) {
|
||||
if agent == nil {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ func BuiltinDefinitions() []Definition {
|
|||
cmdModeCommand(),
|
||||
picoModeCommand(),
|
||||
hipicoCmnd(),
|
||||
execCommand(),
|
||||
editCommand(),
|
||||
// Info and session management
|
||||
usageCommand(),
|
||||
|
|
|
|||
38
pkg/commands/cmd_exec.go
Normal file
38
pkg/commands/cmd_exec.go
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// execCommand registers /exec (alias: !) for running shell commands.
|
||||
// Available in both pico and cmd mode; in cmd mode bare text is rewritten to /exec
|
||||
// before dispatch so all shell execution flows through this single handler.
|
||||
func execCommand() Definition {
|
||||
return Definition{
|
||||
Name: "exec",
|
||||
Description: "Execute a shell command",
|
||||
Usage: "/exec <command>",
|
||||
Handler: func(ctx context.Context, req Request, rt *Runtime) error {
|
||||
if rt == nil || rt.ExecCmd == nil {
|
||||
return req.Reply(unavailableMsg)
|
||||
}
|
||||
// Strip the leading command token ("/exec", "!exec", or "!" fallback)
|
||||
// and treat the remainder as the shell command to run.
|
||||
args := strings.TrimSpace(req.Text)
|
||||
if idx := strings.IndexAny(args, " \t"); idx >= 0 {
|
||||
args = strings.TrimSpace(args[idx:])
|
||||
} else {
|
||||
args = ""
|
||||
}
|
||||
if args == "" {
|
||||
return req.Reply("Usage: /exec <command>\nExample: /exec ls -la")
|
||||
}
|
||||
result, err := rt.ExecCmd(ctx, args)
|
||||
if err != nil {
|
||||
return req.Reply("Error: " + err.Error())
|
||||
}
|
||||
return req.Reply(result)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package commands
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Outcome int
|
||||
|
|
@ -44,6 +45,14 @@ func (e *Executor) Execute(ctx context.Context, req Request) ExecuteResult {
|
|||
|
||||
def, found := e.reg.Lookup(cmdName)
|
||||
if !found {
|
||||
// "!foo" where foo is not a registered command → fall back to /exec.
|
||||
// This makes ! a universal shell-exec prefix: !ls, !git status, etc.
|
||||
if strings.HasPrefix(strings.TrimSpace(req.Text), "!") {
|
||||
if execDef, ok := e.reg.Lookup("exec"); ok {
|
||||
req.Text = "/exec " + strings.TrimSpace(req.Text)[1:]
|
||||
return e.executeDefinition(ctx, req, execDef)
|
||||
}
|
||||
}
|
||||
return ExecuteResult{Outcome: OutcomePassthrough, Command: cmdName}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ type Runtime struct {
|
|||
// Token and model usage stats
|
||||
GetTokenUsage func() (promptTokens, completionTokens, requests int64)
|
||||
|
||||
// Shell command execution — delegates to the loop's executeCmdMode with proper path/session handling
|
||||
ExecCmd func(ctx context.Context, command string) (string, error)
|
||||
|
||||
// One-shot AI query from cmd mode (/hipico)
|
||||
RunOneShot func(ctx context.Context, message string) (string, error)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue