Unverified Commit 53a5a9e9 authored by Parth Sareen's avatar Parth Sareen Committed by GitHub
Browse files

x: redesign agent UI with minimal styling (#13650)

parent e30e08a7
...@@ -37,6 +37,25 @@ var optionLabels = []string{ ...@@ -37,6 +37,25 @@ var optionLabels = []string{
"3. Deny", "3. Deny",
} }
// toolDisplayNames maps internal tool names to human-readable display names.
var toolDisplayNames = map[string]string{
"bash": "Bash",
"web_search": "Web Search",
}
// ToolDisplayName returns the human-readable display name for a tool.
func ToolDisplayName(toolName string) string {
if displayName, ok := toolDisplayNames[toolName]; ok {
return displayName
}
// Default: capitalize first letter and replace underscores with spaces
name := strings.ReplaceAll(toolName, "_", " ")
if len(name) > 0 {
return strings.ToUpper(name[:1]) + name[1:]
}
return toolName
}
// autoAllowCommands are commands that are always allowed without prompting. // autoAllowCommands are commands that are always allowed without prompting.
// These are zero-risk, read-only commands. // These are zero-risk, read-only commands.
var autoAllowCommands = map[string]bool{ var autoAllowCommands = map[string]bool{
...@@ -509,11 +528,12 @@ func (a *ApprovalManager) RequestApproval(toolName string, args map[string]any) ...@@ -509,11 +528,12 @@ func (a *ApprovalManager) RequestApproval(toolName string, args map[string]any)
// formatToolDisplay creates the display string for a tool call. // formatToolDisplay creates the display string for a tool call.
func formatToolDisplay(toolName string, args map[string]any) string { func formatToolDisplay(toolName string, args map[string]any) string {
var sb strings.Builder var sb strings.Builder
displayName := ToolDisplayName(toolName)
// For bash, show command directly // For bash, show command directly
if toolName == "bash" { if toolName == "bash" {
if cmd, ok := args["command"].(string); ok { if cmd, ok := args["command"].(string); ok {
sb.WriteString(fmt.Sprintf("Tool: %s\n", toolName)) sb.WriteString(fmt.Sprintf("Tool: %s\n", displayName))
sb.WriteString(fmt.Sprintf("Command: %s", cmd)) sb.WriteString(fmt.Sprintf("Command: %s", cmd))
return sb.String() return sb.String()
} }
...@@ -522,7 +542,7 @@ func formatToolDisplay(toolName string, args map[string]any) string { ...@@ -522,7 +542,7 @@ func formatToolDisplay(toolName string, args map[string]any) string {
// For web search, show query and internet notice // For web search, show query and internet notice
if toolName == "web_search" { if toolName == "web_search" {
if query, ok := args["query"].(string); ok { if query, ok := args["query"].(string); ok {
sb.WriteString(fmt.Sprintf("Tool: %s\n", toolName)) sb.WriteString(fmt.Sprintf("Tool: %s\n", displayName))
sb.WriteString(fmt.Sprintf("Query: %s\n", query)) sb.WriteString(fmt.Sprintf("Query: %s\n", query))
sb.WriteString("Uses internet via ollama.com") sb.WriteString("Uses internet via ollama.com")
return sb.String() return sb.String()
...@@ -530,7 +550,7 @@ func formatToolDisplay(toolName string, args map[string]any) string { ...@@ -530,7 +550,7 @@ func formatToolDisplay(toolName string, args map[string]any) string {
} }
// Generic display // Generic display
sb.WriteString(fmt.Sprintf("Tool: %s", toolName)) sb.WriteString(fmt.Sprintf("Tool: %s", displayName))
if len(args) > 0 { if len(args) > 0 {
sb.WriteString("\nArguments: ") sb.WriteString("\nArguments: ")
first := true first := true
...@@ -724,7 +744,7 @@ func wrapText(text string, maxWidth int) []string { ...@@ -724,7 +744,7 @@ func wrapText(text string, maxWidth int) []string {
// getHintLines returns the hint text wrapped to terminal width // getHintLines returns the hint text wrapped to terminal width
func getHintLines(state *selectorState) []string { func getHintLines(state *selectorState) []string {
hint := "↑/↓ navigate, Enter confirm, 1-3 quick, Ctrl+C cancel" hint := "up/down select, enter confirm, 1-3 quick select, ctrl+c cancel"
if state.termWidth >= len(hint)+1 { if state.termWidth >= len(hint)+1 {
return []string{hint} return []string{hint}
} }
...@@ -734,86 +754,60 @@ func getHintLines(state *selectorState) []string { ...@@ -734,86 +754,60 @@ func getHintLines(state *selectorState) []string {
// calculateTotalLines calculates how many lines the selector will use // calculateTotalLines calculates how many lines the selector will use
func calculateTotalLines(state *selectorState) int { func calculateTotalLines(state *selectorState) int {
toolLines := wrapText(state.toolDisplay, state.innerWidth) toolLines := strings.Split(state.toolDisplay, "\n")
hintLines := getHintLines(state) hintLines := getHintLines(state)
// top border + (warning line if applicable) + tool lines + separator + options + bottom border + hint lines // warning line (if applicable) + tool lines + blank line + options + blank line + hint lines
warningLines := 0 warningLines := 0
if state.isWarning { if state.isWarning {
warningLines = 1 warningLines = 2 // warning line + blank line after
} }
return 1 + warningLines + len(toolLines) + 1 + len(optionLabels) + 1 + len(hintLines) return warningLines + len(toolLines) + 1 + len(optionLabels) + 1 + len(hintLines)
} }
// renderSelectorBox renders the complete selector box // renderSelectorBox renders the selector (minimal, no box)
func renderSelectorBox(state *selectorState) { func renderSelectorBox(state *selectorState) {
toolLines := wrapText(state.toolDisplay, state.innerWidth) toolLines := strings.Split(state.toolDisplay, "\n")
hintLines := getHintLines(state) hintLines := getHintLines(state)
// Use red for warning (outside cwd), cyan for normal // Draw warning line if needed
boxColor := "\033[36m" // cyan
if state.isWarning { if state.isWarning {
boxColor = "\033[91m" // bright red fmt.Fprintf(os.Stderr, "\033[1mwarning:\033[0m command targets paths outside project\033[K\r\n")
fmt.Fprintf(os.Stderr, "\033[K\r\n") // blank line after warning
} }
// Draw box top // Draw tool info (plain white)
fmt.Fprintf(os.Stderr, "%s┌%s┐\033[0m\033[K\r\n", boxColor, strings.Repeat("─", state.boxWidth-2))
// Draw warning line if needed (inside the box)
if state.isWarning {
warning := "!! OUTSIDE PROJECT !!"
padding := (state.innerWidth - len(warning)) / 2
if padding < 0 {
padding = 0
}
fmt.Fprintf(os.Stderr, "%s│\033[0m %s%s%s %s│\033[0m\033[K\r\n", boxColor,
strings.Repeat(" ", padding), warning, strings.Repeat(" ", state.innerWidth-len(warning)-padding), boxColor)
}
// Draw tool info
for _, line := range toolLines { for _, line := range toolLines {
fmt.Fprintf(os.Stderr, "%s\033[0m %-*s %s│\033[0m\033[K\r\n", boxColor, state.innerWidth, line, boxColor) fmt.Fprintf(os.Stderr, "%s\033[K\r\n", line)
} }
// Draw separator // Blank line separator
fmt.Fprintf(os.Stderr, "%s├%s┤\033[0m\033[K\r\n", boxColor, strings.Repeat("─", state.boxWidth-2)) fmt.Fprintf(os.Stderr, "\033[K\r\n")
// Draw options with numbers (Deny option includes reason input) // Draw options
for i, label := range optionLabels { for i, label := range optionLabels {
if i == 2 { // Deny option - show with reason input beside it if i == 2 { // Deny option with input
denyLabel := "3. Deny: " denyLabel := "3. Deny: "
availableWidth := state.innerWidth - 2 - len(denyLabel)
if availableWidth < 5 {
availableWidth = 5
}
inputDisplay := state.denyReason inputDisplay := state.denyReason
if len(inputDisplay) > availableWidth {
inputDisplay = inputDisplay[len(inputDisplay)-availableWidth:]
}
if i == state.selected { if i == state.selected {
fmt.Fprintf(os.Stderr, "%s│\033[0m \033[1;32m> %s\033[0m%-*s %s│\033[0m\033[K\r\n", boxColor, denyLabel, availableWidth, inputDisplay, boxColor) fmt.Fprintf(os.Stderr, " \033[1m%s\033[0m%s\033[K\r\n", denyLabel, inputDisplay)
} else { } else {
fmt.Fprintf(os.Stderr, "%s│\033[0m \033[90m%s\033[0m%-*s %s│\033[0m\033[K\r\n", boxColor, denyLabel, availableWidth, inputDisplay, boxColor) fmt.Fprintf(os.Stderr, " \033[37m%s\033[0m%s\033[K\r\n", denyLabel, inputDisplay)
} }
} else { } else {
displayLabel := label
if len(displayLabel) > state.innerWidth-2 {
displayLabel = displayLabel[:state.innerWidth-5] + "..."
}
if i == state.selected { if i == state.selected {
fmt.Fprintf(os.Stderr, "%s│\033[0m \033[1;32m> %-*s\033[0m %s\033[0m\033[K\r\n", boxColor, state.innerWidth-2, displayLabel, boxColor) fmt.Fprintf(os.Stderr, " \033[1m%s\033[0m\033[K\r\n", label)
} else { } else {
fmt.Fprintf(os.Stderr, "%s│\033[0m %-*s %s\033[0m\033[K\r\n", boxColor, state.innerWidth-2, displayLabel, boxColor) fmt.Fprintf(os.Stderr, " \033[37m%s\033[0m\033[K\r\n", label)
} }
} }
} }
// Draw box bottom // Blank line before hint
fmt.Fprintf(os.Stderr, "%s└%s┘\033[0m\033[K\r\n", boxColor, strings.Repeat("─", state.boxWidth-2)) fmt.Fprintf(os.Stderr, "\033[K\r\n")
// Draw hint (may be multiple lines) // Draw hint (dark grey)
for i, line := range hintLines { for i, line := range hintLines {
if i == len(hintLines)-1 { if i == len(hintLines)-1 {
// Last line - no newline
fmt.Fprintf(os.Stderr, "\033[90m%s\033[0m\033[K", line) fmt.Fprintf(os.Stderr, "\033[90m%s\033[0m\033[K", line)
} else { } else {
fmt.Fprintf(os.Stderr, "\033[90m%s\033[0m\033[K\r\n", line) fmt.Fprintf(os.Stderr, "\033[90m%s\033[0m\033[K\r\n", line)
...@@ -825,50 +819,33 @@ func renderSelectorBox(state *selectorState) { ...@@ -825,50 +819,33 @@ func renderSelectorBox(state *selectorState) {
func updateSelectorOptions(state *selectorState) { func updateSelectorOptions(state *selectorState) {
hintLines := getHintLines(state) hintLines := getHintLines(state)
// Use red for warning (outside cwd), cyan for normal
boxColor := "\033[36m" // cyan
if state.isWarning {
boxColor = "\033[91m" // bright red
}
// Move up to the first option line // Move up to the first option line
// Cursor is at end of last hint line, need to go up: // Cursor is at end of last hint line, need to go up:
// (hint lines - 1) + 1 (bottom border) + numOptions // (hint lines - 1) + 1 (blank line) + numOptions
linesToMove := len(hintLines) - 1 + 1 + len(optionLabels) linesToMove := len(hintLines) - 1 + 1 + len(optionLabels)
fmt.Fprintf(os.Stderr, "\033[%dA\r", linesToMove) fmt.Fprintf(os.Stderr, "\033[%dA\r", linesToMove)
// Redraw options (Deny option includes reason input) // Redraw options
for i, label := range optionLabels { for i, label := range optionLabels {
if i == 2 { // Deny option if i == 2 { // Deny option
denyLabel := "3. Deny: " denyLabel := "3. Deny: "
availableWidth := state.innerWidth - 2 - len(denyLabel)
if availableWidth < 5 {
availableWidth = 5
}
inputDisplay := state.denyReason inputDisplay := state.denyReason
if len(inputDisplay) > availableWidth {
inputDisplay = inputDisplay[len(inputDisplay)-availableWidth:]
}
if i == state.selected { if i == state.selected {
fmt.Fprintf(os.Stderr, "%s│\033[0m \033[1;32m> %s\033[0m%-*s %s│\033[0m\033[K\r\n", boxColor, denyLabel, availableWidth, inputDisplay, boxColor) fmt.Fprintf(os.Stderr, " \033[1m%s\033[0m%s\033[K\r\n", denyLabel, inputDisplay)
} else { } else {
fmt.Fprintf(os.Stderr, "%s│\033[0m \033[90m%s\033[0m%-*s %s│\033[0m\033[K\r\n", boxColor, denyLabel, availableWidth, inputDisplay, boxColor) fmt.Fprintf(os.Stderr, " \033[37m%s\033[0m%s\033[K\r\n", denyLabel, inputDisplay)
} }
} else { } else {
displayLabel := label
if len(displayLabel) > state.innerWidth-2 {
displayLabel = displayLabel[:state.innerWidth-5] + "..."
}
if i == state.selected { if i == state.selected {
fmt.Fprintf(os.Stderr, "%s│\033[0m \033[1;32m> %-*s\033[0m %s\033[0m\033[K\r\n", boxColor, state.innerWidth-2, displayLabel, boxColor) fmt.Fprintf(os.Stderr, " \033[1m%s\033[0m\033[K\r\n", label)
} else { } else {
fmt.Fprintf(os.Stderr, "%s│\033[0m %-*s %s\033[0m\033[K\r\n", boxColor, state.innerWidth-2, displayLabel, boxColor) fmt.Fprintf(os.Stderr, " \033[37m%s\033[0m\033[K\r\n", label)
} }
} }
} }
// Redraw bottom and hint // Blank line + hint
fmt.Fprintf(os.Stderr, "%s└%s┘\033[0m\033[K\r\n", boxColor, strings.Repeat("─", state.boxWidth-2)) fmt.Fprintf(os.Stderr, "\033[K\r\n")
for i, line := range hintLines { for i, line := range hintLines {
if i == len(hintLines)-1 { if i == len(hintLines)-1 {
fmt.Fprintf(os.Stderr, "\033[90m%s\033[0m\033[K", line) fmt.Fprintf(os.Stderr, "\033[90m%s\033[0m\033[K", line)
...@@ -882,36 +859,23 @@ func updateSelectorOptions(state *selectorState) { ...@@ -882,36 +859,23 @@ func updateSelectorOptions(state *selectorState) {
func updateReasonInput(state *selectorState) { func updateReasonInput(state *selectorState) {
hintLines := getHintLines(state) hintLines := getHintLines(state)
// Use red for warning (outside cwd), cyan for normal
boxColor := "\033[36m" // cyan
if state.isWarning {
boxColor = "\033[91m" // bright red
}
// Move up to the Deny line (3rd option, index 2) // Move up to the Deny line (3rd option, index 2)
// Cursor is at end of last hint line, need to go up: // Cursor is at end of last hint line, need to go up:
// (hint lines - 1) + 1 (bottom border) + 1 (Deny is last option) // (hint lines - 1) + 1 (blank line) + 1 (Deny is last option)
linesToMove := len(hintLines) - 1 + 1 + 1 linesToMove := len(hintLines) - 1 + 1 + 1
fmt.Fprintf(os.Stderr, "\033[%dA\r", linesToMove) fmt.Fprintf(os.Stderr, "\033[%dA\r", linesToMove)
// Redraw Deny line with reason // Redraw Deny line with reason
denyLabel := "3. Deny: " denyLabel := "3. Deny: "
availableWidth := state.innerWidth - 2 - len(denyLabel)
if availableWidth < 5 {
availableWidth = 5
}
inputDisplay := state.denyReason inputDisplay := state.denyReason
if len(inputDisplay) > availableWidth {
inputDisplay = inputDisplay[len(inputDisplay)-availableWidth:]
}
if state.selected == 2 { if state.selected == 2 {
fmt.Fprintf(os.Stderr, "%s│\033[0m \033[1;32m> %s\033[0m%-*s %s│\033[0m\033[K\r\n", boxColor, denyLabel, availableWidth, inputDisplay, boxColor) fmt.Fprintf(os.Stderr, " \033[1m%s\033[0m%s\033[K\r\n", denyLabel, inputDisplay)
} else { } else {
fmt.Fprintf(os.Stderr, "%s│\033[0m \033[90m%s\033[0m%-*s %s│\033[0m\033[K\r\n", boxColor, denyLabel, availableWidth, inputDisplay, boxColor) fmt.Fprintf(os.Stderr, " \033[37m%s\033[0m%s\033[K\r\n", denyLabel, inputDisplay)
} }
// Redraw bottom and hint // Blank line + hint
fmt.Fprintf(os.Stderr, "%s└%s┘\033[0m\033[K\r\n", boxColor, strings.Repeat("─", state.boxWidth-2)) fmt.Fprintf(os.Stderr, "\033[K\r\n")
for i, line := range hintLines { for i, line := range hintLines {
if i == len(hintLines)-1 { if i == len(hintLines)-1 {
fmt.Fprintf(os.Stderr, "\033[90m%s\033[0m\033[K", line) fmt.Fprintf(os.Stderr, "\033[90m%s\033[0m\033[K", line)
...@@ -935,11 +899,10 @@ func clearSelectorBox(state *selectorState) { ...@@ -935,11 +899,10 @@ func clearSelectorBox(state *selectorState) {
// fallbackApproval handles approval when terminal control isn't available. // fallbackApproval handles approval when terminal control isn't available.
func (a *ApprovalManager) fallbackApproval(toolDisplay string) (ApprovalResult, error) { func (a *ApprovalManager) fallbackApproval(toolDisplay string) (ApprovalResult, error) {
fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr)
fmt.Fprintln(os.Stderr, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
fmt.Fprintln(os.Stderr, toolDisplay) fmt.Fprintln(os.Stderr, toolDisplay)
fmt.Fprintln(os.Stderr, "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━") fmt.Fprintln(os.Stderr)
fmt.Fprintln(os.Stderr, "[1] Execute once [2] Always allow [3] Deny") fmt.Fprintln(os.Stderr, "[1] Execute once [2] Always allow [3] Deny")
fmt.Fprint(os.Stderr, "Choice: ") fmt.Fprint(os.Stderr, "choice: ")
var input string var input string
fmt.Scanln(&input) fmt.Scanln(&input)
...@@ -982,19 +945,16 @@ func (a *ApprovalManager) AllowedTools() []string { ...@@ -982,19 +945,16 @@ func (a *ApprovalManager) AllowedTools() []string {
// FormatApprovalResult returns a formatted string showing the approval result. // FormatApprovalResult returns a formatted string showing the approval result.
func FormatApprovalResult(toolName string, args map[string]any, result ApprovalResult) string { func FormatApprovalResult(toolName string, args map[string]any, result ApprovalResult) string {
var status string var label string
var icon string displayName := ToolDisplayName(toolName)
switch result.Decision { switch result.Decision {
case ApprovalOnce: case ApprovalOnce:
status = "Approved" label = "approved"
icon = "\033[32m✓\033[0m"
case ApprovalAlways: case ApprovalAlways:
status = "Always allowed" label = "always allowed"
icon = "\033[32m✓\033[0m"
case ApprovalDeny: case ApprovalDeny:
status = "Denied" label = "denied"
icon = "\033[31m✗\033[0m"
} }
// Format based on tool type // Format based on tool type
...@@ -1004,7 +964,7 @@ func FormatApprovalResult(toolName string, args map[string]any, result ApprovalR ...@@ -1004,7 +964,7 @@ func FormatApprovalResult(toolName string, args map[string]any, result ApprovalR
if len(cmd) > 40 { if len(cmd) > 40 {
cmd = cmd[:37] + "..." cmd = cmd[:37] + "..."
} }
return fmt.Sprintf("▶ bash: %s [%s] %s", cmd, status, icon) return fmt.Sprintf("\033[1m%s:\033[0m %s: %s", label, displayName, cmd)
} }
} }
...@@ -1014,11 +974,11 @@ func FormatApprovalResult(toolName string, args map[string]any, result ApprovalR ...@@ -1014,11 +974,11 @@ func FormatApprovalResult(toolName string, args map[string]any, result ApprovalR
if len(query) > 40 { if len(query) > 40 {
query = query[:37] + "..." query = query[:37] + "..."
} }
return fmt.Sprintf("▶ web_search: %s [%s] %s", query, status, icon) return fmt.Sprintf("\033[1m%s:\033[0m %s: %s", label, displayName, query)
} }
} }
return fmt.Sprintf("▶ %s [%s] %s", toolName, status, icon) return fmt.Sprintf("\033[1m%s:\033[0m %s", label, displayName)
} }
// FormatDenyResult returns the tool result message when a tool is denied. // FormatDenyResult returns the tool result message when a tool is denied.
...@@ -1049,15 +1009,14 @@ func PromptYesNo(question string) (bool, error) { ...@@ -1049,15 +1009,14 @@ func PromptYesNo(question string) (bool, error) {
renderYesNo := func() { renderYesNo := func() {
// Move to start of line and clear // Move to start of line and clear
fmt.Fprintf(os.Stderr, "\r\033[K") fmt.Fprintf(os.Stderr, "\r\033[K")
fmt.Fprintf(os.Stderr, "\033[36m%s\033[0m ", question) fmt.Fprintf(os.Stderr, "%s ", question)
for i, opt := range options { for i, opt := range options {
if i == selected { if i == selected {
fmt.Fprintf(os.Stderr, "\033[1;32m[%s]\033[0m ", opt) fmt.Fprintf(os.Stderr, "\033[1m%s\033[0m ", opt)
} else { } else {
fmt.Fprintf(os.Stderr, "\033[90m %s \033[0m ", opt) fmt.Fprintf(os.Stderr, "\033[37m%s\033[0m ", opt)
} }
} }
fmt.Fprintf(os.Stderr, "\033[90m(←/→ or y/n, Enter to confirm)\033[0m")
} }
renderYesNo() renderYesNo()
......
...@@ -91,8 +91,8 @@ func waitForOllamaSignin(ctx context.Context) error { ...@@ -91,8 +91,8 @@ func waitForOllamaSignin(ctx context.Context) error {
var aErr api.AuthorizationError var aErr api.AuthorizationError
if errors.As(err, &aErr) && aErr.SigninURL != "" { if errors.As(err, &aErr) && aErr.SigninURL != "" {
fmt.Fprintf(os.Stderr, "\n To sign in, navigate to:\n") fmt.Fprintf(os.Stderr, "\n To sign in, navigate to:\n")
fmt.Fprintf(os.Stderr, " \033[36m%s\033[0m\n\n", aErr.SigninURL) fmt.Fprintf(os.Stderr, " %s\n\n", aErr.SigninURL)
fmt.Fprintf(os.Stderr, " \033[90mWaiting for sign in to complete...\033[0m") fmt.Fprintf(os.Stderr, " \033[90mwaiting for sign in to complete...\033[0m")
// Poll until auth succeeds // Poll until auth succeeds
ticker := time.NewTicker(2 * time.Second) ticker := time.NewTicker(2 * time.Second)
...@@ -106,7 +106,7 @@ func waitForOllamaSignin(ctx context.Context) error { ...@@ -106,7 +106,7 @@ func waitForOllamaSignin(ctx context.Context) error {
case <-ticker.C: case <-ticker.C:
user, whoamiErr := client.Whoami(ctx) user, whoamiErr := client.Whoami(ctx)
if whoamiErr == nil && user != nil && user.Name != "" { if whoamiErr == nil && user != nil && user.Name != "" {
fmt.Fprintf(os.Stderr, "\r\033[K \033[32mSigned in as %s\033[0m\n", user.Name) fmt.Fprintf(os.Stderr, "\r\033[K\033[A\r\033[K \033[1msigned in:\033[0m %s\n", user.Name)
return nil return nil
} }
// Still waiting, show dot // Still waiting, show dot
...@@ -264,12 +264,12 @@ func Chat(ctx context.Context, opts RunOptions) (*api.Message, error) { ...@@ -264,12 +264,12 @@ func Chat(ctx context.Context, opts RunOptions) (*api.Message, error) {
var authErr api.AuthorizationError var authErr api.AuthorizationError
if errors.As(err, &authErr) { if errors.As(err, &authErr) {
p.StopAndClear() p.StopAndClear()
fmt.Fprintf(os.Stderr, "\033[33mAuthentication required to use this cloud model.\033[0m\n") fmt.Fprintf(os.Stderr, "\033[1mauth required:\033[0m cloud model requires authentication\n")
result, promptErr := agent.PromptYesNo("Sign in to Ollama?") result, promptErr := agent.PromptYesNo("Sign in to Ollama?")
if promptErr == nil && result { if promptErr == nil && result {
if signinErr := waitForOllamaSignin(ctx); signinErr == nil { if signinErr := waitForOllamaSignin(ctx); signinErr == nil {
// Retry the chat request // Retry the chat request
fmt.Fprintf(os.Stderr, "\033[90mRetrying...\033[0m\n") fmt.Fprintf(os.Stderr, "\033[90mretrying...\033[0m\n")
continue // Retry the loop continue // Retry the loop
} }
} }
...@@ -283,11 +283,11 @@ func Chat(ctx context.Context, opts RunOptions) (*api.Message, error) { ...@@ -283,11 +283,11 @@ func Chat(ctx context.Context, opts RunOptions) (*api.Message, error) {
p.StopAndClear() p.StopAndClear()
if consecutiveErrors >= 3 { if consecutiveErrors >= 3 {
fmt.Fprintf(os.Stderr, "\033[31m✗ Too many consecutive errors, giving up\033[0m\n") fmt.Fprintf(os.Stderr, "\033[1merror:\033[0m too many consecutive errors, giving up\n")
return nil, fmt.Errorf("too many consecutive server errors: %s", statusErr.ErrorMessage) return nil, fmt.Errorf("too many consecutive server errors: %s", statusErr.ErrorMessage)
} }
fmt.Fprintf(os.Stderr, "\033[33m⚠ Server error (attempt %d/3): %s\033[0m\n", consecutiveErrors, statusErr.ErrorMessage) fmt.Fprintf(os.Stderr, "\033[1mwarning:\033[0m server error (attempt %d/3): %s\n", consecutiveErrors, statusErr.ErrorMessage)
// Include both the model's response and the error so it can learn // Include both the model's response and the error so it can learn
assistantContent := fullResponse.String() assistantContent := fullResponse.String()
...@@ -353,8 +353,8 @@ func Chat(ctx context.Context, opts RunOptions) (*api.Message, error) { ...@@ -353,8 +353,8 @@ func Chat(ctx context.Context, opts RunOptions) (*api.Message, error) {
if cmd, ok := args["command"].(string); ok { if cmd, ok := args["command"].(string); ok {
// Check if command is denied (dangerous pattern) // Check if command is denied (dangerous pattern)
if denied, pattern := agent.IsDenied(cmd); denied { if denied, pattern := agent.IsDenied(cmd); denied {
fmt.Fprintf(os.Stderr, "\033[91m✗ Blocked: %s\033[0m\n", formatToolShort(toolName, args)) fmt.Fprintf(os.Stderr, "\033[1mblocked:\033[0m %s\n", formatToolShort(toolName, args))
fmt.Fprintf(os.Stderr, "\033[91m Matches dangerous pattern: %s\033[0m\n", pattern) fmt.Fprintf(os.Stderr, " matches dangerous pattern: %s\n", pattern)
toolResults = append(toolResults, api.Message{ toolResults = append(toolResults, api.Message{
Role: "tool", Role: "tool",
Content: agent.FormatDeniedResult(cmd, pattern), Content: agent.FormatDeniedResult(cmd, pattern),
...@@ -365,7 +365,7 @@ func Chat(ctx context.Context, opts RunOptions) (*api.Message, error) { ...@@ -365,7 +365,7 @@ func Chat(ctx context.Context, opts RunOptions) (*api.Message, error) {
// Check if command is auto-allowed (safe command) // Check if command is auto-allowed (safe command)
if agent.IsAutoAllowed(cmd) { if agent.IsAutoAllowed(cmd) {
fmt.Fprintf(os.Stderr, "\033[90m▶ Auto-allowed: %s\033[0m\n", formatToolShort(toolName, args)) fmt.Fprintf(os.Stderr, "\033[1mauto-allowed:\033[0m %s\n", formatToolShort(toolName, args))
skipApproval = true skipApproval = true
} }
} }
...@@ -375,7 +375,7 @@ func Chat(ctx context.Context, opts RunOptions) (*api.Message, error) { ...@@ -375,7 +375,7 @@ func Chat(ctx context.Context, opts RunOptions) (*api.Message, error) {
// In yolo mode, skip all approval prompts // In yolo mode, skip all approval prompts
if opts.YoloMode { if opts.YoloMode {
if !skipApproval { if !skipApproval {
fmt.Fprintf(os.Stderr, "\033[90m▶ Running: %s\033[0m\n", formatToolShort(toolName, args)) fmt.Fprintf(os.Stderr, "\033[1mrunning:\033[0m %s\n", formatToolShort(toolName, args))
} }
} else if !skipApproval && !approval.IsAllowed(toolName, args) { } else if !skipApproval && !approval.IsAllowed(toolName, args) {
result, err := approval.RequestApproval(toolName, args) result, err := approval.RequestApproval(toolName, args)
...@@ -405,7 +405,7 @@ func Chat(ctx context.Context, opts RunOptions) (*api.Message, error) { ...@@ -405,7 +405,7 @@ func Chat(ctx context.Context, opts RunOptions) (*api.Message, error) {
} }
} else if !skipApproval { } else if !skipApproval {
// Already allowed - show running indicator // Already allowed - show running indicator
fmt.Fprintf(os.Stderr, "\033[90m▶ Running: %s\033[0m\n", formatToolShort(toolName, args)) fmt.Fprintf(os.Stderr, "\033[1mrunning:\033[0m %s\n", formatToolShort(toolName, args))
} }
// Execute the tool // Execute the tool
...@@ -414,13 +414,13 @@ func Chat(ctx context.Context, opts RunOptions) (*api.Message, error) { ...@@ -414,13 +414,13 @@ func Chat(ctx context.Context, opts RunOptions) (*api.Message, error) {
// Check if web search needs authentication // Check if web search needs authentication
if errors.Is(err, tools.ErrWebSearchAuthRequired) { if errors.Is(err, tools.ErrWebSearchAuthRequired) {
// Prompt user to sign in // Prompt user to sign in
fmt.Fprintf(os.Stderr, "\033[33m Web search requires authentication.\033[0m\n") fmt.Fprintf(os.Stderr, "\033[1mauth required:\033[0m web search requires authentication\n")
result, promptErr := agent.PromptYesNo("Sign in to Ollama?") result, promptErr := agent.PromptYesNo("Sign in to Ollama?")
if promptErr == nil && result { if promptErr == nil && result {
// Get signin URL and wait for auth completion // Get signin URL and wait for auth completion
if signinErr := waitForOllamaSignin(ctx); signinErr == nil { if signinErr := waitForOllamaSignin(ctx); signinErr == nil {
// Retry the web search // Retry the web search
fmt.Fprintf(os.Stderr, "\033[90m Retrying web search...\033[0m\n") fmt.Fprintf(os.Stderr, "\033[90mretrying web search...\033[0m\n")
toolResult, err = toolRegistry.Execute(call) toolResult, err = toolRegistry.Execute(call)
if err == nil { if err == nil {
goto toolSuccess goto toolSuccess
...@@ -428,7 +428,7 @@ func Chat(ctx context.Context, opts RunOptions) (*api.Message, error) { ...@@ -428,7 +428,7 @@ func Chat(ctx context.Context, opts RunOptions) (*api.Message, error) {
} }
} }
} }
fmt.Fprintf(os.Stderr, "\033[31m Error: %v\033[0m\n", err) fmt.Fprintf(os.Stderr, "\033[1merror:\033[0m %v\n", err)
toolResults = append(toolResults, api.Message{ toolResults = append(toolResults, api.Message{
Role: "tool", Role: "tool",
Content: fmt.Sprintf("Error: %v", err), Content: fmt.Sprintf("Error: %v", err),
...@@ -499,17 +499,18 @@ func truncateUTF8(s string, limit int) string { ...@@ -499,17 +499,18 @@ func truncateUTF8(s string, limit int) string {
// formatToolShort returns a short description of a tool call. // formatToolShort returns a short description of a tool call.
func formatToolShort(toolName string, args map[string]any) string { func formatToolShort(toolName string, args map[string]any) string {
displayName := agent.ToolDisplayName(toolName)
if toolName == "bash" { if toolName == "bash" {
if cmd, ok := args["command"].(string); ok { if cmd, ok := args["command"].(string); ok {
return fmt.Sprintf("bash: %s", truncateUTF8(cmd, 50)) return fmt.Sprintf("%s: %s", displayName, truncateUTF8(cmd, 50))
} }
} }
if toolName == "web_search" { if toolName == "web_search" {
if query, ok := args["query"].(string); ok { if query, ok := args["query"].(string); ok {
return fmt.Sprintf("web_search: %s", truncateUTF8(query, 50)) return fmt.Sprintf("%s: %s", displayName, truncateUTF8(query, 50))
} }
} }
return toolName return displayName
} }
// Helper types and functions for display // Helper types and functions for display
...@@ -649,7 +650,7 @@ func GenerateInteractive(cmd *cobra.Command, modelName string, wordWrap bool, op ...@@ -649,7 +650,7 @@ func GenerateInteractive(cmd *cobra.Command, modelName string, wordWrap bool, op
// Check if model supports tools // Check if model supports tools
supportsTools, err := checkModelCapabilities(cmd.Context(), modelName) supportsTools, err := checkModelCapabilities(cmd.Context(), modelName)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "\033[33mWarning: Could not check model capabilities: %v\033[0m\n", err) fmt.Fprintf(os.Stderr, "\033[1mwarning:\033[0m could not check model capabilities: %v\n", err)
supportsTools = false supportsTools = false
} }
...@@ -658,13 +659,13 @@ func GenerateInteractive(cmd *cobra.Command, modelName string, wordWrap bool, op ...@@ -658,13 +659,13 @@ func GenerateInteractive(cmd *cobra.Command, modelName string, wordWrap bool, op
if supportsTools { if supportsTools {
toolRegistry = tools.DefaultRegistry() toolRegistry = tools.DefaultRegistry()
if toolRegistry.Count() > 0 { if toolRegistry.Count() > 0 {
fmt.Fprintf(os.Stderr, "\033[90mTools available: %s\033[0m\n", strings.Join(toolRegistry.Names(), ", ")) fmt.Fprintf(os.Stderr, "\033[90mtools available: %s\033[0m\n", strings.Join(toolRegistry.Names(), ", "))
} }
if yoloMode { if yoloMode {
fmt.Fprintf(os.Stderr, "\033[33m⚠ YOLO mode: All tool approvals will be skipped\033[0m\n") fmt.Fprintf(os.Stderr, "\033[1mwarning:\033[0m yolo mode - all tool approvals will be skipped\n")
} }
} else { } else {
fmt.Fprintf(os.Stderr, "\033[33mNote: Model does not support tools - running in chat-only mode\033[0m\n") fmt.Fprintf(os.Stderr, "\033[1mnote:\033[0m model does not support tools - running in chat-only mode\n")
} }
// Create approval manager for session // Create approval manager for session
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment