Skip to content

Commit e1f4002

Browse files
authored
Merge pull request #1 from GrayCodeAI/dev
feat: ecosystem strengthening - caching, MCP servers, cascade routing, integration tests
2 parents e3f7934 + 128c8c7 commit e1f4002

12 files changed

Lines changed: 1133 additions & 80 deletions

File tree

‎cmd/chat.go‎

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/charmbracelet/lipgloss"
2323

2424
hawkconfig "github.com/GrayCodeAI/hawk/config"
25+
"github.com/GrayCodeAI/hawk/convodag"
2526
"github.com/GrayCodeAI/hawk/engine"
2627
"github.com/GrayCodeAI/hawk/logger"
2728
"github.com/GrayCodeAI/hawk/memory"
@@ -209,6 +210,14 @@ func newChatModel(ref *progRef, systemPrompt string, settings hawkconfig.Setting
209210
return chatModel{}, err
210211
}
211212

213+
// Initialize conversation DAG for branching support
214+
if home, err := os.UserHomeDir(); err == nil {
215+
dagPath := filepath.Join(home, ".hawk", "sessions", "convo.db")
216+
if dag, err := convodag.New(dagPath, sid); err == nil {
217+
sess.ConvoDAG = dag
218+
}
219+
}
220+
212221
initWidth := 80
213222
initHeight := 24
214223
if w, h, err := term.GetSize(int(os.Stdout.Fd())); err == nil && w > 0 {
@@ -223,7 +232,7 @@ func newChatModel(ref *progRef, systemPrompt string, settings hawkconfig.Setting
223232
vpHeight = 4
224233
}
225234
vp := viewport.New(initWidth, vpHeight)
226-
vp.MouseWheelEnabled = false
235+
vp.MouseWheelEnabled = true
227236

228237
m := chatModel{input: ta, configInput: ci, spinner: sp, viewport: vp, session: sess, registry: registry, settings: settings, ref: ref, sessionID: sid, partial: &strings.Builder{}, spinnerVerb: spinnerVerbs[rand.Intn(len(spinnerVerbs))], width: initWidth, height: initHeight, historyIdx: 0, autoScroll: true, startedAt: time.Now(), activeSkills: make(map[string]plugin.SmartSkill)}
229238

@@ -256,6 +265,16 @@ func newChatModel(ref *progRef, systemPrompt string, settings hawkconfig.Setting
256265
}
257266
}()
258267

268+
// Prefetch models for current provider in background so /config and /model are instant
269+
go func() {
270+
provider := effectiveProvider
271+
models, _ := hawkconfig.FetchModelsForProvider(provider)
272+
ids := extractModelIDs(models)
273+
if len(ids) > 0 {
274+
modelCache[provider] = ids
275+
}
276+
}()
277+
259278
// Initialize plugin runtime
260279
pr := plugin.NewRuntime()
261280
_ = pr.LoadAll()
@@ -319,6 +338,8 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
319338
m.messages = append(m.messages, displayMsg{role: "system", content: "✓ Always allowed: " + m.permReq.ToolName})
320339
m.permReq = nil
321340
}
341+
m.viewDirty = true
342+
m.updateViewportContent()
322343
return m, nil
323344
}
324345
// AskUser prompt active — Enter submits answer
@@ -329,6 +350,8 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
329350
m.messages = append(m.messages, displayMsg{role: "user", content: answer})
330351
m.askReq.response <- answer
331352
m.askReq = nil
353+
m.viewDirty = true
354+
m.updateViewportContent()
332355
return m, nil
333356
}
334357
// Let textarea handle other keys
@@ -349,6 +372,8 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
349372
}
350373
m.waiting = false
351374
m.input.Focus()
375+
m.viewDirty = true
376+
m.updateViewportContent()
352377
return m, nil
353378
}
354379
m.saveSession()
@@ -367,6 +392,8 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
367392
m.waiting = false
368393
m.input.Focus()
369394
}
395+
m.viewDirty = true
396+
m.updateViewportContent()
370397
return m, nil
371398
}
372399
// Allow typing in input while streaming
@@ -384,9 +411,13 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
384411
}
385412
m.lastCtrlC = time.Now()
386413
m.messages = append(m.messages, displayMsg{role: "system", content: "Press Ctrl+C again to quit."})
414+
m.viewDirty = true
415+
m.updateViewportContent()
387416
return m, nil
388417
default:
389418
next, cmd := m.handleConfigKey(msg)
419+
next.viewDirty = true
420+
next.updateViewportContent()
390421
return next, cmd
391422
}
392423
}
@@ -405,6 +436,8 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
405436
m.session.SetModel(models[idx])
406437
m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Model → %s", models[idx])})
407438
}
439+
m.viewDirty = true
440+
m.updateViewportContent()
408441
return m, nil
409442
case tea.KeyCtrlL:
410443
modes := []string{"default", "acceptEdits", "bypassPermissions"}
@@ -419,6 +452,8 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
419452
m.session.SetPermissionMode(modes[idx])
420453
labels := map[string]string{"default": "Off", "acceptEdits": "Auto-edit", "bypassPermissions": "Full Auto"}
421454
m.messages = append(m.messages, displayMsg{role: "system", content: fmt.Sprintf("Autonomy → %s", labels[modes[idx]])})
455+
m.viewDirty = true
456+
m.updateViewportContent()
422457
return m, nil
423458
case tea.KeyCtrlC:
424459
if time.Since(m.lastCtrlC) < 1*time.Second {
@@ -428,6 +463,8 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
428463
}
429464
m.lastCtrlC = time.Now()
430465
m.messages = append(m.messages, displayMsg{role: "system", content: "Press Ctrl+C again to quit."})
466+
m.viewDirty = true
467+
m.updateViewportContent()
431468
return m, nil
432469
case tea.KeyTab:
433470
sugs := slashSuggestions(m.input.Value())
@@ -499,7 +536,10 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
499536
m.historyDraft = ""
500537
m.input.Reset()
501538
if strings.HasPrefix(text, "/") {
502-
return m.handleCommand(text)
539+
result, cmd := m.handleCommand(text)
540+
m.viewDirty = true
541+
m.updateViewportContent()
542+
return result, cmd
503543
}
504544
// Shell escape: !command runs directly without AI
505545
if strings.HasPrefix(text, "!") {
@@ -512,6 +552,7 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
512552
}
513553
m.waiting = true
514554
m.autoScroll = true
555+
m.viewDirty = true
515556
m.spinnerVerb = spinnerVerbs[rand.Intn(len(spinnerVerbs))]
516557
m.partial.Reset()
517558
m.startStream()
@@ -521,12 +562,24 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
521562
case modelsFetchedMsg:
522563
if len(msg) > 0 {
523564
m.configModels = []string(msg)
565+
// Auto-set first model so provider switch is immediately usable
566+
if m.configOpen && len(m.configModels) > 0 {
567+
m.session.SetModel(m.configModels[0])
568+
_ = hawkconfig.SetGlobalSetting("model", m.configModels[0])
569+
}
570+
}
571+
if m.configOpen {
572+
m.viewDirty = true
573+
m.updateViewportContent()
524574
}
525575
return m, nil
526576

527577
case loopTickMsg:
528578
if !m.waiting {
529-
return m.handleCommand(msg.command)
579+
result, cmd := m.handleCommand(msg.command)
580+
m.viewDirty = true
581+
m.updateViewportContent()
582+
return result, cmd
530583
}
531584
return m, nil
532585

@@ -552,8 +605,9 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
552605

553606
case toolResultMsg:
554607
content := msg.content
555-
if len(content) > 500 {
556-
content = content[:500] + "..."
608+
totalLen := len(content)
609+
if totalLen > 800 {
610+
content = content[:800] + fmt.Sprintf(" … (%d more chars)", totalLen-800)
557611
}
558612
m.messages = append(m.messages, displayMsg{role: "tool_result", content: fmt.Sprintf("[%s] %s", msg.name, content)})
559613
m.viewDirty = true
@@ -619,24 +673,23 @@ func (m chatModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
619673
m.width = msg.Width
620674
m.height = msg.Height
621675
m.input.SetWidth(msg.Width - 4)
622-
// Resize viewport: total height minus bottom bar
623-
vpHeight := msg.Height - 6
624-
if vpHeight < 4 {
625-
vpHeight = 4
626-
}
627-
m.viewport.Width = msg.Width
628-
m.viewport.Height = vpHeight
629676
m.welcomeCache = buildWelcomeMessage(m.session, m.sessionID, m.registry, nil, m.settings, false, msg.Width)
630677
m.viewDirty = true
631678

632679
case spinner.TickMsg:
633680
var cmd tea.Cmd
634681
m.spinner, cmd = m.spinner.Update(msg)
635682
cmds = append(cmds, cmd)
683+
if m.waiting {
684+
m.viewDirty = true
685+
}
636686

637687
case glimmerTickMsg:
638688
m.glimmerPos++
639689
cmds = append(cmds, glimmerTickCmd())
690+
if m.waiting {
691+
m.viewDirty = true
692+
}
640693
}
641694

642695
if !m.waiting {

0 commit comments

Comments
 (0)