Skip to content

Commit 8eb6d5b

Browse files
author
okurucan
committed
UI yenileme, setup kaldırma, MCP düzenleme
1 parent 4c4dd3e commit 8eb6d5b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+5092
-2355
lines changed

app.go

Lines changed: 370 additions & 14 deletions
Large diffs are not rendered by default.

backend/claude/process.go

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -175,37 +175,38 @@ func buildArgs(opts ProcessOptions) []string {
175175
"--verbose",
176176
}
177177

178+
isResume := opts.SessionID != ""
179+
178180
// Resume an existing Claude conversation
179-
if opts.SessionID != "" {
181+
if isResume {
180182
args = append(args, "--resume", opts.SessionID)
181183
}
182184

183-
if opts.Model != "" {
184-
args = append(args, "--model", opts.Model)
185-
}
185+
// Only set model and system prompt for new sessions.
186+
// Resumed sessions already have these values from the original session;
187+
// passing them again can cause CLI errors or unexpected behavior.
188+
if !isResume {
189+
if opts.Model != "" {
190+
args = append(args, "--model", opts.Model)
191+
}
186192

187-
if opts.SystemPrompt != "" {
188-
args = append(args, "--system-prompt", opts.SystemPrompt)
189-
}
193+
if opts.SystemPrompt != "" {
194+
args = append(args, "--system-prompt", opts.SystemPrompt)
195+
}
190196

191-
if len(opts.AllowedTools) > 0 {
192-
for _, tool := range opts.AllowedTools {
193-
args = append(args, "--allowedTools", tool)
197+
if len(opts.AllowedTools) > 0 {
198+
for _, tool := range opts.AllowedTools {
199+
args = append(args, "--allowedTools", tool)
200+
}
194201
}
195202
}
196203

197-
switch opts.Permissions {
198-
case "bypassPermissions":
199-
args = append(args, "--dangerously-skip-permissions")
200-
case "acceptEdits":
201-
args = append(args, "--permission-mode", "acceptEdits")
202-
case "default":
203-
// use default permission mode
204-
case "":
205-
args = append(args, "--dangerously-skip-permissions")
206-
default:
207-
args = append(args, "--permission-mode", opts.Permissions)
208-
}
204+
// In -p (print/non-interactive) mode, stdin is closed so interactive
205+
// permission approval is impossible. Always use --dangerously-skip-permissions
206+
// to ensure all tools (Bash, Read, Write, Edit, etc.) can execute.
207+
// Without this, modes like "acceptEdits" would silently reject Bash/Read
208+
// calls since there's no stdin to approve them.
209+
args = append(args, "--dangerously-skip-permissions")
209210

210211
// Prompt is the final positional argument.
211212
// Use "--" to end option parsing so the prompt text isn't misread as a flag.

backend/models/enums.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ package models
33
type TaskStatus string
44

55
const (
6-
TaskStatusPending TaskStatus = "pending"
7-
TaskStatusQueued TaskStatus = "queued"
8-
TaskStatusRunning TaskStatus = "running"
9-
TaskStatusCompleted TaskStatus = "completed"
10-
TaskStatusFailed TaskStatus = "failed"
11-
TaskStatusCancelled TaskStatus = "cancelled"
6+
TaskStatusPending TaskStatus = "pending"
7+
TaskStatusQueued TaskStatus = "queued"
8+
TaskStatusRunning TaskStatus = "running"
9+
TaskStatusCompleted TaskStatus = "completed"
10+
TaskStatusFailed TaskStatus = "failed"
11+
TaskStatusCancelled TaskStatus = "cancelled"
12+
TaskStatusAwaitingInput TaskStatus = "awaiting_input"
1213
)
1314

1415
type SessionStatus string

backend/models/project.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ type Project struct {
88
Path string `json:"path"`
99
TestCommand string `json:"test_command,omitempty"`
1010
BuildCommand string `json:"build_command,omitempty"`
11-
SetupCommand string `json:"setup_command,omitempty"`
11+
SetupCommands StringSlice `json:"setup_commands" gorm:"type:text"`
1212
CreatedAt time.Time `json:"created_at"`
1313
UpdatedAt time.Time `json:"updated_at"`
1414
}

backend/models/task.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ type Task struct {
1919
ResultText string `json:"result_text,omitempty"`
2020
FilesChanged StringSlice `json:"files_changed" gorm:"type:text"`
2121

22+
// Agent interaction - set when agent needs user input to continue
23+
PendingInputData string `json:"pending_input_data,omitempty" gorm:"type:text"`
24+
2225
// Test/Build
2326
TestPassed *bool `json:"test_passed,omitempty"`
2427
TestOutput string `json:"test_output,omitempty"`

backend/services/agent_runner.go

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"context"
77
"fmt"
88
"log"
9+
"strings"
910
"sync"
1011

1112
wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime"
@@ -84,14 +85,20 @@ func (ar *AgentRunner) bufferEvent(taskID string, event claude.TaskStreamEvent)
8485

8586
// RunTaskOptions configures a RunTask invocation.
8687
type RunTaskOptions struct {
87-
SessionID string // Claude session ID for --resume (empty = new session)
88-
Prompt string // Override task prompt (used for follow-ups)
89-
OnSessionID func(sessionID string) // Callback when Claude session_id is received
88+
SessionID string // Claude session ID for --resume (empty = new session)
89+
Prompt string // Override task prompt (used for follow-ups)
90+
OnSessionID func(sessionID string) // Callback when Claude session_id is received
91+
}
92+
93+
// RunResult carries information about how the task run completed.
94+
type RunResult struct {
95+
NeedsInput bool // true if the agent's output indicates it needs user input
96+
LastText string // the last text output from the agent (for displaying in the UI)
9097
}
9198

9299
// RunTask starts a Claude Code process for a task with the given agent configuration.
93100
// It streams events to the frontend and returns when the process completes.
94-
func (ar *AgentRunner) RunTask(ctx context.Context, task *models.Task, agent *models.Agent, workDir string, opts ...RunTaskOptions) error {
101+
func (ar *AgentRunner) RunTask(ctx context.Context, task *models.Task, agent *models.Agent, workDir string, opts ...RunTaskOptions) (*RunResult, error) {
95102
var runOpts RunTaskOptions
96103
if len(opts) > 0 {
97104
runOpts = opts[0]
@@ -113,7 +120,7 @@ func (ar *AgentRunner) RunTask(ctx context.Context, task *models.Task, agent *mo
113120
SessionID: runOpts.SessionID,
114121
})
115122
if err != nil {
116-
return fmt.Errorf("start claude (%s): %w", ar.cliPath, err)
123+
return nil, fmt.Errorf("start claude (%s): %w", ar.cliPath, err)
117124
}
118125

119126
ar.mu.Lock()
@@ -126,8 +133,9 @@ func (ar *AgentRunner) RunTask(ctx context.Context, task *models.Task, agent *mo
126133
ar.mu.Unlock()
127134
}()
128135

129-
// Stream events to frontend
136+
// Stream events to frontend, track last text for question detection
130137
eventCount := 0
138+
var lastText string
131139
for event := range proc.Events() {
132140
eventCount++
133141
if eventCount <= 3 || eventCount%10 == 0 {
@@ -141,9 +149,20 @@ func (ar *AgentRunner) RunTask(ctx context.Context, task *models.Task, agent *mo
141149

142150
ar.emitTaskEvent(task.ID, event)
143151

152+
// Track last text content for question detection
153+
if event.Type == "assistant" {
154+
text := claude.ExtractTextContent(event)
155+
if text != "" {
156+
lastText = text
157+
}
158+
}
159+
144160
// Capture result text
145161
if event.Type == "result" {
146162
task.ResultText = event.Result
163+
if event.Result != "" {
164+
lastText = event.Result
165+
}
147166
}
148167
}
149168
log.Printf("[runner] task %s: stream ended after %d events", task.ID[:8], eventCount)
@@ -165,11 +184,60 @@ func (ar *AgentRunner) RunTask(ctx context.Context, task *models.Task, agent *mo
165184
if proc.Err() != nil {
166185
stderrOutput := proc.Stderr()
167186
if stderrOutput != "" {
168-
return fmt.Errorf("claude process: %w\nstderr: %s", proc.Err(), stderrOutput)
187+
return nil, fmt.Errorf("claude process: %w\nstderr: %s", proc.Err(), stderrOutput)
188+
}
189+
return nil, fmt.Errorf("claude process: %w", proc.Err())
190+
}
191+
192+
// Detect if the agent's output indicates it needs user input
193+
result := &RunResult{
194+
LastText: lastText,
195+
NeedsInput: detectNeedsInput(lastText),
196+
}
197+
return result, nil
198+
}
199+
200+
// detectNeedsInput checks if the agent's last output looks like it's asking for user input.
201+
// Only checks the last paragraph to avoid false positives from questions in the middle of output.
202+
func detectNeedsInput(text string) bool {
203+
if text == "" {
204+
return false
205+
}
206+
trimmed := strings.TrimSpace(text)
207+
208+
// Only look at the last paragraph (after last double newline) to reduce false positives.
209+
// Agents often have questions mid-output but the final paragraph is what matters.
210+
if idx := strings.LastIndex(trimmed, "\n\n"); idx >= 0 {
211+
trimmed = strings.TrimSpace(trimmed[idx:])
212+
}
213+
214+
// Check if the last paragraph ends with a question mark
215+
if strings.HasSuffix(trimmed, "?") {
216+
return true
217+
}
218+
219+
lower := strings.ToLower(trimmed)
220+
221+
// Common patterns indicating the agent wants approval/input (checked in last paragraph only)
222+
inputPatterns := []string{
223+
"approve", "approval", "onay",
224+
"ready for review", "ready to proceed",
225+
"which option", "hangi seçenek", "hangisini",
226+
"should i proceed", "devam edeyim mi",
227+
"waiting for", "bekliyor",
228+
"please confirm", "lütfen onaylayın",
229+
"let me know", "bana bildirin",
230+
"what do you think", "ne düşünüyorsun",
231+
"do you want", "ister misin",
232+
"select one", "birini seç",
233+
}
234+
for _, pattern := range inputPatterns {
235+
if strings.Contains(lower, pattern) {
236+
return true
169237
}
170-
return fmt.Errorf("claude process: %w", proc.Err())
171238
}
172-
return nil
239+
240+
return false
173241
}
174242

175243
// StopTask kills the Claude process for a specific task.

backend/services/diff_tracker.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,21 @@ func ParseHunks(unifiedDiff string) []DiffHunk {
104104
return hunks
105105
}
106106

107+
// excludeDirs lists directories to skip when computing diffs.
108+
var excludeDirs = []string{
109+
".git", "node_modules", ".next", "__pycache__", "vendor",
110+
".venv", ".cache", ".claude", ".turbo",
111+
}
112+
107113
// ComputeDiff compares a task workspace against the original project directory.
108114
func (dt *DiffTracker) ComputeDiff(originalPath, workspacePath string) (*DiffResult, error) {
109-
// Use diff to find changes
110-
cmd := exec.Command("diff", "-rq", originalPath, workspacePath)
115+
// Use diff to find changes, excluding VCS and dependency directories
116+
args := []string{"-rq"}
117+
for _, dir := range excludeDirs {
118+
args = append(args, "--exclude="+dir)
119+
}
120+
args = append(args, originalPath, workspacePath)
121+
cmd := exec.Command("diff", args...)
111122
output, _ := cmd.CombinedOutput() // diff returns exit code 1 when files differ
112123

113124
result := &DiffResult{}

0 commit comments

Comments
 (0)