Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions internal/agent/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,11 @@ func (a *Agent) SetModel(model string) {
// Model returns the current model codename.
func (a *Agent) Model() string { return a.cfg.Model }

// ToolList returns the registered tools in registration order.
func (a *Agent) ToolList() []tools.ToolInfo {
return a.tools.ToolList()
}

// Provider returns the active backend kind (e.g. "xshellz", "ollama").
func (a *Agent) Provider() string {
if a.cfg.Provider == "" {
Expand Down
24 changes: 24 additions & 0 deletions internal/tools/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@ type FunctionDef struct {
Parameters json.RawMessage `json:"parameters"`
}

// ToolInfo contains human-readable information about a registered tool.
type ToolInfo struct {
Name string
Description string
Mutating bool
}

// Registry holds the tools available to the agent, preserving order.
type Registry struct {
tools map[string]Tool
Expand Down Expand Up @@ -73,6 +80,23 @@ func (r *Registry) Definitions() []Definition {
return defs
}

// ToolList returns the registered tools in registration order.
func (r *Registry) ToolList() []ToolInfo {
out := make([]ToolInfo, 0, len(r.order))

for _, name := range r.order {
t := r.tools[name]

out = append(out, ToolInfo{
Name: t.Name(),
Description: t.Description(),
Mutating: t.Mutating(),
})
}

return out
}

// DefaultRegistry returns the standard coding-agent toolset.
func DefaultRegistry() *Registry {
bg := newBGManager() // shared by bash / bash_output / kill_shell
Expand Down
31 changes: 31 additions & 0 deletions internal/tui/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,7 @@ var slashCmds = []slashCmd{
{"/clear", "reset the conversation"},
{"/purge", "delete ALL saved sessions"},
{"/update", "update to the latest release"},
{"/tools", "list all available tools"},
{"/help", "show detailed help"},
{"/exit", "quit (the session is saved)"},
}
Expand Down Expand Up @@ -2044,6 +2045,25 @@ func (m model) command(input string) (tea.Model, tea.Cmd) {
return m.attachSession(fields)
case "/help":
return m, tea.Printf("%s", renderHelp(m.md, m.width))
case "/tools":
var b strings.Builder

b.WriteString("### Available Tools\n\n")

for _, t := range m.agent.ToolList() {
mode := "Read-only"
if t.Mutating {
mode = "Permission-gated"
}

fmt.Fprintf(&b, "- **%s** (%s) — %s\n",
t.Name,
mode,
t.Description,
)
}

return m, tea.Printf("%s", renderTools(m.md, m.width, b.String()))
case "/learn":
// Run the project-learn task as a turn (writing BORG.md prompts for
// permission like any edit). Same harness config as `borg learn`.
Expand Down Expand Up @@ -2604,6 +2624,17 @@ func renderHelp(md *mdRenderer, width int) string {
return out
}

func renderTools(md *mdRenderer, width int, text string) string {
if md != nil {
return strings.TrimRight(md.render(text, width), "\n")
}
out, err := glamour.Render(text, "dark")
if err != nil {
return text
}
return out
}

// privacyURL is borg's canonical privacy policy.
const privacyURL = "https://www.xshellz.com/privacy-policy"

Expand Down
5 changes: 4 additions & 1 deletion internal/tui/repl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,14 @@ func TestCommandHelpExitUnknownPurge(t *testing.T) {
_, cmd := slash(m, "/help")
require.NotNil(t, cmd)

_, cmd = slash(m, "/tools")
require.NotNil(t, cmd)

mq, _ := slash(m, "/exit")
require.True(t, mq.quitting)

_, cmd = slash(m, "/bogus")
require.NotNil(t, cmd) // prints an "unknown command" line
require.NotNil(t, cmd)

mp, _ := slash(m, "/purge")
require.Equal(t, modeConfirmPurge, mp.mode)
Expand Down
Loading