mirror of
https://github.com/cheikh2shift/godex.git
synced 2026-10-23 11:22:18 -05:00
Merge pull request #22 from cheikh2shift/windows-fixes
Enhance Windows compatibility and improve tool descriptions
This commit is contained in:
4 files changed
+85
-44
No files matched your search
+66
-39
@@ -16,6 +16,7 @@ import (
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -406,10 +407,7 @@ func main() {
|
||||
}
|
||||
hiveMgr, err = hive.NewManager(hiveCode, baseDir, provider.Model, maxTokens, getServerNames(servers), statusCh, func(ctx context.Context, prompt string) (string, error) {
|
||||
native := llmProvider != nil && llmProvider.SupportsNativeToolCalls()
|
||||
toolsDesc := ""
|
||||
if !native {
|
||||
toolsDesc = getToolsDescription(servers)
|
||||
}
|
||||
toolsDesc := getToolsDescription(servers)
|
||||
fullPrompt := buildFullPrompt(native, toolsDesc, "", wd, prompt, "", hiveCode)
|
||||
maxRounds := 10
|
||||
if provider.MaxToolRounds != nil && *provider.MaxToolRounds > 0 {
|
||||
@@ -629,10 +627,7 @@ promptLoop:
|
||||
completionMsg := fmt.Sprintf("Hive worker %s has completed and sent back the following result:\n%s", workerLabel, payload)
|
||||
sessionContext := buildSessionContext(prevSession, agentsContext, commitContextPath, commitContextRef, wd)
|
||||
native := llmProvider != nil && llmProvider.SupportsNativeToolCalls()
|
||||
toolsDesc := ""
|
||||
if !native {
|
||||
toolsDesc = getToolsDescription(servers)
|
||||
}
|
||||
toolsDesc := getToolsDescription(servers)
|
||||
var hiveInstanceID string
|
||||
if hiveMgr != nil {
|
||||
hiveInstanceID = hiveMgr.Instance().ID
|
||||
@@ -909,12 +904,7 @@ promptLoop:
|
||||
}
|
||||
|
||||
nativeToolCalls := llmProvider.SupportsNativeToolCalls()
|
||||
var toolsSection string
|
||||
if nativeToolCalls {
|
||||
toolsSection = ""
|
||||
} else {
|
||||
toolsSection = fmt.Sprintf("\nYou have access to these tools:\n%s\n", getToolsDescription(servers))
|
||||
}
|
||||
toolsSection := getToolsDescription(servers)
|
||||
|
||||
sessionContext := buildSessionContext(prevSession, agentsContext, commitContextPath, commitContextRef, wd)
|
||||
|
||||
@@ -1258,10 +1248,7 @@ promptLoop:
|
||||
completionMsg := fmt.Sprintf("Hive worker %s has completed and sent back the following result:\n%s", workerLabel, payload)
|
||||
sessionContext := buildSessionContext(prevSession, agentsContext, commitContextPath, commitContextRef, wd)
|
||||
native := llmProvider != nil && llmProvider.SupportsNativeToolCalls()
|
||||
toolsDesc := ""
|
||||
if !native {
|
||||
toolsDesc = getToolsDescription(servers)
|
||||
}
|
||||
toolsDesc := getToolsDescription(servers)
|
||||
var hiveInstanceID string
|
||||
if hiveMgr != nil {
|
||||
hiveInstanceID = hiveMgr.Instance().ID
|
||||
@@ -2062,10 +2049,7 @@ func runSinglePrompt(ctx context.Context, provider *config.Provider, prompt stri
|
||||
llmProvider, _ := agent.GetProvider(provider)
|
||||
nativeToolCalls := llmProvider != nil && llmProvider.SupportsNativeToolCalls()
|
||||
|
||||
toolsDesc := ""
|
||||
if !nativeToolCalls {
|
||||
toolsDesc = getToolsDescription(servers)
|
||||
}
|
||||
toolsDesc := getToolsDescription(servers)
|
||||
fullPrompt := buildFullPrompt(nativeToolCalls, toolsDesc, "", wd, prompt, tree, "")
|
||||
|
||||
// Get tool settings
|
||||
@@ -2099,33 +2083,46 @@ func buildFullPrompt(nativeToolCalls bool, toolsSection, sessionContext, wd, inp
|
||||
hiveInfo = fmt.Sprintf(`- Hive Instance ID: %s. You can't assign a task to yourself, but you can ask other hive workers to perform tasks for you.
|
||||
`, hiveInstanceID)
|
||||
}
|
||||
osName := runtime.GOOS
|
||||
base += fmt.Sprintf(`CRITICAL INFORMATION:
|
||||
- Operating System: %s (%s)
|
||||
- Current working directory: %s
|
||||
%sUse this path when the user asks about "this folder", "current directory", or similar.
|
||||
|
||||
IMPORTANT: Execute tools FIRST, perform any action asked for by the user, then provide the final answer. Do NOT include any final answer, summary, or "FINAL_ANSWER:" until AFTER you have executed all necessary tools and received their results. If you need to run commands/tests to verify something, run them first before answering.
|
||||
`, runtime.GOOS, runtime.GOARCH, wd, hiveInfo)
|
||||
`, osName, runtime.GOARCH, wd, hiveInfo)
|
||||
|
||||
if !nativeToolCalls {
|
||||
base = fmt.Sprintf(`You have access to these tools:
|
||||
%s
|
||||
if strings.TrimSpace(toolsSection) != "" {
|
||||
base += fmt.Sprintf("\nYou have access to these tools:\n%s\n", toolsSection)
|
||||
}
|
||||
|
||||
%s
|
||||
shellName := "sh"
|
||||
shellLimits := `- Shell variables like $HOME, $PATH are NOT expanded - use absolute paths instead
|
||||
- Interactive commands (vim, less, top) will NOT work - use non-interactive alternatives
|
||||
- Shell aliases are NOT expanded - use full command names`
|
||||
if osName == "windows" {
|
||||
shellName = "cmd.exe"
|
||||
shellLimits = `- Use cmd.exe syntax (e.g., %USERPROFILE% not $HOME, %PATH% not $PATH)
|
||||
- PowerShell-specific commands may not work; prefer cmd.exe built-ins
|
||||
- Interactive commands will NOT work - use non-interactive alternatives`
|
||||
}
|
||||
|
||||
base += fmt.Sprintf(`
|
||||
IMPORTANT: When you need to read files, search, or get directory contents, you MUST call the appropriate tool with the CORRECT path.
|
||||
Do NOT use example paths like "/path/to/directory" - use the actual path: %s
|
||||
|
||||
BASH TOOL LIMITATIONS:
|
||||
- Shell variables like $HOME, $PATH are NOT expanded - use absolute paths instead
|
||||
- Interactive commands (vim, less, top) will NOT work - use non-interactive alternatives
|
||||
- Shell aliases are NOT expanded - use full command names
|
||||
- Commands run via `+"`"+`%s`+"`"+`
|
||||
- %s
|
||||
- NEVER run servers or long-running programs in foreground - they will hang. ALWAYS use background: true
|
||||
- ALWAYS use background: true for any server, daemon, or program that doesn't exit immediately
|
||||
- ALWAYS use background: true when starting a webserver or a long-running background task
|
||||
- After starting a background process, use sleep before making requests to it
|
||||
- Use kill_command with the PID to stop background processes when done
|
||||
`, wd, shellName, shellLimits)
|
||||
|
||||
if !nativeToolCalls {
|
||||
base += `
|
||||
To call tools, respond with one or more JSON objects, each in its own markdown code block.
|
||||
You can call multiple tools at once to be more efficient. If tools are independent of each other, call them in parallel for faster execution.
|
||||
When planning parallel tool calls, ensure there is no conflict - tools that modify the same resource (file, directory, process, etc.) should NOT be called in parallel; run them sequentially instead.
|
||||
@@ -2140,8 +2137,7 @@ Example:
|
||||
}
|
||||
`+"```"+`
|
||||
|
||||
IMPORTANT: Execute tools FIRST, perform any action asked for by the user, then provide the final answer. Do NOT include any final answer, summary, or "FINAL_ANSWER:" until AFTER you have executed all necessary tools and received their results. If you need to run commands/tests to verify something, run them first before answering.
|
||||
`, toolsSection, base, wd)
|
||||
IMPORTANT: Execute tools FIRST, perform any action asked for by the user, then provide the final answer. Do NOT include any final answer, summary, or "FINAL_ANSWER:" until AFTER you have executed all necessary tools and received their results. If you need to run commands/tests to verify something, run them first before answering.`
|
||||
}
|
||||
|
||||
if strings.TrimSpace(tree) != "" {
|
||||
@@ -2154,10 +2150,7 @@ User request: %s`, strings.TrimSpace(base), input)
|
||||
|
||||
func runToolLoop(ctx context.Context, provider *config.Provider, servers []MCPServer, fullPrompt, input string, maxToolRounds int, toolTimeout int, llmProvider providers.Provider, verbose, debug, autoDenyRestrictedPaths bool, nocont bool, onToolCall func(string)) (string, error) {
|
||||
prevRoundToolCalls := make(map[string]bool)
|
||||
toolsDesc := ""
|
||||
if llmProvider == nil || !llmProvider.SupportsNativeToolCalls() {
|
||||
toolsDesc = getToolsDescription(servers)
|
||||
}
|
||||
toolsDesc := getToolsDescription(servers)
|
||||
|
||||
prevNoTool := false
|
||||
for round := 0; round < maxToolRounds; round++ {
|
||||
@@ -2376,10 +2369,10 @@ func runToolLoop(ctx context.Context, provider *config.Provider, servers []MCPSe
|
||||
}
|
||||
|
||||
func buildContinuePrompt(llmProvider providers.Provider, servers []MCPServer, originalInput, continuePrompt string, currentRound, maxRounds int) string {
|
||||
if llmProvider != nil && llmProvider.SupportsNativeToolCalls() {
|
||||
return fmt.Sprintf("Continue from where you left off. %s\n\nProvide your FINAL_ANSWER now.", continuePrompt)
|
||||
}
|
||||
toolsDesc := getToolsDescription(servers)
|
||||
if llmProvider != nil && llmProvider.SupportsNativeToolCalls() {
|
||||
return fmt.Sprintf("You have access to these tools:%s\n\nContinue from where you left off. %s\n\nProvide your FINAL_ANSWER now.", toolsDesc, continuePrompt)
|
||||
}
|
||||
toolCallFormat := "To call tools, respond with JSON in markdown code blocks:\n```json\n{\n \"name\": \"tool_name\",\n \"arguments\": {\n \"arg1\": \"value1\"\n }\n}\n```"
|
||||
roundInfo := ""
|
||||
if currentRound > 0 {
|
||||
@@ -2403,6 +2396,40 @@ func getToolsDescription(servers []MCPServer) string {
|
||||
desc.WriteString(fmt.Sprintf("\n[%s]\n", serverName))
|
||||
for _, tool := range tools {
|
||||
desc.WriteString(fmt.Sprintf(" - %s: %s\n", tool.Name, tool.Description))
|
||||
var schema map[string]any
|
||||
if err := json.Unmarshal(tool.InputSchema, &schema); err != nil {
|
||||
continue
|
||||
}
|
||||
props, _ := schema["properties"].(map[string]any)
|
||||
required, _ := schema["required"].([]any)
|
||||
reqSet := make(map[string]bool, len(required))
|
||||
for _, r := range required {
|
||||
if s, ok := r.(string); ok {
|
||||
reqSet[s] = true
|
||||
}
|
||||
}
|
||||
if len(props) > 0 {
|
||||
// Sort for deterministic output
|
||||
names := make([]string, 0, len(props))
|
||||
for name := range props {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
p, _ := props[name].(map[string]any)
|
||||
pType, _ := p["type"].(string)
|
||||
pDesc, _ := p["description"].(string)
|
||||
req := "optional"
|
||||
if reqSet[name] {
|
||||
req = "required"
|
||||
}
|
||||
if pDesc != "" {
|
||||
desc.WriteString(fmt.Sprintf(" %s (%s, %s): %s\n", name, pType, req, pDesc))
|
||||
} else {
|
||||
desc.WriteString(fmt.Sprintf(" %s (%s, %s)\n", name, pType, req))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return desc.String()
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
@@ -271,8 +272,8 @@ func NewBashServer(allowedPaths []string, autoConfirm bool) *BashServer {
|
||||
jobTracker: NewJobTracker(),
|
||||
tools: []Tool{
|
||||
{
|
||||
Name: "run_command",
|
||||
Description: "Run a shell command and return its output. For servers/services/long-running processes, you MUST set background: true (killable via /kill or /killbg).",
|
||||
Name: "run_command",
|
||||
Description: "Run a shell command and return its output. Uses cmd.exe on Windows, sh on Unix. For servers/services/long-running processes, you MUST set background: true (killable via /kill or /killbg).",
|
||||
InputSchema: json.RawMessage(`{"type":"object","properties":{"command":{"type":"string","description":"Shell command to run"},"timeout":{"type":"number","description":"Timeout in seconds (default 180)"},"background":{"type":"boolean","description":"Run in background as a goroutine (default false)"}},"required":["command"]}`),
|
||||
},
|
||||
{
|
||||
@@ -594,7 +595,12 @@ func (s *BashServer) runSyncCommand(ctx context.Context, command string, timeout
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, "sh", "-c", command)
|
||||
var cmd *exec.Cmd
|
||||
if runtime.GOOS == "windows" {
|
||||
cmd = exec.CommandContext(ctx, "cmd", "/c", command)
|
||||
} else {
|
||||
cmd = exec.CommandContext(ctx, "sh", "-c", command)
|
||||
}
|
||||
cmd.Stdin = nil // Close stdin to prevent interactive prompts from blocking
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
@@ -663,7 +663,7 @@ func (s *Scheduler) executeWithTools(ctx context.Context, task *ScheduledTask) (
|
||||
}
|
||||
|
||||
func (s *Scheduler) buildInitialPrompt(input, toolsDesc string, toolCallFormat string, nativeToolCalls bool) string {
|
||||
osInfo := "linux"
|
||||
osInfo := runtime.GOOS
|
||||
if s.os != "" {
|
||||
osInfo = s.os
|
||||
}
|
||||
@@ -692,7 +692,7 @@ func (s *Scheduler) buildContinuePrompt(continuePrompt, toolsDesc string, toolCa
|
||||
if currentRound > 0 {
|
||||
roundInfo = fmt.Sprintf("\n\nNOTE: You are on round %d/%d. Only call more tools if absolutely necessary.", currentRound, maxRounds)
|
||||
}
|
||||
osInfo := "linux"
|
||||
osInfo := runtime.GOOS
|
||||
if s.os != "" {
|
||||
osInfo = s.os
|
||||
}
|
||||
|
||||
@@ -479,6 +479,14 @@ func (m multiSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
// Fallback: on Windows consoles that are not in raw / virtual-terminal
|
||||
// input mode, Bubble Tea delivers Space as a printable rune rather than
|
||||
// a KeySpace event. Accept that as a toggle so the multi-select works
|
||||
// cross-platform (notably fixing the MCP picker on Windows).
|
||||
if msg.Type == tea.KeyRunes && len(msg.Runes) == 1 && msg.Runes[0] == ' ' {
|
||||
m.selected[m.cursor] = !m.selected[m.cursor]
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user