@caretive/caret-cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.npmrc.tmp +2 -0
- package/README.md +72 -0
- package/cmd/cline/main.go +348 -0
- package/cmd/cline-host/main.go +71 -0
- package/e2e/default_update_test.go +154 -0
- package/e2e/helpers_test.go +378 -0
- package/e2e/main_test.go +47 -0
- package/e2e/mixed_stress_test.go +120 -0
- package/e2e/sqlite_helper.go +161 -0
- package/e2e/start_list_test.go +178 -0
- package/go.mod +64 -0
- package/go.sum +162 -0
- package/man/cline.1 +331 -0
- package/man/cline.1.md +332 -0
- package/package.json +54 -0
- package/pkg/cli/auth/auth_cline_provider.go +285 -0
- package/pkg/cli/auth/auth_menu.go +323 -0
- package/pkg/cli/auth/auth_subscription.go +130 -0
- package/pkg/cli/auth/byo_quick_setup.go +247 -0
- package/pkg/cli/auth/models_cline.go +141 -0
- package/pkg/cli/auth/models_list_fetch.go +156 -0
- package/pkg/cli/auth/models_list_static.go +69 -0
- package/pkg/cli/auth/providers_byo.go +184 -0
- package/pkg/cli/auth/providers_list.go +517 -0
- package/pkg/cli/auth/update_api_configurations.go +647 -0
- package/pkg/cli/auth/wizard_byo.go +764 -0
- package/pkg/cli/auth/wizard_byo_bedrock.go +193 -0
- package/pkg/cli/auth/wizard_byo_oca.go +366 -0
- package/pkg/cli/auth.go +43 -0
- package/pkg/cli/clerror/cline_error.go +187 -0
- package/pkg/cli/config/manager.go +208 -0
- package/pkg/cli/config/settings_renderer.go +198 -0
- package/pkg/cli/config.go +152 -0
- package/pkg/cli/display/ansi.go +27 -0
- package/pkg/cli/display/banner.go +211 -0
- package/pkg/cli/display/deduplicator.go +95 -0
- package/pkg/cli/display/markdown_renderer.go +139 -0
- package/pkg/cli/display/renderer.go +304 -0
- package/pkg/cli/display/segment_streamer.go +212 -0
- package/pkg/cli/display/streaming.go +134 -0
- package/pkg/cli/display/system_renderer.go +269 -0
- package/pkg/cli/display/tool_renderer.go +455 -0
- package/pkg/cli/display/tool_result_parser.go +371 -0
- package/pkg/cli/display/typewriter.go +210 -0
- package/pkg/cli/doctor.go +65 -0
- package/pkg/cli/global/cline-clients.go +501 -0
- package/pkg/cli/global/global.go +113 -0
- package/pkg/cli/global/registry.go +304 -0
- package/pkg/cli/handlers/ask_handlers.go +339 -0
- package/pkg/cli/handlers/handler.go +130 -0
- package/pkg/cli/handlers/say_handlers.go +521 -0
- package/pkg/cli/instances.go +506 -0
- package/pkg/cli/logs.go +382 -0
- package/pkg/cli/output/coordinator.go +167 -0
- package/pkg/cli/output/input_model.go +497 -0
- package/pkg/cli/sqlite/locks.go +366 -0
- package/pkg/cli/task/history_handler.go +72 -0
- package/pkg/cli/task/input_handler.go +577 -0
- package/pkg/cli/task/manager.go +1283 -0
- package/pkg/cli/task/settings_parser.go +754 -0
- package/pkg/cli/task/stream_coordinator.go +60 -0
- package/pkg/cli/task.go +675 -0
- package/pkg/cli/terminal/keyboard.go +695 -0
- package/pkg/cli/tui/HELP_WANTED.md +1 -0
- package/pkg/cli/types/history.go +17 -0
- package/pkg/cli/types/messages.go +329 -0
- package/pkg/cli/types/state.go +59 -0
- package/pkg/cli/updater/updater.go +409 -0
- package/pkg/cli/version.go +43 -0
- package/pkg/common/constants.go +6 -0
- package/pkg/common/schema.go +54 -0
- package/pkg/common/types.go +54 -0
- package/pkg/common/utils.go +185 -0
- package/pkg/generated/field_overrides.go +39 -0
- package/pkg/generated/providers.go +1584 -0
- package/pkg/hostbridge/diff.go +351 -0
- package/pkg/hostbridge/disabled/watch.go +39 -0
- package/pkg/hostbridge/disabled/window.go +63 -0
- package/pkg/hostbridge/disabled/workspace.go +66 -0
- package/pkg/hostbridge/env.go +166 -0
- package/pkg/hostbridge/grpc_server.go +113 -0
- package/pkg/hostbridge/simple.go +43 -0
- package/pkg/hostbridge/simple_workspace.go +85 -0
- package/pkg/hostbridge/window.go +129 -0
- package/scripts/publish-caret-cli.sh +39 -0
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
package task
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"context"
|
|
5
|
+
"encoding/json"
|
|
6
|
+
"errors"
|
|
7
|
+
"fmt"
|
|
8
|
+
"strings"
|
|
9
|
+
"sync"
|
|
10
|
+
"time"
|
|
11
|
+
|
|
12
|
+
tea "github.com/charmbracelet/bubbletea"
|
|
13
|
+
"github.com/charmbracelet/lipgloss"
|
|
14
|
+
"github.com/cline/cli/pkg/cli/global"
|
|
15
|
+
"github.com/cline/cli/pkg/cli/output"
|
|
16
|
+
"github.com/cline/cli/pkg/cli/types"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
// InputHandler manages interactive user input during follow mode
|
|
20
|
+
type InputHandler struct {
|
|
21
|
+
manager *Manager
|
|
22
|
+
coordinator *StreamCoordinator
|
|
23
|
+
cancelFunc context.CancelFunc
|
|
24
|
+
mu sync.RWMutex
|
|
25
|
+
isRunning bool
|
|
26
|
+
pollTicker *time.Ticker
|
|
27
|
+
program *tea.Program
|
|
28
|
+
programRunning bool
|
|
29
|
+
programDoneChan chan struct{} // Signals when program actually exits
|
|
30
|
+
resultChan chan output.InputSubmitMsg
|
|
31
|
+
cancelChan chan struct{}
|
|
32
|
+
feedbackApproval bool // Track if we're in feedback after approval
|
|
33
|
+
feedbackApproved bool // Track the approval decision
|
|
34
|
+
approvalMessage *types.ClineMessage // Store the approval message for determining action
|
|
35
|
+
ctx context.Context // Context for restart callback
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// NewInputHandler creates a new input handler
|
|
39
|
+
func NewInputHandler(manager *Manager, coordinator *StreamCoordinator, cancelFunc context.CancelFunc) *InputHandler {
|
|
40
|
+
return &InputHandler{
|
|
41
|
+
manager: manager,
|
|
42
|
+
coordinator: coordinator,
|
|
43
|
+
cancelFunc: cancelFunc,
|
|
44
|
+
isRunning: false,
|
|
45
|
+
pollTicker: time.NewTicker(500 * time.Millisecond),
|
|
46
|
+
resultChan: make(chan output.InputSubmitMsg, 1),
|
|
47
|
+
cancelChan: make(chan struct{}, 1),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Start begins monitoring for input opportunities
|
|
52
|
+
func (ih *InputHandler) Start(ctx context.Context, errChan chan error) {
|
|
53
|
+
ih.mu.Lock()
|
|
54
|
+
ih.isRunning = true
|
|
55
|
+
ih.mu.Unlock()
|
|
56
|
+
|
|
57
|
+
defer func() {
|
|
58
|
+
ih.mu.Lock()
|
|
59
|
+
ih.isRunning = false
|
|
60
|
+
ih.mu.Unlock()
|
|
61
|
+
ih.pollTicker.Stop()
|
|
62
|
+
if ih.program != nil {
|
|
63
|
+
ih.program.Quit()
|
|
64
|
+
}
|
|
65
|
+
}()
|
|
66
|
+
|
|
67
|
+
for {
|
|
68
|
+
select {
|
|
69
|
+
case <-ctx.Done():
|
|
70
|
+
return
|
|
71
|
+
case <-ih.pollTicker.C:
|
|
72
|
+
// First check if approval is needed
|
|
73
|
+
needsApproval, approvalMsg, err := ih.manager.CheckNeedsApproval(ctx)
|
|
74
|
+
if err != nil {
|
|
75
|
+
if global.Config.Verbose {
|
|
76
|
+
output.Printf("\nDebug: CheckNeedsApproval error: %v\n", err)
|
|
77
|
+
}
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if needsApproval {
|
|
82
|
+
ih.coordinator.SetInputAllowed(true)
|
|
83
|
+
|
|
84
|
+
// Show approval prompt
|
|
85
|
+
approved, feedback, err := ih.promptForApproval(ctx, approvalMsg)
|
|
86
|
+
|
|
87
|
+
if err != nil {
|
|
88
|
+
// Check if the error is due to interrupt (Ctrl+C) or context cancellation
|
|
89
|
+
if errors.Is(err, context.Canceled) || ctx.Err() != nil {
|
|
90
|
+
// User pressed Ctrl+C - cancel context to exit FollowConversation
|
|
91
|
+
ih.cancelFunc()
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
if global.Config.Verbose {
|
|
95
|
+
output.Printf("\nDebug: Approval prompt error: %v\n", err)
|
|
96
|
+
}
|
|
97
|
+
continue
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
ih.coordinator.SetInputAllowed(false)
|
|
101
|
+
|
|
102
|
+
// Send approval response
|
|
103
|
+
approveStr := "false"
|
|
104
|
+
if approved {
|
|
105
|
+
approveStr = "true"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if err := ih.manager.SendMessage(ctx, feedback, nil, nil, approveStr); err != nil {
|
|
109
|
+
output.Printf("\nError sending approval: %v\n", err)
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if global.Config.Verbose {
|
|
114
|
+
output.Printf("\nDebug: Approval sent (approved=%s, feedback=%q)\n", approveStr, feedback)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Give the system a moment to process before re-polling
|
|
118
|
+
time.Sleep(1 * time.Second)
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check if we can send a regular message
|
|
123
|
+
err = ih.manager.CheckSendEnabled(ctx)
|
|
124
|
+
if err != nil {
|
|
125
|
+
// Handle specific error cases
|
|
126
|
+
if errors.Is(err, ErrNoActiveTask) {
|
|
127
|
+
// No active task - don't show input prompt
|
|
128
|
+
ih.coordinator.SetInputAllowed(false)
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
if errors.Is(err, ErrTaskBusy) {
|
|
132
|
+
// Task is busy - don't show input prompt
|
|
133
|
+
ih.coordinator.SetInputAllowed(false)
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
// Unexpected error
|
|
137
|
+
if global.Config.Verbose {
|
|
138
|
+
output.Printf("\nDebug: CheckSendEnabled error: %v\n", err)
|
|
139
|
+
}
|
|
140
|
+
continue
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// If we reach here, we can send a message
|
|
144
|
+
ih.coordinator.SetInputAllowed(true)
|
|
145
|
+
|
|
146
|
+
// Show prompt and get input
|
|
147
|
+
message, shouldSend, err := ih.promptForInput(ctx)
|
|
148
|
+
|
|
149
|
+
if err != nil {
|
|
150
|
+
// Check if the error is due to interrupt (Ctrl+C) or context cancellation
|
|
151
|
+
if errors.Is(err, context.Canceled) || ctx.Err() != nil {
|
|
152
|
+
// User pressed Ctrl+C - cancel context to exit FollowConversation
|
|
153
|
+
ih.cancelFunc()
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
if global.Config.Verbose {
|
|
157
|
+
output.Printf("\nDebug: Input prompt error: %v\n", err)
|
|
158
|
+
}
|
|
159
|
+
continue
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
ih.coordinator.SetInputAllowed(false)
|
|
163
|
+
|
|
164
|
+
if shouldSend {
|
|
165
|
+
// Check for mode switch commands first
|
|
166
|
+
newMode, remainingMessage, isModeSwitch := ih.parseModeSwitch(message)
|
|
167
|
+
if isModeSwitch {
|
|
168
|
+
// Create styles for mode switch messages (respect global color profile)
|
|
169
|
+
actStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Bold(true)
|
|
170
|
+
planStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("3")).Bold(true)
|
|
171
|
+
|
|
172
|
+
if remainingMessage != "" {
|
|
173
|
+
// Switching with a message - behavior differs by mode
|
|
174
|
+
if newMode == "act" {
|
|
175
|
+
// Act mode: can send mode + message in one call
|
|
176
|
+
if err := ih.manager.SetMode(ctx, newMode, &remainingMessage, nil, nil); err != nil {
|
|
177
|
+
output.Printf("\nError switching to act mode with message: %v\n", err)
|
|
178
|
+
continue
|
|
179
|
+
}
|
|
180
|
+
output.Printf("\n%s\n", actStyle.Render("Switched to act mode"))
|
|
181
|
+
} else {
|
|
182
|
+
// Plan mode: must switch first, then send message separately
|
|
183
|
+
if err := ih.manager.SetMode(ctx, newMode, nil, nil, nil); err != nil {
|
|
184
|
+
output.Printf("\nError switching to plan mode: %v\n", err)
|
|
185
|
+
continue
|
|
186
|
+
}
|
|
187
|
+
output.Printf("\n%s\n", planStyle.Render("Switched to plan mode"))
|
|
188
|
+
|
|
189
|
+
// Now send the message separately
|
|
190
|
+
time.Sleep(500 * time.Millisecond) // Give mode switch time to process
|
|
191
|
+
if err := ih.manager.SendMessage(ctx, remainingMessage, nil, nil, ""); err != nil {
|
|
192
|
+
output.Printf("\nError sending message after mode switch: %v\n", err)
|
|
193
|
+
continue
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
// Just switch mode, no message
|
|
198
|
+
if err := ih.manager.SetMode(ctx, newMode, nil, nil, nil); err != nil {
|
|
199
|
+
output.Printf("\nError switching to %s mode: %v\n", newMode, err)
|
|
200
|
+
continue
|
|
201
|
+
}
|
|
202
|
+
// Color based on mode
|
|
203
|
+
if newMode == "act" {
|
|
204
|
+
output.Printf("\n%s\n", actStyle.Render("Switched to act mode"))
|
|
205
|
+
} else {
|
|
206
|
+
output.Printf("\n%s\n", planStyle.Render("Switched to plan mode"))
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Mode switch handled, continue to next poll
|
|
211
|
+
time.Sleep(1 * time.Second)
|
|
212
|
+
continue
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Handle special commands
|
|
216
|
+
if handled := ih.handleSpecialCommand(ctx, message); handled {
|
|
217
|
+
continue
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Send the message
|
|
221
|
+
if err := ih.manager.SendMessage(ctx, message, nil, nil, ""); err != nil {
|
|
222
|
+
output.Printf("\nError sending message: %v\n", err)
|
|
223
|
+
continue
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if global.Config.Verbose {
|
|
227
|
+
output.Printf("\nDebug: Message sent successfully\n")
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Give the system a moment to process before re-polling
|
|
231
|
+
time.Sleep(1 * time.Second)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// determineAutoApprovalAction determines which auto-approval action to enable based on the ask type
|
|
238
|
+
func determineAutoApprovalAction(msg *types.ClineMessage) (string, error) {
|
|
239
|
+
switch types.AskType(msg.Ask) {
|
|
240
|
+
case types.AskTypeTool:
|
|
241
|
+
// Parse tool message to determine if it's a read or edit operation
|
|
242
|
+
var toolMsg types.ToolMessage
|
|
243
|
+
if err := json.Unmarshal([]byte(msg.Text), &toolMsg); err != nil {
|
|
244
|
+
return "", fmt.Errorf("failed to parse tool message: %w", err)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Determine action based on tool type
|
|
248
|
+
switch types.ToolType(toolMsg.Tool) {
|
|
249
|
+
case types.ToolTypeReadFile,
|
|
250
|
+
types.ToolTypeListFilesTopLevel,
|
|
251
|
+
types.ToolTypeListFilesRecursive,
|
|
252
|
+
types.ToolTypeListCodeDefinitionNames,
|
|
253
|
+
types.ToolTypeSearchFiles,
|
|
254
|
+
types.ToolTypeWebFetch:
|
|
255
|
+
return "read_files", nil
|
|
256
|
+
case types.ToolTypeEditedExistingFile,
|
|
257
|
+
types.ToolTypeNewFileCreated:
|
|
258
|
+
return "edit_files", nil
|
|
259
|
+
case types.ToolTypeFileDeleted:
|
|
260
|
+
return "apply_patch", nil
|
|
261
|
+
default:
|
|
262
|
+
return "", fmt.Errorf("unsupported tool type: %s", toolMsg.Tool)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
case types.AskTypeCommand:
|
|
266
|
+
return "execute_all_commands", nil
|
|
267
|
+
|
|
268
|
+
case types.AskTypeBrowserActionLaunch:
|
|
269
|
+
return "use_browser", nil
|
|
270
|
+
|
|
271
|
+
case types.AskTypeUseMcpServer:
|
|
272
|
+
return "use_mcp", nil
|
|
273
|
+
|
|
274
|
+
default:
|
|
275
|
+
return "", fmt.Errorf("unsupported ask type: %s", msg.Ask)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// promptForInput displays an interactive prompt and waits for user input
|
|
280
|
+
func (ih *InputHandler) promptForInput(ctx context.Context) (string, bool, error) {
|
|
281
|
+
currentMode := ih.manager.GetCurrentMode()
|
|
282
|
+
|
|
283
|
+
model := output.NewInputModel(
|
|
284
|
+
output.InputTypeMessage,
|
|
285
|
+
"Cline is ready for your message...",
|
|
286
|
+
"/plan or /act to switch modes\nctrl+e to open editor",
|
|
287
|
+
currentMode,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
return ih.runInputProgram(ctx, model)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// promptForApproval displays an approval prompt for tool/command requests
|
|
294
|
+
func (ih *InputHandler) promptForApproval(ctx context.Context, msg *types.ClineMessage) (bool, string, error) {
|
|
295
|
+
// Store the approval message for later use in determining auto-approval action
|
|
296
|
+
ih.approvalMessage = msg
|
|
297
|
+
|
|
298
|
+
model := output.NewInputModel(
|
|
299
|
+
output.InputTypeApproval,
|
|
300
|
+
"Let Cline use this tool?",
|
|
301
|
+
"",
|
|
302
|
+
ih.manager.GetCurrentMode(),
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
message, shouldSend, err := ih.runInputProgram(ctx, model)
|
|
306
|
+
if err != nil {
|
|
307
|
+
return false, "", err
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if !shouldSend {
|
|
311
|
+
return false, "", nil
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// The approval and feedback are handled via the model state
|
|
315
|
+
return ih.feedbackApproved, message, nil
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// runInputProgram runs the bubbletea program and waits for result
|
|
319
|
+
func (ih *InputHandler) runInputProgram(ctx context.Context, model output.InputModel) (string, bool, error) {
|
|
320
|
+
ih.mu.Lock()
|
|
321
|
+
|
|
322
|
+
// Create the program with custom update wrapper
|
|
323
|
+
wrappedModel := &inputProgramWrapper{
|
|
324
|
+
model: &model,
|
|
325
|
+
resultChan: ih.resultChan,
|
|
326
|
+
cancelChan: ih.cancelChan,
|
|
327
|
+
handler: ih,
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
ih.program = tea.NewProgram(wrappedModel)
|
|
331
|
+
ih.programDoneChan = make(chan struct{})
|
|
332
|
+
ih.ctx = ctx
|
|
333
|
+
|
|
334
|
+
// Set up coordinator references
|
|
335
|
+
output.SetProgram(ih.program)
|
|
336
|
+
output.SetInputModel(wrappedModel.model)
|
|
337
|
+
output.SetRestartCallback(ih.restartProgram)
|
|
338
|
+
output.SetInputVisible(true)
|
|
339
|
+
ih.programRunning = true
|
|
340
|
+
ih.mu.Unlock()
|
|
341
|
+
|
|
342
|
+
// Run program in goroutine
|
|
343
|
+
programErrChan := make(chan error, 1)
|
|
344
|
+
go func() {
|
|
345
|
+
if _, err := ih.program.Run(); err != nil {
|
|
346
|
+
programErrChan <- err
|
|
347
|
+
}
|
|
348
|
+
// Signal that program is done
|
|
349
|
+
close(ih.programDoneChan)
|
|
350
|
+
}()
|
|
351
|
+
|
|
352
|
+
// Wait for result, cancellation, or context done
|
|
353
|
+
select {
|
|
354
|
+
case <-ctx.Done():
|
|
355
|
+
ih.mu.Lock()
|
|
356
|
+
output.SetInputVisible(false)
|
|
357
|
+
if ih.program != nil {
|
|
358
|
+
ih.program.Quit()
|
|
359
|
+
}
|
|
360
|
+
ih.programRunning = false
|
|
361
|
+
ih.mu.Unlock()
|
|
362
|
+
return "", false, ctx.Err()
|
|
363
|
+
|
|
364
|
+
case <-ih.cancelChan:
|
|
365
|
+
ih.mu.Lock()
|
|
366
|
+
output.SetInputVisible(false)
|
|
367
|
+
ih.programRunning = false
|
|
368
|
+
ih.mu.Unlock()
|
|
369
|
+
return "", false, context.Canceled
|
|
370
|
+
|
|
371
|
+
case err := <-programErrChan:
|
|
372
|
+
ih.mu.Lock()
|
|
373
|
+
output.SetInputVisible(false)
|
|
374
|
+
ih.programRunning = false
|
|
375
|
+
ih.mu.Unlock()
|
|
376
|
+
return "", false, err
|
|
377
|
+
|
|
378
|
+
case result := <-ih.resultChan:
|
|
379
|
+
ih.mu.Lock()
|
|
380
|
+
output.SetInputVisible(false)
|
|
381
|
+
ih.programRunning = false
|
|
382
|
+
ih.mu.Unlock()
|
|
383
|
+
|
|
384
|
+
// Handle different input types
|
|
385
|
+
switch result.InputType {
|
|
386
|
+
case output.InputTypeMessage:
|
|
387
|
+
if result.Value == "" {
|
|
388
|
+
return "", false, nil
|
|
389
|
+
}
|
|
390
|
+
return result.Value, true, nil
|
|
391
|
+
|
|
392
|
+
case output.InputTypeApproval:
|
|
393
|
+
if result.NeedsFeedback {
|
|
394
|
+
// Need to collect feedback - will be handled by model state change
|
|
395
|
+
return "", false, nil
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Check if NoAskAgain was selected
|
|
399
|
+
if result.NoAskAgain && result.Approved && ih.approvalMessage != nil {
|
|
400
|
+
// Determine which auto-approval action to enable
|
|
401
|
+
action, err := determineAutoApprovalAction(ih.approvalMessage)
|
|
402
|
+
if err != nil {
|
|
403
|
+
output.Printf("\nWarning: Could not determine auto-approval action: %v\n", err)
|
|
404
|
+
} else {
|
|
405
|
+
// Enable the auto-approval action
|
|
406
|
+
if err := ih.manager.UpdateTaskAutoApprovalAction(ctx, action); err != nil {
|
|
407
|
+
output.Printf("\nWarning: Could not update auto-approval: %v\n", err)
|
|
408
|
+
} else {
|
|
409
|
+
output.Printf("\nAuto-approval enabled for %s\n", action)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Store approval state for when feedback comes back
|
|
415
|
+
ih.feedbackApproval = false
|
|
416
|
+
ih.feedbackApproved = result.Approved
|
|
417
|
+
return "", true, nil
|
|
418
|
+
|
|
419
|
+
case output.InputTypeFeedback:
|
|
420
|
+
// This came from approval flow
|
|
421
|
+
ih.feedbackApproval = true
|
|
422
|
+
ih.feedbackApproved = result.Approved // Use the approval decision from the feedback
|
|
423
|
+
return result.Value, true, nil
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return "", false, nil
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// inputProgramWrapper wraps the InputModel to handle message routing
|
|
431
|
+
type inputProgramWrapper struct {
|
|
432
|
+
model *output.InputModel
|
|
433
|
+
resultChan chan output.InputSubmitMsg
|
|
434
|
+
cancelChan chan struct{}
|
|
435
|
+
handler *InputHandler
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
func (w *inputProgramWrapper) Init() tea.Cmd {
|
|
439
|
+
return w.model.Init()
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
func (w *inputProgramWrapper) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
443
|
+
switch msg := msg.(type) {
|
|
444
|
+
case output.InputSubmitMsg:
|
|
445
|
+
// Handle input submission - clear the screen before quitting
|
|
446
|
+
w.resultChan <- msg
|
|
447
|
+
clearCodes := w.model.ClearScreen()
|
|
448
|
+
if clearCodes != "" {
|
|
449
|
+
fmt.Print(clearCodes)
|
|
450
|
+
}
|
|
451
|
+
return w, tea.Quit
|
|
452
|
+
|
|
453
|
+
case output.InputCancelMsg:
|
|
454
|
+
// Handle cancellation - clear the screen before quitting
|
|
455
|
+
w.cancelChan <- struct{}{}
|
|
456
|
+
clearCodes := w.model.ClearScreen()
|
|
457
|
+
if clearCodes != "" {
|
|
458
|
+
fmt.Print(clearCodes)
|
|
459
|
+
}
|
|
460
|
+
return w, tea.Quit
|
|
461
|
+
|
|
462
|
+
case output.ChangeInputTypeMsg:
|
|
463
|
+
// Change input type (approval -> feedback)
|
|
464
|
+
_, cmd := w.model.Update(msg)
|
|
465
|
+
return w, cmd
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Forward to wrapped model
|
|
469
|
+
_, cmd := w.model.Update(msg)
|
|
470
|
+
return w, cmd
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
func (w *inputProgramWrapper) View() string {
|
|
474
|
+
return w.model.View()
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// parseModeSwitch checks if message starts with /act or /plan and extracts the mode and remaining message
|
|
478
|
+
func (ih *InputHandler) parseModeSwitch(message string) (string, string, bool) {
|
|
479
|
+
trimmed := strings.TrimSpace(message)
|
|
480
|
+
lower := strings.ToLower(trimmed)
|
|
481
|
+
|
|
482
|
+
if strings.HasPrefix(lower, "/plan") {
|
|
483
|
+
remaining := strings.TrimSpace(trimmed[5:])
|
|
484
|
+
return "plan", remaining, true
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if strings.HasPrefix(lower, "/act") {
|
|
488
|
+
remaining := strings.TrimSpace(trimmed[4:])
|
|
489
|
+
return "act", remaining, true
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return "", message, false
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// handleSpecialCommand processes special commands like /cancel, /exit
|
|
496
|
+
func (ih *InputHandler) handleSpecialCommand(ctx context.Context, message string) bool {
|
|
497
|
+
switch strings.ToLower(strings.TrimSpace(message)) {
|
|
498
|
+
case "/cancel":
|
|
499
|
+
ih.manager.GetRenderer().RenderTaskCancelled()
|
|
500
|
+
if err := ih.manager.CancelTask(ctx); err != nil {
|
|
501
|
+
output.Printf("Error cancelling task: %v\n", err)
|
|
502
|
+
} else {
|
|
503
|
+
output.Println("Task cancelled successfully")
|
|
504
|
+
}
|
|
505
|
+
return true
|
|
506
|
+
case "/exit", "/quit":
|
|
507
|
+
output.Println("\nExiting follow mode...")
|
|
508
|
+
return true
|
|
509
|
+
default:
|
|
510
|
+
return false
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Stop stops the input handler
|
|
515
|
+
func (ih *InputHandler) Stop() {
|
|
516
|
+
ih.mu.Lock()
|
|
517
|
+
defer ih.mu.Unlock()
|
|
518
|
+
if ih.pollTicker != nil {
|
|
519
|
+
ih.pollTicker.Stop()
|
|
520
|
+
}
|
|
521
|
+
if ih.program != nil && ih.programRunning {
|
|
522
|
+
ih.program.Quit()
|
|
523
|
+
}
|
|
524
|
+
ih.isRunning = false
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// IsRunning returns whether the input handler is currently running
|
|
528
|
+
func (ih *InputHandler) IsRunning() bool {
|
|
529
|
+
ih.mu.RLock()
|
|
530
|
+
defer ih.mu.RUnlock()
|
|
531
|
+
return ih.isRunning
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// restartProgram restarts the Bubble Tea program with preserved state
|
|
535
|
+
func (ih *InputHandler) restartProgram(savedModel *output.InputModel) {
|
|
536
|
+
ih.mu.Lock()
|
|
537
|
+
|
|
538
|
+
// Wait for old program to actually quit
|
|
539
|
+
if ih.programDoneChan != nil {
|
|
540
|
+
select {
|
|
541
|
+
case <-ih.programDoneChan:
|
|
542
|
+
// Program quit successfully
|
|
543
|
+
case <-time.After(100 * time.Millisecond):
|
|
544
|
+
// Timeout - continue anyway
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Create new wrapper with the saved model
|
|
549
|
+
wrappedModel := &inputProgramWrapper{
|
|
550
|
+
model: savedModel,
|
|
551
|
+
resultChan: ih.resultChan,
|
|
552
|
+
cancelChan: ih.cancelChan,
|
|
553
|
+
handler: ih,
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Start new program
|
|
557
|
+
ih.program = tea.NewProgram(wrappedModel)
|
|
558
|
+
ih.programDoneChan = make(chan struct{})
|
|
559
|
+
|
|
560
|
+
// Update coordinator references
|
|
561
|
+
output.SetProgram(ih.program)
|
|
562
|
+
output.SetInputModel(savedModel)
|
|
563
|
+
output.SetInputVisible(true)
|
|
564
|
+
ih.programRunning = true
|
|
565
|
+
ih.mu.Unlock()
|
|
566
|
+
|
|
567
|
+
// Run in goroutine
|
|
568
|
+
go func() {
|
|
569
|
+
if _, err := ih.program.Run(); err != nil {
|
|
570
|
+
// Log error if needed
|
|
571
|
+
if global.Config.Verbose {
|
|
572
|
+
output.Printf("\nDebug: Program restart error: %v\n", err)
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
close(ih.programDoneChan)
|
|
576
|
+
}()
|
|
577
|
+
}
|