on main help sign in
chore: gofumpt project
45b1e63Christian Rocha
internal/agent/agent.go+12−6
@@ -668,7 +668,8 @@ func (a *sessionAgent) Summarize(ctx context.Context, sessionID string, opts fan
668 }
668 }
669 }()
669 }()
670
670
671− agent := fantasy.NewAgent(largeModel.Model,
671+ agent := fantasy.NewAgent(
672+ largeModel.Model,
672 fantasy.WithSystemPrompt(string(summaryPrompt)),
673 fantasy.WithSystemPrompt(string(summaryPrompt)),
673 fantasy.WithUserAgent(userAgent),
674 fantasy.WithUserAgent(userAgent),
674 )
675 )
@@ -814,7 +815,8 @@ func (a *sessionAgent) preparePrompt(msgs []message.Message, supportsImages bool
814 var history []fantasy.Message
815 var history []fantasy.Message
815 if !a.isSubAgent {
816 if !a.isSubAgent {
816 history = append(history, fantasy.NewUserMessage(
817 history = append(history, fantasy.NewUserMessage(
818+ fmt.Sprintf(
817− fmt.Sprintf("<system_reminder>%s</system_reminder>",
819+ "<system_reminder>%s</system_reminder>",
818 `This is a reminder that your todo list is currently empty. DO NOT mention this to the user explicitly because they are already aware.
820 `This is a reminder that your todo list is currently empty. DO NOT mention this to the user explicitly because they are already aware.
819If you are working on tasks that would benefit from a todo list please use the "todos" tool to create one.
821If you are working on tasks that would benefit from a todo list please use the "todos" tool to create one.
820If not, please feel free to ignore. Again do not mention this message to the user.`,
822If not, please feel free to ignore. Again do not mention this message to the user.`,
@@ -920,7 +922,8 @@ func filterOrphanedToolResults(m message.Message, knownToolCallIDs map[string]st
920 if _, known := knownToolCallIDs[tr.ToolCallID]; known {
922 if _, known := knownToolCallIDs[tr.ToolCallID]; known {
921 validParts = append(validParts, part)
923 validParts = append(validParts, part)
922 } else {
924 } else {
925+ slog.Warn(
923− slog.Warn("Dropping orphaned tool result with no matching tool call",
926+ "Dropping orphaned tool result with no matching tool call",
924 "tool_call_id", tr.ToolCallID,
927 "tool_call_id", tr.ToolCallID,
925 )
928 )
926 }
929 }
@@ -946,7 +949,8 @@ func syntheticToolResultsForOrphanedCalls(m message.Message, knownToolResultIDs
946 if _, hasResult := knownToolResultIDs[tc.ID]; hasResult {
949 if _, hasResult := knownToolResultIDs[tc.ID]; hasResult {
947 continue
950 continue
948 }
951 }
952+ slog.Warn(
949− slog.Warn("Injecting synthetic tool result for orphaned tool call",
953+ "Injecting synthetic tool result for orphaned tool call",
950 "tool_call_id", tc.ID,
954 "tool_call_id", tc.ID,
951 "tool_name", tc.Name,
955 "tool_name", tc.Name,
952 )
956 )
@@ -1004,7 +1008,8 @@ func (a *sessionAgent) generateTitle(ctx context.Context, sessionID string, user
1004 }
1008 }
1005
1009
1006 newAgent := func(m fantasy.LanguageModel, p []byte, tok int64) fantasy.Agent {
1010 newAgent := func(m fantasy.LanguageModel, p []byte, tok int64) fantasy.Agent {
1007− return fantasy.NewAgent(m,
1011+ return fantasy.NewAgent(
1012+ m,
1008 fantasy.WithSystemPrompt(string(p)+"\n /no_think"),
1013 fantasy.WithSystemPrompt(string(p)+"\n /no_think"),
1009 fantasy.WithMaxOutputTokens(tok),
1014 fantasy.WithMaxOutputTokens(tok),
1010 fantasy.WithUserAgent(userAgent),
1015 fantasy.WithUserAgent(userAgent),
@@ -1273,7 +1278,8 @@ func (a *sessionAgent) convertToToolResult(result fantasy.ToolResultContent) mes
1273 case fantasy.ToolResultContentTypeMedia:
1278 case fantasy.ToolResultContentTypeMedia:
1274 if r, ok := fantasy.AsToolResultOutputType[fantasy.ToolResultOutputContentMedia](result.Result); ok {
1279 if r, ok := fantasy.AsToolResultOutputType[fantasy.ToolResultOutputContentMedia](result.Result); ok {
1275 if !stringext.IsValidBase64(r.Data) {
1280 if !stringext.IsValidBase64(r.Data) {
1281+ slog.Warn(
1276− slog.Warn("Tool returned media with invalid base64 data, discarding image",
1282+ "Tool returned media with invalid base64 data, discarding image",
1277 "tool", result.ToolName,
1283 "tool", result.ToolName,
1278 "tool_call_id", result.ToolCallID,
1284 "tool_call_id", result.ToolCallID,
1279 )
1285 )
internal/agent/agent_tool.go+2−1
@@ -63,5 +63,6 @@ func (c *coordinator) agentTool(ctx context.Context) (fantasy.AgentTool, error)
63 Prompt: params.Prompt,
63 Prompt: params.Prompt,
64 SessionTitle: "New Agent Session",
64 SessionTitle: "New Agent Session",
65 })
65 })
66+ },
66− }), nil
67+ ), nil
67}
68}
internal/agent/agentic_fetch_tool.go+4−2
@@ -80,7 +80,8 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) (
80 description = "Search the web and analyze results"
80 description = "Search the web and analyze results"
81 }
81 }
82
82
83− p, err := c.permissions.Request(ctx,
83+ p, err := c.permissions.Request(
84+ ctx,
84 permission.CreatePermissionRequest{
85 permission.CreatePermissionRequest{
85 SessionID: validationResult.SessionID,
86 SessionID: validationResult.SessionID,
86 Path: c.cfg.WorkingDir(),
87 Path: c.cfg.WorkingDir(),
@@ -200,5 +201,6 @@ func (c *coordinator) agenticFetchTool(_ context.Context, client *http.Client) (
200 c.permissions.AutoApproveSession(sessionID)
201 c.permissions.AutoApproveSession(sessionID)
201 },
202 },
202 })
203 })
204+ },
203− }), nil
205+ ), nil
204}
206}
internal/agent/coordinator.go+8−4
@@ -491,7 +491,8 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent, isSubA
491 hookRunner = hooks.NewRunner(preToolHooks, c.cfg.WorkingDir(), c.cfg.WorkingDir())
491 hookRunner = hooks.NewRunner(preToolHooks, c.cfg.WorkingDir(), c.cfg.WorkingDir())
492 }
492 }
493
493
494− allTools = append(allTools,
494+ allTools = append(
495+ allTools,
495 tools.NewBashTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Config().Options.Attribution, modelID),
496 tools.NewBashTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Config().Options.Attribution, modelID),
496 tools.NewCrushInfoTool(c.cfg, c.lspManager, c.allSkills, c.activeSkills, c.skillTracker),
497 tools.NewCrushInfoTool(c.cfg, c.lspManager, c.allSkills, c.activeSkills, c.skillTracker),
497 tools.NewCrushLogsTool(logFile),
498 tools.NewCrushLogsTool(logFile),
@@ -740,7 +741,8 @@ func (c *coordinator) buildOpenaiCompatProvider(baseURL, apiKey string, headers
740 // Set HTTP client based on provider and debug mode.
741 // Set HTTP client based on provider and debug mode.
741 var httpClient *http.Client
742 var httpClient *http.Client
742 if providerID == string(catwalk.InferenceProviderCopilot) {
743 if providerID == string(catwalk.InferenceProviderCopilot) {
743− opts = append(opts,
744+ opts = append(
745+ opts,
744 openaicompat.WithUseResponsesAPI(),
746 openaicompat.WithUseResponsesAPI(),
745 openaicompat.WithResponsesAPIFunc(func(modelID string) bool {
747 openaicompat.WithResponsesAPIFunc(func(modelID string) bool {
746 return copilotResponsesModels[modelID]
748 return copilotResponsesModels[modelID]
@@ -1213,7 +1215,8 @@ func logTurnSkillUsage(
1213 }
1215 }
1214 }
1216 }
1215
1217
1218+ slog.Info(
1216− slog.Info("Skill turn summary",
1219+ "Skill turn summary",
1217 "component", "skills",
1220 "component", "skills",
1218 "session_id", sessionID,
1221 "session_id", sessionID,
1219 "prompt_len", len(prompt),
1222 "prompt_len", len(prompt),
@@ -1257,7 +1260,8 @@ func logDiscoveryStats(
1257
1260
1258 xml := skills.ToPromptXML(activeSkills)
1261 xml := skills.ToPromptXML(activeSkills)
1259
1262
1263+ slog.Info(
1260− slog.Info("Skill discovery complete",
1264+ "Skill discovery complete",
1261 "component", "skills",
1265 "component", "skills",
1262 "builtin_ok", len(builtin),
1266 "builtin_ok", len(builtin),
1263 "builtin_errors", countErrors(builtinStates),
1267 "builtin_errors", countErrors(builtinStates),
internal/agent/tools/bash.go+4−2
@@ -219,7 +219,8 @@ func NewBashTool(permissions permission.Service, workingDir string, attribution
219 return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for executing shell command")
219 return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for executing shell command")
220 }
220 }
221 if !isSafeReadOnly {
221 if !isSafeReadOnly {
222− p, err := permissions.Request(ctx,
222+ p, err := permissions.Request(
223+ ctx,
223 permission.CreatePermissionRequest{
224 permission.CreatePermissionRequest{
224 SessionID: sessionID,
225 SessionID: sessionID,
225 Path: execWorkingDir,
226 Path: execWorkingDir,
@@ -375,7 +376,8 @@ func NewBashTool(permissions permission.Service, workingDir string, attribution
375 }
376 }
376 response := fmt.Sprintf("Command is taking longer than expected and has been moved to background.\n\nBackground shell ID: %s\n\nUse job_output tool to view output or job_kill to terminate.", bgShell.ID)
377 response := fmt.Sprintf("Command is taking longer than expected and has been moved to background.\n\nBackground shell ID: %s\n\nUse job_output tool to view output or job_kill to terminate.", bgShell.ID)
377 return fantasy.WithResponseMetadata(fantasy.NewTextResponse(response), metadata), nil
378 return fantasy.WithResponseMetadata(fantasy.NewTextResponse(response), metadata), nil
379+ },
378− })
380+ )
379}
381}
380
382
381// formatOutput formats the output of a completed command with error handling
383// formatOutput formats the output of a completed command with error handling
internal/agent/tools/crush_info.go+2−1
@@ -33,7 +33,8 @@ func NewCrushInfoTool(
33 crushInfoDescription,
33 crushInfoDescription,
34 func(ctx context.Context, _ CrushInfoParams, _ fantasy.ToolCall) (fantasy.ToolResponse, error) {
34 func(ctx context.Context, _ CrushInfoParams, _ fantasy.ToolCall) (fantasy.ToolResponse, error) {
35 return fantasy.NewTextResponse(buildCrushInfo(cfg, lspManager, allSkills, activeSkills, skillTracker)), nil
35 return fantasy.NewTextResponse(buildCrushInfo(cfg, lspManager, allSkills, activeSkills, skillTracker)), nil
36+ },
36− })
37+ )
37}
38}
38
39
39func buildCrushInfo(cfg *config.ConfigStore, lspManager *lsp.Manager, allSkills []*skills.Skill, activeSkills []*skills.Skill, skillTracker *skills.Tracker) string {
40func buildCrushInfo(cfg *config.ConfigStore, lspManager *lsp.Manager, allSkills []*skills.Skill, activeSkills []*skills.Skill, skillTracker *skills.Tracker) string {
internal/agent/tools/diagnostics.go+2−1
@@ -35,7 +35,8 @@ func NewDiagnosticsTool(lspManager *lsp.Manager) fantasy.AgentTool {
35 notifyLSPs(ctx, lspManager, params.FilePath)
35 notifyLSPs(ctx, lspManager, params.FilePath)
36 output := getDiagnostics(params.FilePath, lspManager)
36 output := getDiagnostics(params.FilePath, lspManager)
37 return fantasy.NewTextResponse(output), nil
37 return fantasy.NewTextResponse(output), nil
38+ },
38− })
39+ )
39}
40}
40
41
41// openInLSPs ensures LSP servers are running and aware of the file, but does
42// openInLSPs ensures LSP servers are running and aware of the file, but does
internal/agent/tools/download.go+4−2
@@ -87,7 +87,8 @@ func NewDownloadTool(permissions permission.Service, workingDir string, client *
87 return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for downloading files")
87 return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for downloading files")
88 }
88 }
89
89
90− p, err := permissions.Request(ctx,
90+ p, err := permissions.Request(
91+ ctx,
91 permission.CreatePermissionRequest{
92 permission.CreatePermissionRequest{
92 SessionID: sessionID,
93 SessionID: sessionID,
93 Path: filePath,
94 Path: filePath,
@@ -160,5 +161,6 @@ func NewDownloadTool(permissions permission.Service, workingDir string, client *
160 }
161 }
161
162
162 return fantasy.NewTextResponse(responseMsg), nil
163 return fantasy.NewTextResponse(responseMsg), nil
164+ },
163− })
165+ )
164}
166}
internal/agent/tools/edit.go+18−9
@@ -104,7 +104,8 @@ func NewEditTool(
104 text += getDiagnostics(params.FilePath, lspManager)
104 text += getDiagnostics(params.FilePath, lspManager)
105 response.Content = text
105 response.Content = text
106 return response, nil
106 return response, nil
107+ },
107− })
108+ )
108}
109}
109
110
110func createNewFile(edit editContext, filePath, content string, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
111func createNewFile(edit editContext, filePath, content string, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
@@ -133,7 +134,8 @@ func createNewFile(edit editContext, filePath, content string, call fantasy.Tool
133 content,
134 content,
134 strings.TrimPrefix(filePath, edit.workingDir),
135 strings.TrimPrefix(filePath, edit.workingDir),
135 )
136 )
136− p, err := edit.permissions.Request(edit.ctx,
137+ p, err := edit.permissions.Request(
138+ edit.ctx,
137 permission.CreatePermissionRequest{
139 permission.CreatePermissionRequest{
138 SessionID: sessionID,
140 SessionID: sessionID,
139 Path: fsext.PathOrPrefix(filePath, edit.workingDir),
141 Path: fsext.PathOrPrefix(filePath, edit.workingDir),
@@ -213,9 +215,11 @@ func deleteContent(edit editContext, filePath, oldString string, replaceAll bool
213 modTime := fileInfo.ModTime().Truncate(time.Second)
215 modTime := fileInfo.ModTime().Truncate(time.Second)
214 if modTime.After(lastRead) {
216 if modTime.After(lastRead) {
215 return fantasy.NewTextErrorResponse(
217 return fantasy.NewTextErrorResponse(
218+ fmt.Sprintf(
216− fmt.Sprintf("file %s has been modified since it was last read (mod time: %s, last read: %s)",
219+ "file %s has been modified since it was last read (mod time: %s, last read: %s)",
217 filePath, modTime.Format(time.RFC3339), lastRead.Format(time.RFC3339),
220 filePath, modTime.Format(time.RFC3339), lastRead.Format(time.RFC3339),
221+ ),
218− )), nil
222+ ), nil
219 }
223 }
220
224
221 content, err := os.ReadFile(filePath)
225 content, err := os.ReadFile(filePath)
@@ -252,7 +256,8 @@ func deleteContent(edit editContext, filePath, oldString string, replaceAll bool
252 strings.TrimPrefix(filePath, edit.workingDir),
256 strings.TrimPrefix(filePath, edit.workingDir),
253 )
257 )
254
258
255− p, err := edit.permissions.Request(edit.ctx,
259+ p, err := edit.permissions.Request(
260+ edit.ctx,
256 permission.CreatePermissionRequest{
261 permission.CreatePermissionRequest{
257 SessionID: sessionID,
262 SessionID: sessionID,
258 Path: fsext.PathOrPrefix(filePath, edit.workingDir),
263 Path: fsext.PathOrPrefix(filePath, edit.workingDir),
@@ -344,9 +349,11 @@ func replaceContent(edit editContext, filePath, oldString, newString string, rep
344 modTime := fileInfo.ModTime().Truncate(time.Second)
349 modTime := fileInfo.ModTime().Truncate(time.Second)
345 if modTime.After(lastRead) {
350 if modTime.After(lastRead) {
346 return fantasy.NewTextErrorResponse(
351 return fantasy.NewTextErrorResponse(
352+ fmt.Sprintf(
347− fmt.Sprintf("file %s has been modified since it was last read (mod time: %s, last read: %s)",
353+ "file %s has been modified since it was last read (mod time: %s, last read: %s)",
348 filePath, modTime.Format(time.RFC3339), lastRead.Format(time.RFC3339),
354 filePath, modTime.Format(time.RFC3339), lastRead.Format(time.RFC3339),
355+ ),
349− )), nil
356+ ), nil
350 }
357 }
351
358
352 content, err := os.ReadFile(filePath)
359 content, err := os.ReadFile(filePath)
@@ -383,7 +390,8 @@ func replaceContent(edit editContext, filePath, oldString, newString string, rep
383 strings.TrimPrefix(filePath, edit.workingDir),
390 strings.TrimPrefix(filePath, edit.workingDir),
384 )
391 )
385
392
386− p, err := edit.permissions.Request(edit.ctx,
393+ p, err := edit.permissions.Request(
394+ edit.ctx,
387 permission.CreatePermissionRequest{
395 permission.CreatePermissionRequest{
388 SessionID: sessionID,
396 SessionID: sessionID,
389 Path: fsext.PathOrPrefix(filePath, edit.workingDir),
397 Path: fsext.PathOrPrefix(filePath, edit.workingDir),
@@ -445,5 +453,6 @@ func replaceContent(edit editContext, filePath, oldString, newString string, rep
445 NewContent: newContent,
453 NewContent: newContent,
446 Additions: additions,
454 Additions: additions,
447 Removals: removals,
455 Removals: removals,
456+ },
448− }), nil
457+ ), nil
449}
458}
internal/agent/tools/fetch.go+4−2
@@ -77,7 +77,8 @@ func NewFetchTool(permissions permission.Service, workingDir string, client *htt
77 return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for creating a new file")
77 return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for creating a new file")
78 }
78 }
79
79
80− p, err := permissions.Request(ctx,
80+ p, err := permissions.Request(
81+ ctx,
81 permission.CreatePermissionRequest{
82 permission.CreatePermissionRequest{
82 SessionID: sessionID,
83 SessionID: sessionID,
83 Path: workingDir,
84 Path: workingDir,
@@ -184,7 +185,8 @@ func NewFetchTool(permissions permission.Service, workingDir string, client *htt
184 }
185 }
185
186
186 return fantasy.NewTextResponse(content), nil
187 return fantasy.NewTextResponse(content), nil
188+ },
187− })
189+ )
188}
190}
189
191
190func extractTextFromHTML(html string) (string, error) {
192func extractTextFromHTML(html string) (string, error) {
internal/agent/tools/glob.go+2−1
@@ -82,7 +82,8 @@ func NewGlobTool(workingDir string) fantasy.AgentTool {
82 Truncated: truncated,
82 Truncated: truncated,
83 },
83 },
84 ), nil
84 ), nil
85+ },
85− })
86+ )
86}
87}
87
88
88func globFiles(ctx context.Context, pattern, searchPath string, limit int) ([]string, bool, error) {
89func globFiles(ctx context.Context, pattern, searchPath string, limit int) ([]string, bool, error) {
internal/agent/tools/grep.go+2−1
@@ -186,7 +186,8 @@ func NewGrepTool(workingDir string, config config.ToolGrep) fantasy.AgentTool {
186 Truncated: truncated,
186 Truncated: truncated,
187 },
187 },
188 ), nil
188 ), nil
189+ },
189− })
190+ )
190}
191}
191
192
192func searchFiles(ctx context.Context, pattern, rootPath, include string, limit int) ([]grepMatch, bool, error) {
193func searchFiles(ctx context.Context, pattern, rootPath, include string, limit int) ([]grepMatch, bool, error) {
internal/agent/tools/job_kill.go+2−1
@@ -55,5 +55,6 @@ func NewJobKillTool() fantasy.AgentTool {
55
55
56 result := fmt.Sprintf("Background shell %s terminated successfully", params.ShellID)
56 result := fmt.Sprintf("Background shell %s terminated successfully", params.ShellID)
57 return fantasy.WithResponseMetadata(fantasy.NewTextResponse(result), metadata), nil
57 return fantasy.WithResponseMetadata(fantasy.NewTextResponse(result), metadata), nil
58+ },
58− })
59+ )
59}
60}
internal/agent/tools/job_output.go+2−1
@@ -87,5 +87,6 @@ func NewJobOutputTool() fantasy.AgentTool {
87
87
88 result := fmt.Sprintf("Status: %s\n\n%s", status, output)
88 result := fmt.Sprintf("Status: %s\n\n%s", status, output)
89 return fantasy.WithResponseMetadata(fantasy.NewTextResponse(result), metadata), nil
89 return fantasy.WithResponseMetadata(fantasy.NewTextResponse(result), metadata), nil
90+ },
90− })
91+ )
91}
92}
internal/agent/tools/list_mcp_resources.go+2−1
@@ -44,7 +44,8 @@ func NewListMCPResourcesTool(cfg *config.ConfigStore, permissions permission.Ser
44 }
44 }
45
45
46 relPath := filepathext.SmartJoin(cfg.WorkingDir(), params.MCPName)
46 relPath := filepathext.SmartJoin(cfg.WorkingDir(), params.MCPName)
47− p, err := permissions.Request(ctx,
47+ p, err := permissions.Request(
48+ ctx,
48 permission.CreatePermissionRequest{
49 permission.CreatePermissionRequest{
49 SessionID: sessionID,
50 SessionID: sessionID,
50 Path: relPath,
51 Path: relPath,
internal/agent/tools/ls.go+4−2
@@ -102,7 +102,8 @@ func NewLsTool(permissions permission.Service, workingDir string, lsConfig confi
102 return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for accessing directories outside working directory")
102 return fantasy.ToolResponse{}, fmt.Errorf("session ID is required for accessing directories outside working directory")
103 }
103 }
104
104
105− granted, err := permissions.Request(ctx,
105+ granted, err := permissions.Request(
106+ ctx,
106 permission.CreatePermissionRequest{
107 permission.CreatePermissionRequest{
107 SessionID: sessionID,
108 SessionID: sessionID,
108 Path: absSearchPath,
109 Path: absSearchPath,
@@ -130,7 +131,8 @@ func NewLsTool(permissions permission.Service, workingDir string, lsConfig confi
130 fantasy.NewTextResponse(output),
131 fantasy.NewTextResponse(output),
131 metadata,
132 metadata,
132 ), nil
133 ), nil
134+ },
133− })
135+ )
134}
136}
135
137
136func ListDirectoryTree(searchPath string, params LSParams, lsConfig config.ToolLs) (string, LSResponseMetadata, error) {
138func ListDirectoryTree(searchPath string, params LSParams, lsConfig config.ToolLs) (string, LSResponseMetadata, error) {
internal/agent/tools/lsp_restart.go+2−1
@@ -75,5 +75,6 @@ func NewLSPRestartTool(lspManager *lsp.Manager) fantasy.AgentTool {
75 }
75 }
76
76
77 return fantasy.NewTextResponse(output), nil
77 return fantasy.NewTextResponse(output), nil
78+ },
78− })
79+ )
79}
80}
internal/agent/tools/mcp-tools.go+2−1
@@ -105,7 +105,8 @@ func (m *Tool) Run(ctx context.Context, params fantasy.ToolCall) (fantasy.ToolRe
105 // Skip permission for whitelisted Docker MCP tools.
105 // Skip permission for whitelisted Docker MCP tools.
106 if !slices.Contains(whitelistDockerTools, params.Name) {
106 if !slices.Contains(whitelistDockerTools, params.Name) {
107 permissionDescription := fmt.Sprintf("execute %s with the following parameters:", m.Info().Name)
107 permissionDescription := fmt.Sprintf("execute %s with the following parameters:", m.Info().Name)
108− p, err := m.permissions.Request(ctx,
108+ p, err := m.permissions.Request(
109+ ctx,
109 permission.CreatePermissionRequest{
110 permission.CreatePermissionRequest{
110 SessionID: sessionID,
111 SessionID: sessionID,
111 ToolCallID: params.ID,
112 ToolCallID: params.ID,
internal/agent/tools/multiedit.go+6−3
@@ -110,7 +110,8 @@ func NewMultiEditTool(
110 text += getDiagnostics(params.FilePath, lspManager)
110 text += getDiagnostics(params.FilePath, lspManager)
111 response.Content = text
111 response.Content = text
112 return response, nil
112 return response, nil
113+ },
113− })
114+ )
114}
115}
115
116
116func validateEdits(edits []MultiEditOperation) error {
117func validateEdits(edits []MultiEditOperation) error {
@@ -266,9 +267,11 @@ func processMultiEditExistingFile(edit editContext, params MultiEditParams, call
266 modTime := fileInfo.ModTime().Truncate(time.Second)
267 modTime := fileInfo.ModTime().Truncate(time.Second)
267 if modTime.After(lastRead) {
268 if modTime.After(lastRead) {
268 return fantasy.NewTextErrorResponse(
269 return fantasy.NewTextErrorResponse(
270+ fmt.Sprintf(
269− fmt.Sprintf("file %s has been modified since it was last read (mod time: %s, last read: %s)",
271+ "file %s has been modified since it was last read (mod time: %s, last read: %s)",
270 params.FilePath, modTime.Format(time.RFC3339), lastRead.Format(time.RFC3339),
272 params.FilePath, modTime.Format(time.RFC3339), lastRead.Format(time.RFC3339),
273+ ),
271− )), nil
274+ ), nil
272 }
275 }
273
276
274 // Read current file content
277 // Read current file content
internal/agent/tools/read_mcp_resource.go+2−1
@@ -50,7 +50,8 @@ func NewReadMCPResourceTool(cfg *config.ConfigStore, permissions permission.Serv
50 }
50 }
51
51
52 relPath := filepathext.SmartJoin(cfg.WorkingDir(), cmp.Or(params.URI, "mcp-resource"))
52 relPath := filepathext.SmartJoin(cfg.WorkingDir(), cmp.Or(params.URI, "mcp-resource"))
53− p, err := permissions.Request(ctx,
53+ p, err := permissions.Request(
54+ ctx,
54 permission.CreatePermissionRequest{
55 permission.CreatePermissionRequest{
55 SessionID: sessionID,
56 SessionID: sessionID,
56 Path: relPath,
57 Path: relPath,
internal/agent/tools/references.go+2−1
@@ -87,7 +87,8 @@ func NewReferencesTool(lspManager *lsp.Manager) fantasy.AgentTool {
87 return fantasy.NewTextErrorResponse(allErrs.Error()), nil
87 return fantasy.NewTextErrorResponse(allErrs.Error()), nil
88 }
88 }
89 return fantasy.NewTextResponse(fmt.Sprintf("No references found for symbol '%s'", params.Symbol)), nil
89 return fantasy.NewTextResponse(fmt.Sprintf("No references found for symbol '%s'", params.Symbol)), nil
90+ },
90− })
91+ )
91}
92}
92
93
93func (r *referencesTool) Name() string {
94func (r *referencesTool) Name() string {
internal/agent/tools/sourcegraph.go+2−1
@@ -150,7 +150,8 @@ func NewSourcegraphTool(client *http.Client) fantasy.AgentTool {
150 }
150 }
151
151
152 return fantasy.NewTextResponse(formattedResults), nil
152 return fantasy.NewTextResponse(formattedResults), nil
153+ },
153− })
154+ )
154}
155}
155
156
156func formatSourcegraphResults(result map[string]any, contextWindow int) (string, error) {
157func formatSourcegraphResults(result map[string]any, contextWindow int) (string, error) {
internal/agent/tools/todos.go+2−1
@@ -130,5 +130,6 @@ func NewTodosTool(sessions session.Service) fantasy.AgentTool {
130 }
130 }
131
131
132 return fantasy.WithResponseMetadata(fantasy.NewTextResponse(response), metadata), nil
132 return fantasy.WithResponseMetadata(fantasy.NewTextResponse(response), metadata), nil
133+ },
133− })
134+ )
134}
135}
internal/agent/tools/view.go+4−2
@@ -134,7 +134,8 @@ func NewViewTool(
134
134
135 // Request permission for files outside working directory, unless it's a skill file.
135 // Request permission for files outside working directory, unless it's a skill file.
136 if isOutsideWorkDir && !isSkillFile {
136 if isOutsideWorkDir && !isSkillFile {
137− granted, permReqErr := permissions.Request(ctx,
137+ granted, permReqErr := permissions.Request(
138+ ctx,
138 permission.CreatePermissionRequest{
139 permission.CreatePermissionRequest{
139 SessionID: sessionID,
140 SessionID: sessionID,
140 Path: absFilePath,
141 Path: absFilePath,
@@ -274,7 +275,8 @@ func NewViewTool(
274 fantasy.NewTextResponse(output),
275 fantasy.NewTextResponse(output),
275 meta,
276 meta,
276 ), nil
277 ), nil
278+ },
277− })
279+ )
278}
280}
279
281
280func addLineNumbers(content string, startLine int) string {
282func addLineNumbers(content string, startLine int) string {
internal/agent/tools/web_fetch.go+2−1
@@ -75,5 +75,6 @@ func NewWebFetchTool(workingDir string, client *http.Client) fantasy.AgentTool {
75 }
75 }
76
76
77 return fantasy.NewTextResponse(result.String()), nil
77 return fantasy.NewTextResponse(result.String()), nil
78+ },
78− })
79+ )
79}
80}
internal/agent/tools/web_search.go+2−1
@@ -57,5 +57,6 @@ func NewWebSearchTool(client *http.Client) fantasy.AgentTool {
57 }
57 }
58
58
59 return fantasy.NewTextResponse(formatSearchResults(results)), nil
59 return fantasy.NewTextResponse(formatSearchResults(results)), nil
60+ },
60− })
61+ )
61}
62}
internal/agent/tools/write.go+6−3
@@ -105,7 +105,8 @@ func NewWriteTool(
105 strings.TrimPrefix(filePath, workingDir),
105 strings.TrimPrefix(filePath, workingDir),
106 )
106 )
107
107
108− p, err := permissions.Request(ctx,
108+ p, err := permissions.Request(
109+ ctx,
109 permission.CreatePermissionRequest{
110 permission.CreatePermissionRequest{
110 SessionID: sessionID,
111 SessionID: sessionID,
111 Path: fsext.PathOrPrefix(filePath, workingDir),
112 Path: fsext.PathOrPrefix(filePath, workingDir),
@@ -161,12 +162,14 @@ func NewWriteTool(
161 result := fmt.Sprintf("File successfully written: %s", filePath)
162 result := fmt.Sprintf("File successfully written: %s", filePath)
162 result = fmt.Sprintf("<result>\n%s\n</result>", result)
163 result = fmt.Sprintf("<result>\n%s\n</result>", result)
163 result += getDiagnostics(filePath, lspManager)
164 result += getDiagnostics(filePath, lspManager)
164− return fantasy.WithResponseMetadata(fantasy.NewTextResponse(result),
165+ return fantasy.WithResponseMetadata(
166+ fantasy.NewTextResponse(result),
165 WriteResponseMetadata{
167 WriteResponseMetadata{
166 Diff: diff,
168 Diff: diff,
167 Additions: additions,
169 Additions: additions,
168 Removals: removals,
170 Removals: removals,
169 },
171 },
170 ), nil
172 ), nil
173+ },
171− })
174+ )
172}
175}
internal/backend/backend.go+2−1
@@ -122,7 +122,8 @@ func (b *Backend) CreateWorkspace(args proto.Workspace) (*Workspace, proto.Works
122 b.workspaces.Set(id, ws)
122 b.workspaces.Set(id, ws)
123
123
124 if args.Version != "" && args.Version != version.Version {
124 if args.Version != "" && args.Version != version.Version {
125+ slog.Warn(
125− slog.Warn("Client/server version mismatch",
126+ "Client/server version mismatch",
126 "client", args.Version,
127 "client", args.Version,
127 "server", version.Version,
128 "server", version.Version,
128 )
129 )
internal/cmd/root.go+2−1
@@ -486,7 +486,8 @@ func perHostServerDir(hostURL *url.URL) (string, error) {
486// the --host flag so client and server compute the same key.
486// the --host flag so client and server compute the same key.
487func safeHostName(hostURL *url.URL) string {
487func safeHostName(hostURL *url.URL) string {
488 return safeNameRegexp.ReplaceAllString(
488 return safeNameRegexp.ReplaceAllString(
489− hostURL.Scheme+"://"+hostURL.Host+hostURL.Path, "_")
489+ hostURL.Scheme+"://"+hostURL.Host+hostURL.Path, "_",
490+ )
490}
491}
491
492
492// serverReadyTimeout returns the total budget for the readiness probe.
493// serverReadyTimeout returns the total budget for the readiness probe.
internal/config/load_test.go+2−1
@@ -248,7 +248,8 @@ func TestConfig_setDefaults(t *testing.T) {
248 cfg := &Config{}
248 cfg := &Config{}
249 cfg.setDefaults(child, "")
249 cfg.setDefaults(child, "")
250
250
251− require.Equal(t,
251+ require.Equal(
252+ t,
252 filepath.Clean(filepath.Join(child, defaultDataDirectory)),
253 filepath.Clean(filepath.Join(child, defaultDataDirectory)),
253 filepath.Clean(cfg.Options.DataDirectory),
254 filepath.Clean(cfg.Options.DataDirectory),
254 )
255 )
internal/event/event_test.go+2−1
@@ -54,7 +54,8 @@ func TestError(t *testing.T) {
54 }()
54 }()
55
55
56 client = nil
56 client = nil
57+ Error(
57− Error("test error",
58+ "test error",
58 "type", "test",
59 "type", "test",
59 "severity", "high",
60 "severity", "high",
60 "source", "unit-test",
61 "source", "unit-test",
internal/hooks/hooks.go+2−1
@@ -128,7 +128,8 @@ func aggregate(results []HookResult, origToolInput string) AggregateResult {
128 if r.UpdatedInput != "" {
128 if r.UpdatedInput != "" {
129 next, err := shallowMerge(merged, r.UpdatedInput)
129 next, err := shallowMerge(merged, r.UpdatedInput)
130 if err != nil {
130 if err != nil {
131+ slog.Warn(
131− slog.Warn("Hook updated_input patch rejected; ignoring",
132+ "Hook updated_input patch rejected; ignoring",
132 "error", err,
133 "error", err,
133 "patch", r.UpdatedInput,
134 "patch", r.UpdatedInput,
134 )
135 )
internal/hooks/hooks_test.go+6−3
@@ -538,7 +538,8 @@ func TestAggregationUpdatedInput(t *testing.T) {
538 require.Equal(t, DecisionAllow, agg.Decision)
538 require.Equal(t, DecisionAllow, agg.Decision)
539 // command overridden by second patch; keep preserved from first
539 // command overridden by second patch; keep preserved from first
540 // patch; timeout preserved from original input.
540 // patch; timeout preserved from original input.
541− require.JSONEq(t,
541+ require.JSONEq(
542+ t,
542 `{"command":"second","keep":"me","timeout":60}`,
543 `{"command":"second","keep":"me","timeout":60}`,
543 agg.UpdatedInput,
544 agg.UpdatedInput,
544 )
545 )
@@ -550,7 +551,8 @@ func TestAggregationUpdatedInput(t *testing.T) {
550 {Decision: DecisionAllow, UpdatedInput: `{"env":{"FOO":"bar"}}`},
551 {Decision: DecisionAllow, UpdatedInput: `{"env":{"FOO":"bar"}}`},
551 }, `{"env":{"BAZ":"qux"},"command":"ls"}`)
552 }, `{"env":{"BAZ":"qux"},"command":"ls"}`)
552 // "env" is replaced entirely; "command" preserved.
553 // "env" is replaced entirely; "command" preserved.
553− require.JSONEq(t,
554+ require.JSONEq(
555+ t,
554 `{"env":{"FOO":"bar"},"command":"ls"}`,
556 `{"env":{"FOO":"bar"},"command":"ls"}`,
555 agg.UpdatedInput,
557 agg.UpdatedInput,
556 )
558 )
@@ -678,7 +680,8 @@ func TestRunnerUpdatedInput(t *testing.T) {
678 result, err := r.Run(context.Background(), EventPreToolUse, "sess", "bash", `{"command":"echo original","timeout":60}`)
680 result, err := r.Run(context.Background(), EventPreToolUse, "sess", "bash", `{"command":"echo original","timeout":60}`)
679 require.NoError(t, err)
681 require.NoError(t, err)
680 require.Equal(t, DecisionAllow, result.Decision)
682 require.Equal(t, DecisionAllow, result.Decision)
681− require.JSONEq(t,
683+ require.JSONEq(
684+ t,
682 `{"command":"echo rewritten","timeout":60}`,
685 `{"command":"echo rewritten","timeout":60}`,
683 result.UpdatedInput,
686 result.UpdatedInput,
684 )
687 )
internal/hooks/input.go+4−2
@@ -53,7 +53,8 @@ func BuildPayload(eventName, sessionID, cwd, toolName, toolInputJSON string) []b
53func BuildEnv(eventName, toolName, sessionID, cwd, projectDir, toolInputJSON string) []string {
53func BuildEnv(eventName, toolName, sessionID, cwd, projectDir, toolInputJSON string) []string {
54 env := os.Environ()
54 env := os.Environ()
55 env = append(env, shell.CrushEnvMarkers()...)
55 env = append(env, shell.CrushEnvMarkers()...)
56− env = append(env,
56+ env = append(
57+ env,
57 fmt.Sprintf("CRUSH_EVENT=%s", eventName),
58 fmt.Sprintf("CRUSH_EVENT=%s", eventName),
58 fmt.Sprintf("CRUSH_TOOL_NAME=%s", toolName),
59 fmt.Sprintf("CRUSH_TOOL_NAME=%s", toolName),
59 fmt.Sprintf("CRUSH_SESSION_ID=%s", sessionID),
60 fmt.Sprintf("CRUSH_SESSION_ID=%s", sessionID),
@@ -105,7 +106,8 @@ func parseStdout(stdout string) HookResult {
105 }
106 }
106
107
107 if parsed.Version > SupportedOutputVersion {
108 if parsed.Version > SupportedOutputVersion {
109+ slog.Debug(
108− slog.Debug("Hook output declared a newer envelope version than this build supports",
110+ "Hook output declared a newer envelope version than this build supports",
109 "version", parsed.Version,
111 "version", parsed.Version,
110 "supported", SupportedOutputVersion,
112 "supported", SupportedOutputVersion,
111 )
113 )
internal/hooks/runner.go+10−5
@@ -54,7 +54,8 @@ func NewRunner(hooks []config.HookConfig, cwd, projectDir string) *Runner {
54 if h.Matcher != "" {
54 if h.Matcher != "" {
55 re, err := regexp.Compile(h.Matcher)
55 re, err := regexp.Compile(h.Matcher)
56 if err != nil {
56 if err != nil {
57+ slog.Warn(
57− slog.Warn("Hook matcher failed to compile; skipping hook",
58+ "Hook matcher failed to compile; skipping hook",
58 "matcher", h.Matcher,
59 "matcher", h.Matcher,
59 "command", h.Command,
60 "command", h.Command,
60 "error", err,
61 "error", err,
@@ -130,7 +131,8 @@ func (r *Runner) Run(ctx context.Context, eventName, sessionID, toolName, toolIn
130 InputRewrite: results[i].UpdatedInput != "",
131 InputRewrite: results[i].UpdatedInput != "",
131 }
132 }
132 }
133 }
134+ slog.Info(
133− slog.Info("Hook completed",
135+ "Hook completed",
134 "event", eventName,
136 "event", eventName,
135 "tool", toolName,
137 "tool", toolName,
136 "hooks", len(deduped),
138 "hooks", len(deduped),
@@ -194,7 +196,8 @@ func (r *Runner) runOne(parentCtx context.Context, hook config.HookConfig, envVa
194 case err = <-done:
196 case err = <-done:
195 // Interpreter yielded within the grace period; safe to read.
197 // Interpreter yielded within the grace period; safe to read.
196 case <-time.After(abandonGrace):
198 case <-time.After(abandonGrace):
199+ slog.Warn(
197− slog.Warn("Hook did not yield after cancel; abandoning goroutine",
200+ "Hook did not yield after cancel; abandoning goroutine",
198 "command", hook.Command,
201 "command", hook.Command,
199 "timeout", timeout,
202 "timeout", timeout,
200 )
203 )
@@ -240,7 +243,8 @@ func (r *Runner) runOne(parentCtx context.Context, hook config.HookConfig, envVa
240 }
243 }
241 default:
244 default:
242 // Other non-zero exits are non-blocking errors.
245 // Other non-zero exits are non-blocking errors.
246+ slog.Warn(
243− slog.Warn("Hook failed with non-blocking error",
247+ "Hook failed with non-blocking error",
244 "command", hook.Command,
248 "command", hook.Command,
245 "exit_code", exitCode,
249 "exit_code", exitCode,
246 "stderr", strings.TrimSpace(stderr.String()),
250 "stderr", strings.TrimSpace(stderr.String()),
@@ -252,7 +256,8 @@ func (r *Runner) runOne(parentCtx context.Context, hook config.HookConfig, envVa
252
256
253 // Exit code 0 — parse stdout JSON.
257 // Exit code 0 — parse stdout JSON.
254 result := parseStdout(stdout.String())
258 result := parseStdout(stdout.String())
259+ slog.Debug(
255− slog.Debug("Hook executed",
260+ "Hook executed",
256 "command", hook.Command,
261 "command", hook.Command,
257 "decision", result.Decision.String(),
262 "decision", result.Decision.String(),
258 )
263 )
internal/proto/agent.go+2−2
@@ -51,7 +51,7 @@ func (e AgentEvent) MarshalJSON() ([]byte, error) {
51 }
51 }
52 return ""
52 return ""
53 }(),
53 }(),
54− Alias: (Alias)(e),
54+ Alias: Alias(e),
55 })
55 })
56}
56}
57
57
@@ -62,7 +62,7 @@ func (e *AgentEvent) UnmarshalJSON(data []byte) error {
62 Error string `json:"error,omitempty"`
62 Error string `json:"error,omitempty"`
63 Alias
63 Alias
64 }{
64 }{
65− Alias: (Alias)(*e),
65+ Alias: Alias(*e),
66 }
66 }
67 if err := json.Unmarshal(data, &aux); err != nil {
67 if err := json.Unmarshal(data, &aux); err != nil {
68 return err
68 return err
internal/proto/mcp.go+4−4
@@ -100,7 +100,7 @@ func (e MCPEvent) MarshalJSON() ([]byte, error) {
100 }
100 }
101 return ""
101 return ""
102 }(),
102 }(),
103− Alias: (Alias)(e),
103+ Alias: Alias(e),
104 })
104 })
105}
105}
106
106
@@ -111,7 +111,7 @@ func (e *MCPEvent) UnmarshalJSON(data []byte) error {
111 Error string `json:"error,omitempty"`
111 Error string `json:"error,omitempty"`
112 Alias
112 Alias
113 }{
113 }{
114− Alias: (Alias)(*e),
114+ Alias: Alias(*e),
115 }
115 }
116 if err := json.Unmarshal(data, &aux); err != nil {
116 if err := json.Unmarshal(data, &aux); err != nil {
117 return err
117 return err
@@ -148,7 +148,7 @@ func (i MCPClientInfo) MarshalJSON() ([]byte, error) {
148 }
148 }
149 return ""
149 return ""
150 }(),
150 }(),
151− Alias: (Alias)(i),
151+ Alias: Alias(i),
152 })
152 })
153}
153}
154
154
@@ -159,7 +159,7 @@ func (i *MCPClientInfo) UnmarshalJSON(data []byte) error {
159 Error string `json:"error,omitempty"`
159 Error string `json:"error,omitempty"`
160 Alias
160 Alias
161 }{
161 }{
162− Alias: (Alias)(*i),
162+ Alias: Alias(*i),
163 }
163 }
164 if err := json.Unmarshal(data, &aux); err != nil {
164 if err := json.Unmarshal(data, &aux); err != nil {
165 return err
165 return err
internal/proto/proto.go+4−4
@@ -131,7 +131,7 @@ func (e LSPEvent) MarshalJSON() ([]byte, error) {
131 }
131 }
132 return ""
132 return ""
133 }(),
133 }(),
134− Alias: (Alias)(e),
134+ Alias: Alias(e),
135 })
135 })
136}
136}
137
137
@@ -142,7 +142,7 @@ func (e *LSPEvent) UnmarshalJSON(data []byte) error {
142 Error string `json:"error,omitempty"`
142 Error string `json:"error,omitempty"`
143 Alias
143 Alias
144 }{
144 }{
145− Alias: (Alias)(*e),
145+ Alias: Alias(*e),
146 }
146 }
147 if err := json.Unmarshal(data, &aux); err != nil {
147 if err := json.Unmarshal(data, &aux); err != nil {
148 return err
148 return err
@@ -176,7 +176,7 @@ func (i LSPClientInfo) MarshalJSON() ([]byte, error) {
176 }
176 }
177 return ""
177 return ""
178 }(),
178 }(),
179− Alias: (Alias)(i),
179+ Alias: Alias(i),
180 })
180 })
181}
181}
182
182
@@ -187,7 +187,7 @@ func (i *LSPClientInfo) UnmarshalJSON(data []byte) error {
187 Error string `json:"error,omitempty"`
187 Error string `json:"error,omitempty"`
188 Alias
188 Alias
189 }{
189 }{
190− Alias: (Alias)(*i),
190+ Alias: Alias(*i),
191 }
191 }
192 if err := json.Unmarshal(data, &aux); err != nil {
192 if err := json.Unmarshal(data, &aux); err != nil {
193 return err
193 return err
internal/server/logging.go+4−2
@@ -15,7 +15,8 @@ func (s *Server) loggingHandler(next http.Handler) http.Handler {
15
15
16 start := time.Now()
16 start := time.Now()
17 lrw := &loggingResponseWriter{ResponseWriter: w, statusCode: http.StatusOK}
17 lrw := &loggingResponseWriter{ResponseWriter: w, statusCode: http.StatusOK}
18− s.logger.Debug("HTTP request",
18+ s.logger.Debug(
19+ "HTTP request",
19 slog.String("method", r.Method),
20 slog.String("method", r.Method),
20 slog.String("path", r.URL.Path),
21 slog.String("path", r.URL.Path),
21 slog.String("remote_addr", r.RemoteAddr),
22 slog.String("remote_addr", r.RemoteAddr),
@@ -25,7 +26,8 @@ func (s *Server) loggingHandler(next http.Handler) http.Handler {
25 next.ServeHTTP(lrw, r)
26 next.ServeHTTP(lrw, r)
26 duration := time.Since(start)
27 duration := time.Since(start)
27
28
28− s.logger.Debug("HTTP response",
29+ s.logger.Debug(
30+ "HTTP response",
29 slog.String("method", r.Method),
31 slog.String("method", r.Method),
30 slog.String("path", r.URL.Path),
32 slog.String("path", r.URL.Path),
31 slog.Int("status", lrw.statusCode),
33 slog.Int("status", lrw.statusCode),
internal/shell/jq_test.go+2−1
@@ -185,7 +185,8 @@ func TestJQ_Success(t *testing.T) {
185 t.Parallel()
185 t.Parallel()
186
186
187 var stdout bytes.Buffer
187 var stdout bytes.Buffer
188− err := handleJQ(t.Context(),
188+ err := handleJQ(
189+ t.Context(),
189 []string{"jq", "-c", ".a"},
190 []string{"jq", "-c", ".a"},
190 strings.NewReader(`{"a":1}`),
191 strings.NewReader(`{"a":1}`),
191 &stdout, io.Discard,
192 &stdout, io.Discard,
internal/ui/chat/tools.go+10−7
@@ -690,9 +690,10 @@ func toolOutputCodeContent(sty *styles.Styles, path, content string, offset, wid
690
690
691 // Add truncation message if needed.
691 // Add truncation message if needed.
692 if len(lines) > maxLines && !expanded {
692 if len(lines) > maxLines && !expanded {
693+ out = append(
693− out = append(out, sty.Tool.ContentCodeTruncation.
694+ out, sty.Tool.ContentCodeTruncation.
694− Width(width).
695+ Width(width).
695− Render(fmt.Sprintf(assistantMessageTruncateFormat, len(lines)-maxLines)),
696+ Render(fmt.Sprintf(assistantMessageTruncateFormat, len(lines)-maxLines)),
696 )
697 )
697 }
698 }
698
699
@@ -851,7 +852,8 @@ func renderHookLine(sty *styles.Styles, hi hooks.HookInfo, rawName, detail strin
851 arrowStyle = sty.Tool.HookDeniedLabel
852 arrowStyle = sty.Tool.HookDeniedLabel
852 }
853 }
853
854
854− return fmt.Sprintf("%s %s%s%s %s %s",
855+ return fmt.Sprintf(
856+ "%s %s%s%s %s %s",
855 labelStyle.Render("Hook"),
857 labelStyle.Render("Hook"),
856 name,
858 name,
857 namePad,
859 namePad,
@@ -1063,9 +1065,10 @@ func toolOutputMarkdownContent(sty *styles.Styles, content string, width int, ex
1063 }
1065 }
1064
1066
1065 if len(lines) > maxLines && !expanded {
1067 if len(lines) > maxLines && !expanded {
1068+ out = append(
1066− out = append(out, sty.Tool.ContentTruncation.
1069+ out, sty.Tool.ContentTruncation.
1067− Width(width).
1070+ Width(width).
1068− Render(fmt.Sprintf(assistantMessageTruncateFormat, len(lines)-maxLines)),
1071+ Render(fmt.Sprintf(assistantMessageTruncateFormat, len(lines)-maxLines)),
1069 )
1072 )
1070 }
1073 }
1071
1074
internal/ui/dialog/commands.go+4−2
@@ -510,7 +510,8 @@ func (c *Commands) defaultCommands() []*CommandItem {
510 }
510 }
511 commands = append(commands, NewCommandItem(c.com.Styles, "toggle_notifications", notificationLabel, "", ActionToggleNotifications{}))
511 commands = append(commands, NewCommandItem(c.com.Styles, "toggle_notifications", notificationLabel, "", ActionToggleNotifications{}))
512
512
513− commands = append(commands,
513+ commands = append(
514+ commands,
514 NewCommandItem(c.com.Styles, "toggle_yolo", "Toggle Yolo Mode", "", ActionToggleYoloMode{}),
515 NewCommandItem(c.com.Styles, "toggle_yolo", "Toggle Yolo Mode", "", ActionToggleYoloMode{}),
515 NewCommandItem(c.com.Styles, "toggle_help", "Toggle Help", "ctrl+g", ActionToggleHelp{}),
516 NewCommandItem(c.com.Styles, "toggle_help", "Toggle Help", "ctrl+g", ActionToggleHelp{}),
516 NewCommandItem(c.com.Styles, "init", "Initialize Project", "", ActionInitializeProject{}),
517 NewCommandItem(c.com.Styles, "init", "Initialize Project", "", ActionInitializeProject{}),
@@ -523,7 +524,8 @@ func (c *Commands) defaultCommands() []*CommandItem {
523 }
524 }
524 commands = append(commands, NewCommandItem(c.com.Styles, "toggle_transparent", transparentLabel, "", ActionToggleTransparentBackground{}))
525 commands = append(commands, NewCommandItem(c.com.Styles, "toggle_transparent", transparentLabel, "", ActionToggleTransparentBackground{}))
525
526
526− commands = append(commands,
527+ commands = append(
528+ commands,
527 NewCommandItem(c.com.Styles, "quit", "Quit", "ctrl+c", tea.QuitMsg{}).WithAliases("exit"),
529 NewCommandItem(c.com.Styles, "quit", "Quit", "ctrl+c", tea.QuitMsg{}).WithAliases("exit"),
528 )
530 )
529
531
internal/ui/dialog/permissions.go+2−1
@@ -775,7 +775,8 @@ func (p *Permissions) ShortHelp() []key.Binding {
775 }
775 }
776
776
777 if p.hasDiffView() {
777 if p.hasDiffView() {
778− bindings = append(bindings,
778+ bindings = append(
779+ bindings,
779 p.keyMap.ToggleDiffMode,
780 p.keyMap.ToggleDiffMode,
780 p.keyMap.ToggleFullscreen,
781 p.keyMap.ToggleFullscreen,
781 )
782 )
internal/ui/dialog/sessions_item.go+2−1
@@ -184,7 +184,8 @@ func renderItem(t ListItemStyles, title string, info string, focused bool, width
184 // precisely via [ansi.AttrUnderline] and [ansi.AttrNoUnderline]
184 // precisely via [ansi.AttrUnderline] and [ansi.AttrNoUnderline]
185 // which only affect the underline attribute without interfering
185 // which only affect the underline attribute without interfering
186 // with other style attributes.
186 // with other style attributes.
187− parts = append(parts,
187+ parts = append(
188+ parts,
188 ansi.NewStyle().Underline(true).String(),
189 ansi.NewStyle().Underline(true).String(),
189 ansi.Cut(title, start, stop+1),
190 ansi.Cut(title, start, stop+1),
190 ansi.NewStyle().Underline(false).String(),
191 ansi.NewStyle().Underline(false).String(),
internal/ui/model/header.go+2−1
@@ -122,7 +122,8 @@ func (h *header) drawHeader(
122 b.WriteString(details)
122 b.WriteString(details)
123
123
124 view := uv.NewStyledString(
124 view := uv.NewStyledString(
125− t.Header.Wrapper.Padding(0, rightPadding, 0, leftPadding).Render(b.String()))
125+ t.Header.Wrapper.Padding(0, rightPadding, 0, leftPadding).Render(b.String()),
126+ )
126 view.Draw(scr, area)
127 view.Draw(scr, area)
127}
128}
128
129
internal/ui/model/ui.go+22−11
@@ -2301,7 +2301,8 @@ func (m *UI) ShortHelp() []key.Binding {
2301 tab.SetHelp("tab", "focus editor")
2301 tab.SetHelp("tab", "focus editor")
2302 }
2302 }
2303
2303
2304− binds = append(binds,
2304+ binds = append(
2305+ binds,
2305 tab,
2306 tab,
2306 commands,
2307 commands,
2307 k.Models,
2308 k.Models,
@@ -2309,11 +2310,13 @@ func (m *UI) ShortHelp() []key.Binding {
2309
2310
2310 switch m.focus {
2311 switch m.focus {
2311 case uiFocusEditor:
2312 case uiFocusEditor:
2312− binds = append(binds,
2313+ binds = append(
2314+ binds,
2313 k.Editor.Newline,
2315 k.Editor.Newline,
2314 )
2316 )
2315 case uiFocusMain:
2317 case uiFocusMain:
2316− binds = append(binds,
2318+ binds = append(
2319+ binds,
2317 k.Chat.UpDown,
2320 k.Chat.UpDown,
2318 k.Chat.UpDownOneItem,
2321 k.Chat.UpDownOneItem,
2319 k.Chat.PageUp,
2322 k.Chat.PageUp,
@@ -2328,14 +2331,16 @@ func (m *UI) ShortHelp() []key.Binding {
2328 // TODO: other states
2331 // TODO: other states
2329 // if m.session == nil {
2332 // if m.session == nil {
2330 // no session selected
2333 // no session selected
2331− binds = append(binds,
2334+ binds = append(
2335+ binds,
2332 commands,
2336 commands,
2333 k.Models,
2337 k.Models,
2334 k.Editor.Newline,
2338 k.Editor.Newline,
2335 )
2339 )
2336 }
2340 }
2337
2341
2338− binds = append(binds,
2342+ binds = append(
2343+ binds,
2339 k.Quit,
2344 k.Quit,
2340 k.Help,
2345 k.Help,
2341 )
2346 )
@@ -2382,7 +2387,8 @@ func (m *UI) FullHelp() [][]key.Binding {
2382 tab.SetHelp("tab", "focus editor")
2387 tab.SetHelp("tab", "focus editor")
2383 }
2388 }
2384
2389
2385− mainBinds = append(mainBinds,
2390+ mainBinds = append(
2391+ mainBinds,
2386 tab,
2392 tab,
2387 commands,
2393 commands,
2388 k.Models,
2394 k.Models,
@@ -2406,7 +2412,8 @@ func (m *UI) FullHelp() [][]key.Binding {
2406 }
2412 }
2407 binds = append(binds, editorBinds)
2413 binds = append(binds, editorBinds)
2408 if hasAttachments {
2414 if hasAttachments {
2409− binds = append(binds,
2415+ binds = append(
2416+ binds,
2410 []key.Binding{
2417 []key.Binding{
2411 k.Editor.AttachmentDeleteMode,
2418 k.Editor.AttachmentDeleteMode,
2412 k.Editor.DeleteAllAttachments,
2419 k.Editor.DeleteAllAttachments,
@@ -2415,7 +2422,8 @@ func (m *UI) FullHelp() [][]key.Binding {
2415 )
2422 )
2416 }
2423 }
2417 case uiFocusMain:
2424 case uiFocusMain:
2418− binds = append(binds,
2425+ binds = append(
2426+ binds,
2419 []key.Binding{
2427 []key.Binding{
2420 k.Chat.UpDown,
2428 k.Chat.UpDown,
2421 k.Chat.UpDownOneItem,
2429 k.Chat.UpDownOneItem,
@@ -2440,7 +2448,8 @@ func (m *UI) FullHelp() [][]key.Binding {
2440 default:
2448 default:
2441 if m.session == nil {
2449 if m.session == nil {
2442 // no session selected
2450 // no session selected
2443− binds = append(binds,
2451+ binds = append(
2452+ binds,
2444 []key.Binding{
2453 []key.Binding{
2445 commands,
2454 commands,
2446 k.Models,
2455 k.Models,
@@ -2457,7 +2466,8 @@ func (m *UI) FullHelp() [][]key.Binding {
2457 }
2466 }
2458 binds = append(binds, editorBinds)
2467 binds = append(binds, editorBinds)
2459 if hasAttachments {
2468 if hasAttachments {
2460− binds = append(binds,
2469+ binds = append(
2470+ binds,
2461 []key.Binding{
2471 []key.Binding{
2462 k.Editor.AttachmentDeleteMode,
2472 k.Editor.AttachmentDeleteMode,
2463 k.Editor.DeleteAllAttachments,
2473 k.Editor.DeleteAllAttachments,
@@ -2468,7 +2478,8 @@ func (m *UI) FullHelp() [][]key.Binding {
2468 }
2478 }
2469 }
2479 }
2470
2480
2471− binds = append(binds,
2481+ binds = append(
2482+ binds,
2472 []key.Binding{
2483 []key.Binding{
2473 help,
2484 help,
2474 k.Quit,
2485 k.Quit,