Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ab0f6f0
feat: polish config and chat UI
Patel230 Jul 19, 2026
b6b00f8
refactor(gateway): consolidate eyrie access behind Provider interface
Patel230 Jul 20, 2026
8bd69fc
perf(gateway): cache shared default gateway with sync.Once
Patel230 Jul 20, 2026
5d821c4
refactor(gateway): split Provider into role interfaces
Patel230 Jul 20, 2026
605b618
test: migrate eyrie imports to gateway boundary
Patel230 Jul 20, 2026
627dbeb
chore: repin eyrie submodule to neutral-defaults commit
Patel230 Jul 20, 2026
06f6381
refactor(gateway): narrow translateProvider + make identity init expl…
Patel230 Jul 20, 2026
8b9b4a5
chore: repin eyrie to include legacy categories.json migration
Patel230 Jul 20, 2026
03f00c4
chore: repin eyrie to merged main (b2cab57, PR #77)
Patel230 Jul 20, 2026
81b3417
fix(engine): route self-heal shell through the Bash safety stack
Patel230 Jul 20, 2026
b4fcb90
fix(tool): background bash process group + flatten nested goroutine
Patel230 Jul 20, 2026
e63a650
refactor(gateway): declare identity first, dedup provider converter
Patel230 Jul 20, 2026
59399ce
refactor(session): remove deprecated stored fields, read through sub-…
Patel230 Jul 20, 2026
16242e0
refactor(settings): remove dead fields + adapt tests
Patel230 Jul 20, 2026
026fcc7
refactor(daemon): split handleChat god-function + observe client disc…
Patel230 Jul 20, 2026
b1b6e46
fix(low): hardening nits across icons, snapshot, multiagent
Patel230 Jul 20, 2026
e494dfc
feat(types): alias internal/types DTOs to hawk-core-contracts/llm
Patel230 Jul 20, 2026
c99ec13
feat(gateway): collapse translateProvider conversions to identity
Patel230 Jul 20, 2026
43e37f2
refactor: improve error handling, thread safety, and tool validation
Patel230 Jul 20, 2026
d905d44
chore: update submodule pointers to latest feat/llm-port-contract com…
Patel230 Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 0 additions & 140 deletions bin/buf

This file was deleted.

17 changes: 15 additions & 2 deletions cmd/chat_config_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ func saveCredentialAsync(inference hawkconfig.CredentialInference, secret string
hawkconfig.InvalidateConfigUICache()
hawkconfig.RefreshConfigCredSnapshot(ctx)
result, err := hawkconfig.ApplyEyrieCredentialsForProvider(ctx, inference.ProviderID)
if err != nil && hawkconfig.IsCatalogCacheRequired(err) {
if refreshErr := hawkconfig.RefreshCatalogAfterCredentials(ctx, nil); refreshErr == nil {
result, err = hawkconfig.ApplyEyrieCredentialsForProvider(ctx, inference.ProviderID)
} else {
err = fmt.Errorf("%w; automatic catalog refresh failed: %v", err, refreshErr)
}
}
if err != nil {
return configApplyCredentialsMsg{
err: err,
Expand Down Expand Up @@ -146,6 +153,7 @@ func (m chatModel) handleConfigApplyCredentialsMsg(msg configApplyCredentialsMsg
m.configSaving = false
ctx := context.Background()
hawkconfig.RefreshConfigCredSnapshot(ctx)
m = m.refreshConfigGatewayRows()
if msg.err != nil {
m.invalidateConnStatus()
if msg.providerID == configProviderOllama {
Expand All @@ -155,8 +163,13 @@ func (m chatModel) handleConfigApplyCredentialsMsg(msg configApplyCredentialsMsg
saved := hawkconfig.HasStoredCredentialForProvider(ctx, msg.providerID) ||
strings.Contains(strings.ToLower(msg.err.Error()), "key saved in keychain")
if saved {
notice = "Key saved in " + credentialsStoreLabel() + " — provider rejected this key: " + notice
if !strings.Contains(strings.ToLower(notice), "refresh") {
if hawkconfig.IsCatalogCacheRequired(msg.err) {
notice = "Key saved in " + credentialsStoreLabel() + " — model catalog unavailable: " + notice
notice += " · run hawk models refresh"
} else {
notice = "Key saved in " + credentialsStoreLabel() + " — provider rejected this key: " + notice
}
if !hawkconfig.IsCatalogCacheRequired(msg.err) && !strings.Contains(strings.ToLower(notice), "refresh") {
notice += " · press r on " + hawkconfig.GatewayDisplayName(msg.providerID) + " to retry"
}
} else {
Expand Down
100 changes: 85 additions & 15 deletions cmd/chat_config_gateways.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ type configGatewayRow struct {
ID string
DisplayName string
HasKey bool
Configured bool
ModelCount int
Active bool
RegionLabel string
RegionRequired bool
CredentialEnv string
KeyConflict bool
}

type configGatewayRefreshMsg struct {
Expand All @@ -28,6 +31,13 @@ type configGatewayRefreshMsg struct {
}

func (m chatModel) configGatewayRows() []configGatewayRow {
if !m.configGatewayRowsDirty && m.configGatewayRowsCache != nil {
return m.configGatewayRowsCache
}
return m.loadConfigGatewayRows()
}

func (m chatModel) loadConfigGatewayRows() []configGatewayRow {
ctx := context.Background()
active := strings.TrimSpace(m.configModelProvider)
activeModel := ""
Expand All @@ -52,6 +62,10 @@ func (m chatModel) configGatewayRows() []configGatewayRow {
modelCacheMu.RUnlock()
}
hasKey := status.HasStoredCredential
credentialEnv, keyConflict := "", false
if hasKey {
credentialEnv, keyConflict = hawkconfig.CredentialEnvironmentConflict(ctx, status.ID)
}
display := status.DisplayName
if status.ID == hawkconfig.ProviderXiaomiTokenPlan {
if reg := status.RegionLabel; reg != "" {
Expand All @@ -71,13 +85,63 @@ func (m chatModel) configGatewayRows() []configGatewayRow {
ID: status.ID,
DisplayName: display,
HasKey: hasKey,
Configured: status.HasConfiguredDeployment || hasKey,
ModelCount: count,
Active: status.Active || hawkconfig.ActiveProviderID(status.ID) == hawkconfig.ActiveProviderID(active),
RegionLabel: status.RegionLabel,
RegionRequired: status.RegionRequired,
CredentialEnv: credentialEnv,
KeyConflict: keyConflict,
})
}
return rows
return prioritizeConfigGatewayRows(rows)
}

func prioritizeConfigGatewayRows(rows []configGatewayRow) []configGatewayRow {
ordered := make([]configGatewayRow, 0, len(rows))
for _, row := range rows {
if row.Active {
ordered = append(ordered, row)
}
}
for _, row := range rows {
if !row.Active && row.Configured {
ordered = append(ordered, row)
}
}
for _, row := range rows {
if !row.Active && !row.Configured {
ordered = append(ordered, row)
}
}
return ordered
}

func (m chatModel) refreshConfigGatewayRows() chatModel {
selectedID := ""
focusID := ""
if m.configSel >= 0 && m.configSel < len(m.configGatewayRowsCache) {
selectedID = m.configGatewayRowsCache[m.configSel].ID
}
if m.configGatewayFocus >= 0 && m.configGatewayFocus < len(m.configGatewayRowsCache) {
focusID = m.configGatewayRowsCache[m.configGatewayFocus].ID
}
m.configGatewayRowsCache = m.loadConfigGatewayRows()
m.configGatewayRowsDirty = false
for i, row := range m.configGatewayRowsCache {
if row.ID == selectedID {
m.configSel = i
}
if row.ID == focusID {
m.configGatewayFocus = i
}
}
return m
}

func (m chatModel) invalidateConfigGatewayRows() chatModel {
m.configGatewayRowsDirty = true
return m
}

func (m chatModel) configGatewayRowIndex(provider string) int {
Expand Down Expand Up @@ -152,13 +216,18 @@ func (m chatModel) configGatewaysView() string {

rows := m.configGatewayRows()
refreshSel := len(rows)
windowSize := m.configVisibleRows()
maxScroll := maxInt(0, len(rows)-windowSize)
if m.configScroll > maxScroll {
m.configScroll = maxScroll
}

if m.configSel < len(rows) {
if m.configSel < m.configScroll {
m.configScroll = m.configSel
}
if m.configSel >= m.configScroll+configWindowSize {
m.configScroll = m.configSel - configWindowSize + 1
if m.configSel >= m.configScroll+windowSize {
m.configScroll = m.configSel - windowSize + 1
}
}

Expand All @@ -169,6 +238,9 @@ func (m chatModel) configGatewaysView() string {
key := "—"
if row.HasKey {
key = "+" + icons.CheckBold() + " "
if row.KeyConflict {
key = "+" + icons.CheckBold() + " !"
}
}
models := "key required"
if row.HasKey && row.ModelCount > 0 {
Expand All @@ -190,7 +262,7 @@ func (m chatModel) configGatewaysView() string {
if m.configScroll > 0 {
b.WriteString(configTableScrollHint(m.configScroll, 0, mutedStyle) + "\n")
}
end := m.configScroll + configWindowSize
end := m.configScroll + windowSize
if end > len(rows) {
end = len(rows)
}
Expand All @@ -215,6 +287,10 @@ func (m chatModel) configGatewaysView() string {
b.WriteString("\n")
targetIdx := m.configGatewayRefreshTargetIndex(rows)
b.WriteString(renderConfigRefreshActionRow(rows[targetIdx].DisplayName, m.configSel == refreshSel) + "\n")
if targetIdx >= 0 && targetIdx < len(rows) && rows[targetIdx].KeyConflict {
warning := fmt.Sprintf("warning: %s differs from the stored keychain credential", rows[targetIdx].CredentialEnv)
b.WriteString("\n" + configWarningStyle().Render(strings.Repeat(" ", configTableIndent)+warning))
}

ctx := context.Background()
indent := strings.Repeat(" ", configTableIndent)
Expand Down Expand Up @@ -322,6 +398,7 @@ func refreshGatewayAsync(providerID string) tea.Cmd {
func (m chatModel) handleConfigGatewayRefreshMsg(msg configGatewayRefreshMsg) chatModel {
m.configSaving = false
InvalidateModelCacheProvider(msg.providerID)
m = m.refreshConfigGatewayRows()
if msg.err != nil {
m.configNotice = sanitizeConfigNotice(hawkconfig.FormatConfigProviderError(msg.providerID, msg.err))
return m
Expand All @@ -335,14 +412,15 @@ func (m chatModel) handleConfigGatewayRefreshMsg(msg configGatewayRefreshMsg) ch

func (m chatModel) focusConfigActiveGateway() chatModel {
rows := m.configGatewayRows()
windowSize := m.configVisibleRows()
if i := m.activeGatewayRowIndex(rows); i >= 0 {
m.configGatewayFocus = i
m.configSel = i
if m.configSel < m.configScroll {
m.configScroll = m.configSel
}
if m.configSel >= m.configScroll+configWindowSize {
m.configScroll = m.configSel - configWindowSize + 1
if m.configSel >= m.configScroll+windowSize {
m.configScroll = m.configSel - windowSize + 1
}
}
return m
Expand All @@ -352,15 +430,7 @@ func (m chatModel) trackConfigGatewayFocus() chatModel {
if m.configTab != configTabGateways {
return m
}
active := strings.TrimSpace(m.configModelProvider)
activeModel := ""
if m.session != nil {
if active == "" {
active = strings.TrimSpace(m.session.Provider())
}
activeModel = strings.TrimSpace(m.session.Model())
}
rows := len(hawkconfig.GatewayStatuses(context.Background(), active, activeModel))
rows := len(m.configGatewayRows())
if m.configSel >= 0 && m.configSel < rows {
m.configGatewayFocus = m.configSel
}
Expand Down
Loading
Loading