@@ -24,6 +24,7 @@ const (
2424 EventTurnStart // new agentic turn
2525 EventDone // agent finished all turns
2626 EventError // error occurred
27+ EventPermission // permission request for TUI
2728)
2829
2930type TokenUsage struct {
@@ -37,11 +38,12 @@ type AgentEvent struct {
3738 Tool string // EventToolStart / EventToolResult — tool name
3839 ToolID string // EventToolStart / EventToolResult — tool call ID
3940 Args map [string ]any // EventToolStart — parsed arguments
40- Output string // EventToolResult — tool output
41- Success bool // EventToolResult
42- Tokens TokenUsage // EventUsage
43- Turn int // EventTurnStart
44- Error error // EventError
41+ Output string // EventToolResult — tool output
42+ Success bool // EventToolResult
43+ Tokens TokenUsage // EventUsage
44+ Turn int // EventTurnStart
45+ Error error // EventError
46+ Permission * PermissionRequest // EventPermission
4547}
4648
4749// ──────────────────────────────────────────────────────────────
@@ -64,24 +66,34 @@ Available tools:
6466- search_files: Regex search across files (powered by ripgrep). Find definitions, usages, etc.
6567- web_search: Search the web. Use for current info, docs, versions, error solutions, or anything not in local files.
6668- dispatch_agent: Spawn a read-only research subagent to investigate questions in isolated context.
67- - shell: Run any shell command. Use for builds, tests, git, package management.
69+ - shell: Run any shell command. Use for builds, tests, package management.
70+ - git_status: Show working tree status (staged, unstaged, untracked files).
71+ - git_diff: Show file diffs (staged, unstaged, or between refs).
72+ - git_log: Show recent commit history.
73+ - git_commit: Stage files and create a commit.
74+ - git_branch: List, create, or switch branches.
6875
6976Guidelines:
7077- You can call multiple tools in parallel — read_file, list_files, search_files, web_search, and dispatch_agent all run concurrently
7178- Use list_files and search_files to explore the project before making changes
7279- Read files before editing them — understand existing code first
7380- Make targeted, minimal changes — don't rewrite entire files unnecessarily
7481- For multiple related edits, prefer multi_edit over separate edit_file calls
82+ - Use git tools instead of shell for git operations — they provide structured output
83+ - After you edit files, the system may report diagnostics (errors, warnings) from language tools. If diagnostics appear, fix the issues before moving on.
7584- If a tool fails, read the error and try a different approach
7685- When finished, briefly summarize what you changed and why`
7786
7887type Agent struct {
79- client * LLMClient
80- workDir string
81- history []ChatMessage
82- events chan <- AgentEvent
83- stopCh <- chan struct {}
84- files int // count of files created/modified
88+ client * LLMClient
89+ workDir string
90+ history []ChatMessage
91+ events chan <- AgentEvent
92+ stopCh <- chan struct {}
93+ files int // count of files created/modified
94+ permCh chan PermissionResponse
95+ permState * PermissionState
96+ diag * DiagnosticsEngine
8597}
8698
8799func NewAgent (client * LLMClient , workDir string , events chan <- AgentEvent , stopCh <- chan struct {}) * Agent {
@@ -94,6 +106,9 @@ func NewAgent(client *LLMClient, workDir string, events chan<- AgentEvent, stopC
94106 history : []ChatMessage {
95107 {Role : "system" , Content : strPtr (sysContent )},
96108 },
109+ permCh : make (chan PermissionResponse , 1 ),
110+ permState : & PermissionState {TrustedTools : map [string ]bool {}},
111+ diag : NewDiagnosticsEngine (workDir ),
97112 }
98113}
99114
@@ -412,6 +427,32 @@ func (a *Agent) executeToolCalls(toolCalls []ToolCall, consecutiveErrors *int) {
412427 argsMap = map [string ]any {"_raw" : tc .Function .Arguments }
413428 }
414429
430+ // Check permission before executing
431+ if ! a .checkPermission (tc .Function .Name , argsMap ) {
432+ output := "Skipped: permission denied by user"
433+ a .events <- AgentEvent {
434+ Type : EventToolStart ,
435+ Tool : tc .Function .Name ,
436+ ToolID : tc .ID ,
437+ Args : argsMap ,
438+ }
439+ a .events <- AgentEvent {
440+ Type : EventToolResult ,
441+ Tool : tc .Function .Name ,
442+ ToolID : tc .ID ,
443+ Args : argsMap ,
444+ Output : output ,
445+ Success : false ,
446+ }
447+ a .history = append (a .history , ChatMessage {
448+ Role : "tool" ,
449+ ToolCallID : tc .ID ,
450+ Name : tc .Function .Name ,
451+ Content : strPtr (output ),
452+ })
453+ continue
454+ }
455+
415456 a .events <- AgentEvent {
416457 Type : EventToolStart ,
417458 Tool : tc .Function .Name ,
@@ -442,6 +483,7 @@ func (a *Agent) executeToolCalls(toolCalls []ToolCall, consecutiveErrors *int) {
442483 allErrors = false
443484 if tc .Function .Name == "write_file" || tc .Function .Name == "edit_file" || tc .Function .Name == "multi_edit" {
444485 a .files ++
486+ a .maybeInjectDiagnostics (tc .Function .Name , argsMap )
445487 }
446488 }
447489
@@ -473,3 +515,88 @@ func (a *Agent) executeToolCalls(toolCalls []ToolCall, consecutiveErrors *int) {
473515func (a * Agent ) FilesChanged () int {
474516 return a .files
475517}
518+
519+ // maybeInjectDiagnostics runs language checkers after file modifications
520+ // and injects a system message with errors if found.
521+ func (a * Agent ) maybeInjectDiagnostics (toolName string , args map [string ]any ) {
522+ if a .diag == nil || ! a .diag .Enabled {
523+ return
524+ }
525+
526+ // Determine which files were modified
527+ var files []string
528+ switch toolName {
529+ case "write_file" , "edit_file" :
530+ if p , ok := args ["path" ].(string ); ok {
531+ files = []string {p }
532+ }
533+ case "multi_edit" :
534+ if edits , ok := args ["edits" ]; ok {
535+ if arr , ok := edits .([]interface {}); ok {
536+ seen := map [string ]bool {}
537+ for _ , e := range arr {
538+ if m , ok := e .(map [string ]interface {}); ok {
539+ if p , ok := m ["path" ].(string ); ok && ! seen [p ] {
540+ files = append (files , p )
541+ seen [p ] = true
542+ }
543+ }
544+ }
545+ }
546+ }
547+ }
548+
549+ if len (files ) == 0 {
550+ return
551+ }
552+
553+ diags := a .diag .CheckFiles (files )
554+ if len (diags ) == 0 {
555+ return
556+ }
557+
558+ // Inject as system message so the LLM sees errors
559+ msg := formatDiagnosticsMessage (diags )
560+ a .history = append (a .history , ChatMessage {
561+ Role : "system" ,
562+ Content : strPtr (msg ),
563+ })
564+ }
565+
566+ // checkPermission asks the TUI for permission if needed.
567+ // Returns true if the tool should execute, false to skip.
568+ func (a * Agent ) checkPermission (toolName string , args map [string ]any ) bool {
569+ // Check session-level trust
570+ if a .permState .Level == PermTrustAll {
571+ return true
572+ }
573+ if a .permState .TrustedTools [toolName ] {
574+ return true
575+ }
576+
577+ // Check if this tool needs permission
578+ if ! NeedsPermission (toolName , args ) {
579+ return true
580+ }
581+
582+ // Send permission request to TUI
583+ req := & PermissionRequest {
584+ Tool : toolName ,
585+ Args : args ,
586+ Summary : PermissionSummary (toolName , args ),
587+ }
588+ a .events <- AgentEvent {Type : EventPermission , Permission : req }
589+
590+ // Block waiting for TUI response (or stop signal)
591+ select {
592+ case resp := <- a .permCh :
593+ if resp .TrustLevel == PermTrustTool {
594+ a .permState .TrustedTools [toolName ] = true
595+ } else if resp .TrustLevel == PermTrustAll {
596+ a .permState .Level = PermTrustAll
597+ }
598+ return resp .Allowed
599+ case <- a .stopCh :
600+ return false
601+ }
602+ }
0 commit comments