diff --git a/internal/llm/agent/agent.go b/internal/llm/agent/agent.go index 907a2348f838aa2f2ba6792db9b768eb656904a8..39c762991019f339348efab8cd9b769077e316f5 100644 --- a/internal/llm/agent/agent.go +++ b/internal/llm/agent/agent.go @@ -22,6 +22,7 @@ import ( "github.com/charmbracelet/crush/internal/permission" "github.com/charmbracelet/crush/internal/pubsub" "github.com/charmbracelet/crush/internal/session" + "github.com/charmbracelet/crush/internal/shell" ) // Common errors @@ -762,6 +763,8 @@ func (a *agent) Summarize(ctx context.Context, sessionID string) error { a.Publish(pubsub.CreatedEvent, event) return } + shell := shell.GetPersistentShell(config.Get().WorkingDir()) + summary += "\n\n**Current working directory of the persistent shell**\n\n" + shell.GetWorkingDir() event = AgentEvent{ Type: AgentEventTypeSummarize, Progress: "Creating new session...", diff --git a/internal/llm/tools/bash.go b/internal/llm/tools/bash.go index 10051a24bce881b9bdc4a8990364d30dec92bc85..99ab86068a5effa1e631037f3340ba814055d709 100644 --- a/internal/llm/tools/bash.go +++ b/internal/llm/tools/bash.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "log/slog" "strings" "time" @@ -23,8 +22,10 @@ type BashPermissionsParams struct { } type BashResponseMetadata struct { - StartTime int64 `json:"start_time"` - EndTime int64 `json:"end_time"` + StartTime int64 `json:"start_time"` + EndTime int64 `json:"end_time"` + Output string `json:"output"` + WorkingDirectory string `json:"working_directory"` } type bashTool struct { permissions permission.Service @@ -146,6 +147,7 @@ Before executing the command, please follow these steps: 5. Return Result: - Provide the processed output of the command. - If any errors occurred during execution, include those in the output. + - The result will also have metadata like the cwd (current working directory) at the end, included with tags. Usage notes: - The command argument is required. @@ -389,9 +391,12 @@ func (b *bashTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) ctx, cancel = context.WithTimeout(ctx, time.Duration(params.Timeout)*time.Millisecond) defer cancel() } - stdout, stderr, err := shell. - GetPersistentShell(b.workingDir). - Exec(ctx, params.Command) + + persistentShell := shell.GetPersistentShell(b.workingDir) + stdout, stderr, err := persistentShell.Exec(ctx, params.Command) + + // Get the current working directory after command execution + currentWorkingDir := persistentShell.GetWorkingDir() interrupted := shell.IsInterrupt(err) exitCode := shell.ExitCode(err) if exitCode == 0 && !interrupted && err != nil { @@ -401,15 +406,6 @@ func (b *bashTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) stdout = truncateOutput(stdout) stderr = truncateOutput(stderr) - slog.Info("Bash command executed", - "command", params.Command, - "stdout", stdout, - "stderr", stderr, - "exit_code", exitCode, - "interrupted", interrupted, - "err", err, - ) - errorMessage := stderr if errorMessage == "" && err != nil { errorMessage = err.Error() @@ -438,9 +434,12 @@ func (b *bashTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error) } metadata := BashResponseMetadata{ - StartTime: startTime.UnixMilli(), - EndTime: time.Now().UnixMilli(), + StartTime: startTime.UnixMilli(), + EndTime: time.Now().UnixMilli(), + Output: stdout, + WorkingDirectory: currentWorkingDir, } + stdout += fmt.Sprintf("\n\n%s", currentWorkingDir) if stdout == "" { return WithResponseMetadata(NewTextResponse(BashNoOutput), metadata), nil } diff --git a/internal/tui/components/chat/messages/renderer.go b/internal/tui/components/chat/messages/renderer.go index 4483b4ed2a9d2bf0b87a6eeae4565049edfa303e..ad9cd475704a84f2c400b611f592d25b5a78eddc 100644 --- a/internal/tui/components/chat/messages/renderer.go +++ b/internal/tui/components/chat/messages/renderer.go @@ -212,10 +212,19 @@ func (br bashRenderer) Render(v *toolCallCmp) string { args := newParamBuilder().addMain(cmd).build() return br.renderWithParams(v, "Bash", args, func() string { - if v.result.Content == tools.BashNoOutput { + var meta tools.BashResponseMetadata + if err := br.unmarshalParams(v.result.Metadata, &meta); err != nil { + return renderPlainContent(v, v.result.Content) + } + // for backwards compatibility with older tool calls. + if meta.Output == "" && v.result.Content != tools.BashNoOutput { + meta.Output = v.result.Content + } + + if meta.Output == "" { return "" } - return renderPlainContent(v, v.result.Content) + return renderPlainContent(v, meta.Output) }) }