Detailed changes
@@ -6,7 +6,7 @@
- **Test**: `task test` or `go test ./...` (run single test: `go test ./internal/llm/prompt -run TestGetContextFromPaths`)
- **Update Golden Files**: `go test ./... -update` (regenerates .golden files when test output changes)
- Update specific package: `go test ./internal/tui/components/core -update` (in this case, we're updating "core")
-- **Lint**: `task lint-fix`
+- **Lint**: `task lint:fix`
- **Format**: `task fmt` (gofumpt -w .)
- **Dev**: `task dev` (runs with profiling enabled)
@@ -62,36 +62,41 @@ type Model struct {
}
type sessionAgent struct {
- largeModel Model
- smallModel Model
- systemPrompt string
- tools []ai.AgentTool
- sessions session.Service
- messages message.Service
+ largeModel Model
+ smallModel Model
+ systemPrompt string
+ tools []ai.AgentTool
+ sessions session.Service
+ messages message.Service
+ disableAutoSummarize bool
messageQueue *csync.Map[string, []SessionAgentCall]
activeRequests *csync.Map[string, context.CancelFunc]
}
-type SessionAgentOption func(*sessionAgent)
+type SessionAgentOptions struct {
+ LargeModel Model
+ SmallModel Model
+ SystemPrompt string
+ DisableAutoSummarize bool
+ Sessions session.Service
+ Messages message.Service
+ Tools []ai.AgentTool
+}
func NewSessionAgent(
- largeModel Model,
- smallModel Model,
- systemPrompt string,
- sessions session.Service,
- messages message.Service,
- tools ...ai.AgentTool,
+ opts SessionAgentOptions,
) SessionAgent {
return &sessionAgent{
- largeModel: largeModel,
- smallModel: smallModel,
- systemPrompt: systemPrompt,
- sessions: sessions,
- messages: messages,
- tools: tools,
- messageQueue: csync.NewMap[string, []SessionAgentCall](),
- activeRequests: csync.NewMap[string, context.CancelFunc](),
+ largeModel: opts.LargeModel,
+ smallModel: opts.SmallModel,
+ systemPrompt: opts.SystemPrompt,
+ sessions: opts.Sessions,
+ messages: opts.Messages,
+ disableAutoSummarize: opts.DisableAutoSummarize,
+ tools: opts.Tools,
+ messageQueue: csync.NewMap[string, []SessionAgentCall](),
+ activeRequests: csync.NewMap[string, context.CancelFunc](),
}
}
@@ -164,6 +169,7 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*ai.Agen
history, files := a.preparePrompt(msgs, call.Attachments...)
var currentAssistant *message.Message
+ var shouldSummarize bool
result, err := agent.Stream(genCtx, ai.AgentStreamCall{
Prompt: call.Prompt,
Files: files,
@@ -292,16 +298,16 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*ai.Agen
IsError: isError,
Metadata: result.ClientMetadata,
}
- _, err := a.messages.Create(context.Background(), currentAssistant.SessionID, message.CreateMessageParams{
+ _, createMsgErr := a.messages.Create(genCtx, currentAssistant.SessionID, message.CreateMessageParams{
Role: message.Tool,
Parts: []message.ContentPart{
toolResult,
},
})
- if err != nil {
- return err
+ if createMsgErr != nil {
+ return createMsgErr
}
- return a.messages.Update(genCtx, *currentAssistant)
+ return nil
},
OnStepFinish: func(stepResult ai.StepResult) error {
finishReason := message.FinishReasonUnknown
@@ -323,6 +329,18 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*ai.Agen
}
return a.messages.Update(genCtx, *currentAssistant)
},
+ StopWhen: []ai.StopCondition{
+ func(_ []ai.StepResult) bool {
+ contextWindow := a.largeModel.CatwalkCfg.ContextWindow
+ tokens := currentSession.CompletionTokens + currentSession.PromptTokens
+ percentage := (float64(tokens) / float64(contextWindow)) * 100
+ if (percentage > 80) && !a.disableAutoSummarize {
+ shouldSummarize = true
+ return true
+ }
+ return false
+ },
+ },
})
if err != nil {
isCancelErr := errors.Is(err, context.Canceled)
@@ -358,28 +376,29 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*ai.Agen
break
}
}
- if !found {
- content := "There was an error while executing the tool"
- if isCancelErr {
- content = "Tool execution canceled by user"
- } else if isPermissionErr {
- content = "Permission denied"
- }
- toolResult := message.ToolResult{
- ToolCallID: tc.ID,
- Name: tc.Name,
- Content: content,
- IsError: true,
- }
- _, createErr = a.messages.Create(context.Background(), currentAssistant.SessionID, message.CreateMessageParams{
- Role: message.Tool,
- Parts: []message.ContentPart{
- toolResult,
- },
- })
- if createErr != nil {
- return nil, createErr
- }
+ if found {
+ continue
+ }
+ content := "There was an error while executing the tool"
+ if isCancelErr {
+ content = "Tool execution canceled by user"
+ } else if isPermissionErr {
+ content = "Permission denied"
+ }
+ toolResult := message.ToolResult{
+ ToolCallID: tc.ID,
+ Name: tc.Name,
+ Content: content,
+ IsError: true,
+ }
+ _, createErr = a.messages.Create(context.Background(), currentAssistant.SessionID, message.CreateMessageParams{
+ Role: message.Tool,
+ Parts: []message.ContentPart{
+ toolResult,
+ },
+ })
+ if createErr != nil {
+ return nil, createErr
}
}
if isCancelErr {
@@ -398,6 +417,13 @@ func (a *sessionAgent) Run(ctx context.Context, call SessionAgentCall) (*ai.Agen
}
wg.Wait()
+ if shouldSummarize {
+ a.activeRequests.Del(call.SessionID)
+ if summarizeErr := a.Summarize(genCtx, call.SessionID); summarizeErr != nil {
+ return nil, summarizeErr
+ }
+ }
+
queuedMessages, ok := a.messageQueue.Get(call.SessionID)
if !ok || len(queuedMessages) == 0 {
return result, err
@@ -437,20 +463,21 @@ func (a *sessionAgent) Summarize(ctx context.Context, sessionID string) error {
ai.WithSystemPrompt(string(summaryPrompt)),
)
summaryMessage, err := a.messages.Create(ctx, sessionID, message.CreateMessageParams{
- Role: message.Assistant,
- Model: a.largeModel.Model.Model(),
- Provider: a.largeModel.Model.Provider(),
+ Role: message.Assistant,
+ Model: a.largeModel.Model.Model(),
+ Provider: a.largeModel.Model.Provider(),
+ IsSummaryMessage: true,
})
if err != nil {
return err
}
- resp, err := agent.Stream(ctx, ai.AgentStreamCall{
+ resp, err := agent.Stream(genCtx, ai.AgentStreamCall{
Prompt: "Provide a detailed summary of our conversation above.",
Messages: aiMsgs,
OnReasoningDelta: func(id string, text string) error {
summaryMessage.AppendReasoningContent(text)
- return a.messages.Update(ctx, summaryMessage)
+ return a.messages.Update(genCtx, summaryMessage)
},
OnReasoningEnd: func(id string, reasoning ai.ReasoningContent) error {
// handle anthropic signature
@@ -460,14 +487,20 @@ func (a *sessionAgent) Summarize(ctx context.Context, sessionID string) error {
}
}
summaryMessage.FinishThinking()
- return a.messages.Update(ctx, summaryMessage)
+ return a.messages.Update(genCtx, summaryMessage)
},
OnTextDelta: func(id, text string) error {
summaryMessage.AppendContent(text)
- return a.messages.Update(ctx, summaryMessage)
+ return a.messages.Update(genCtx, summaryMessage)
},
})
if err != nil {
+ isCancelErr := errors.Is(err, context.Canceled)
+ if isCancelErr {
+ // User cancelled summarize we need to remove the summary message
+ deleteErr := a.messages.Delete(ctx, summaryMessage.ID)
+ return deleteErr
+ }
return err
}
@@ -82,7 +82,7 @@ func (c *coordinator) agentTool() (ai.AgentTool, error) {
PresencePenalty: model.ModelCfg.PresencePenalty,
})
if err != nil {
- return ai.ToolResponse{}, fmt.Errorf("error generating agent: %s", err)
+ return ai.NewTextErrorResponse("error generating response"), nil
}
updatedSession, err := c.sessions.Get(ctx, session.ID)
if err != nil {
@@ -127,7 +127,7 @@ func testSessionAgent(env env, large, small ai.LanguageModel, systemPrompt strin
// todo: add values
},
}
- agent := NewSessionAgent(largeModel, smallModel, systemPrompt, env.sessions, env.messages, tools...)
+ agent := NewSessionAgent(SessionAgentOptions{largeModel, smallModel, systemPrompt, false, env.sessions, env.messages, tools})
return agent
}
@@ -165,7 +165,7 @@ func (c *coordinator) buildAgent(prompt *prompt.Prompt, agent config.Agent) (Ses
if err != nil {
return nil, err
}
- return NewSessionAgent(large, small, systemPrompt, c.sessions, c.messages, tools...), nil
+ return NewSessionAgent(SessionAgentOptions{large, small, systemPrompt, c.cfg.Options.DisableAutoSummarize, c.sessions, c.messages, tools}), nil
}
func (c *coordinator) buildTools(agent config.Agent) ([]ai.AgentTool, error) {
@@ -89,7 +89,10 @@ type GrepResponseMetadata struct {
Truncated bool `json:"truncated"`
}
-const GrepToolName = "grep"
+const (
+ GrepToolName = "grep"
+ maxGrepContentWidth = 500
+)
//go:embed grep.md
var grepDescription []byte
@@ -135,7 +138,11 @@ func NewGrepTool(workingDir string) ai.AgentTool {
fmt.Fprintf(&output, "%s:\n", match.path)
}
if match.lineNum > 0 {
- fmt.Fprintf(&output, " Line %d: %s\n", match.lineNum, match.lineText)
+ lineText := match.lineText
+ if len(lineText) > maxGrepContentWidth {
+ lineText = lineText[:maxGrepContentWidth] + "..."
+ }
+ fmt.Fprintf(&output, " Line %d: %s\n", match.lineNum, lineText)
} else {
fmt.Fprintf(&output, " %s\n", match.path)
}
@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
package db
@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
// source: files.sql
package db
@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
// source: messages.sql
package db
@@ -18,21 +18,23 @@ INSERT INTO messages (
parts,
model,
provider,
+ is_summary_message,
created_at,
updated_at
) VALUES (
- ?, ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now')
+ ?, ?, ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now')
)
-RETURNING id, session_id, role, parts, model, created_at, updated_at, finished_at, provider
+RETURNING id, session_id, role, parts, model, created_at, updated_at, finished_at, provider, is_summary_message
`
type CreateMessageParams struct {
- ID string `json:"id"`
- SessionID string `json:"session_id"`
- Role string `json:"role"`
- Parts string `json:"parts"`
- Model sql.NullString `json:"model"`
- Provider sql.NullString `json:"provider"`
+ ID string `json:"id"`
+ SessionID string `json:"session_id"`
+ Role string `json:"role"`
+ Parts string `json:"parts"`
+ Model sql.NullString `json:"model"`
+ Provider sql.NullString `json:"provider"`
+ IsSummaryMessage int64 `json:"is_summary_message"`
}
func (q *Queries) CreateMessage(ctx context.Context, arg CreateMessageParams) (Message, error) {
@@ -43,6 +45,7 @@ func (q *Queries) CreateMessage(ctx context.Context, arg CreateMessageParams) (M
arg.Parts,
arg.Model,
arg.Provider,
+ arg.IsSummaryMessage,
)
var i Message
err := row.Scan(
@@ -55,6 +58,7 @@ func (q *Queries) CreateMessage(ctx context.Context, arg CreateMessageParams) (M
&i.UpdatedAt,
&i.FinishedAt,
&i.Provider,
+ &i.IsSummaryMessage,
)
return i, err
}
@@ -80,7 +84,7 @@ func (q *Queries) DeleteSessionMessages(ctx context.Context, sessionID string) e
}
const getMessage = `-- name: GetMessage :one
-SELECT id, session_id, role, parts, model, created_at, updated_at, finished_at, provider
+SELECT id, session_id, role, parts, model, created_at, updated_at, finished_at, provider, is_summary_message
FROM messages
WHERE id = ? LIMIT 1
`
@@ -98,12 +102,13 @@ func (q *Queries) GetMessage(ctx context.Context, id string) (Message, error) {
&i.UpdatedAt,
&i.FinishedAt,
&i.Provider,
+ &i.IsSummaryMessage,
)
return i, err
}
const listMessagesBySession = `-- name: ListMessagesBySession :many
-SELECT id, session_id, role, parts, model, created_at, updated_at, finished_at, provider
+SELECT id, session_id, role, parts, model, created_at, updated_at, finished_at, provider, is_summary_message
FROM messages
WHERE session_id = ?
ORDER BY created_at ASC
@@ -128,6 +133,7 @@ func (q *Queries) ListMessagesBySession(ctx context.Context, sessionID string) (
&i.UpdatedAt,
&i.FinishedAt,
&i.Provider,
+ &i.IsSummaryMessage,
); err != nil {
return nil, err
}
@@ -0,0 +1,5 @@
+-- +goose Up
+ALTER TABLE messages ADD COLUMN is_summary_message INTEGER DEFAULT 0 NOT NULL;
+
+-- +goose Down
+ALTER TABLE messages DROP COLUMN is_summary_message;
@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
package db
@@ -19,15 +19,16 @@ type File struct {
}
type Message struct {
- ID string `json:"id"`
- SessionID string `json:"session_id"`
- Role string `json:"role"`
- Parts string `json:"parts"`
- Model sql.NullString `json:"model"`
- CreatedAt int64 `json:"created_at"`
- UpdatedAt int64 `json:"updated_at"`
- FinishedAt sql.NullInt64 `json:"finished_at"`
- Provider sql.NullString `json:"provider"`
+ ID string `json:"id"`
+ SessionID string `json:"session_id"`
+ Role string `json:"role"`
+ Parts string `json:"parts"`
+ Model sql.NullString `json:"model"`
+ CreatedAt int64 `json:"created_at"`
+ UpdatedAt int64 `json:"updated_at"`
+ FinishedAt sql.NullInt64 `json:"finished_at"`
+ Provider sql.NullString `json:"provider"`
+ IsSummaryMessage int64 `json:"is_summary_message"`
}
type Session struct {
@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
package db
@@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
-// sqlc v1.29.0
+// sqlc v1.30.0
// source: sessions.sql
package db
@@ -17,10 +17,11 @@ INSERT INTO messages (
parts,
model,
provider,
+ is_summary_message,
created_at,
updated_at
) VALUES (
- ?, ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now')
+ ?, ?, ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now')
)
RETURNING *;
@@ -120,14 +120,15 @@ type Finish struct {
func (Finish) isPart() {}
type Message struct {
- ID string
- Role MessageRole
- SessionID string
- Parts []ContentPart
- Model string
- Provider string
- CreatedAt int64
- UpdatedAt int64
+ ID string
+ Role MessageRole
+ SessionID string
+ Parts []ContentPart
+ Model string
+ Provider string
+ CreatedAt int64
+ UpdatedAt int64
+ IsSummaryMessage bool
}
func (m *Message) Content() TextContent {
@@ -13,10 +13,11 @@ import (
)
type CreateMessageParams struct {
- Role MessageRole
- Parts []ContentPart
- Model string
- Provider string
+ Role MessageRole
+ Parts []ContentPart
+ Model string
+ Provider string
+ IsSummaryMessage bool
}
type Service interface {
@@ -64,13 +65,18 @@ func (s *service) Create(ctx context.Context, sessionID string, params CreateMes
if err != nil {
return Message{}, err
}
+ isSummary := int64(0)
+ if params.IsSummaryMessage {
+ isSummary = 1
+ }
dbMessage, err := s.q.CreateMessage(ctx, db.CreateMessageParams{
- ID: uuid.New().String(),
- SessionID: sessionID,
- Role: string(params.Role),
- Parts: string(partsJSON),
- Model: sql.NullString{String: string(params.Model), Valid: true},
- Provider: sql.NullString{String: params.Provider, Valid: params.Provider != ""},
+ ID: uuid.New().String(),
+ SessionID: sessionID,
+ Role: string(params.Role),
+ Parts: string(partsJSON),
+ Model: sql.NullString{String: string(params.Model), Valid: true},
+ Provider: sql.NullString{String: params.Provider, Valid: params.Provider != ""},
+ IsSummaryMessage: isSummary,
})
if err != nil {
return Message{}, err
@@ -151,14 +157,15 @@ func (s *service) fromDBItem(item db.Message) (Message, error) {
return Message{}, err
}
return Message{
- ID: item.ID,
- SessionID: item.SessionID,
- Role: MessageRole(item.Role),
- Parts: parts,
- Model: item.Model.String,
- Provider: item.Provider.String,
- CreatedAt: item.CreatedAt,
- UpdatedAt: item.UpdatedAt,
+ ID: item.ID,
+ SessionID: item.SessionID,
+ Role: MessageRole(item.Role),
+ Parts: parts,
+ Model: item.Model.String,
+ Provider: item.Provider.String,
+ CreatedAt: item.CreatedAt,
+ UpdatedAt: item.UpdatedAt,
+ IsSummaryMessage: item.IsSummaryMessage != 0,
}, nil
}
@@ -334,6 +334,11 @@ func (m *messageListCmp) handleMessageEvent(event pubsub.Event[message.Message])
return nil
}
return m.handleNewMessage(event.Payload)
+ case pubsub.DeletedEvent:
+ if event.Payload.SessionID != m.session.ID {
+ return nil
+ }
+ return m.handleDeleteMessage(event.Payload)
case pubsub.UpdatedEvent:
if event.Payload.SessionID != m.session.ID {
return m.handleChildSession(event)
@@ -360,6 +365,18 @@ func (m *messageListCmp) messageExists(messageID string) bool {
return false
}
+// handleDeleteMessage removes a message from the list.
+func (m *messageListCmp) handleDeleteMessage(msg message.Message) tea.Cmd {
+ items := m.listCmp.Items()
+ for i := len(items) - 1; i >= 0; i-- {
+ if msgCmp, ok := items[i].(messages.MessageCmp); ok && msgCmp.GetMessage().ID == msg.ID {
+ m.listCmp.DeleteItem(items[i].ID())
+ return nil
+ }
+ }
+ return nil
+}
+
// handleNewMessage routes new messages to appropriate handlers based on role.
func (m *messageListCmp) handleNewMessage(msg message.Message) tea.Cmd {
switch msg.Role {
@@ -121,6 +121,9 @@ func (m *messageCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Returns different views for spinning, user, and assistant messages.
func (m *messageCmp) View() string {
if m.spinning && m.message.ReasoningContent().Thinking == "" {
+ if m.message.IsSummaryMessage {
+ m.anim.SetLabel("Summarizing")
+ }
return m.style().PaddingLeft(1).Render(m.anim.View())
}
if m.message.ID != "" {
@@ -1,168 +0,0 @@
-package compact
-
-import (
- "context"
-
- "github.com/charmbracelet/bubbles/v2/key"
- tea "github.com/charmbracelet/bubbletea/v2"
- "github.com/charmbracelet/lipgloss/v2"
-
- "github.com/charmbracelet/crush/internal/agent"
- "github.com/charmbracelet/crush/internal/tui/components/core"
- "github.com/charmbracelet/crush/internal/tui/components/dialogs"
- "github.com/charmbracelet/crush/internal/tui/styles"
- "github.com/charmbracelet/crush/internal/tui/util"
-)
-
-const CompactDialogID dialogs.DialogID = "compact"
-
-// CompactDialog interface for the session compact dialog
-type CompactDialog interface {
- dialogs.DialogModel
-}
-
-type compactDialogCmp struct {
- wWidth, wHeight int
- width, height int
- selected int
- keyMap KeyMap
- sessionID string
- progress string
- agent agent.Coordinator
- noAsk bool // If true, skip confirmation dialog
-}
-
-// NewCompactDialogCmp creates a new session compact dialog
-func NewCompactDialogCmp(agent agent.Coordinator, sessionID string, noAsk bool) CompactDialog {
- return &compactDialogCmp{
- sessionID: sessionID,
- keyMap: DefaultKeyMap(),
- selected: 0,
- agent: agent,
- noAsk: noAsk,
- }
-}
-
-func (c *compactDialogCmp) Init() tea.Cmd {
- if c.noAsk {
- // If noAsk is true, skip confirmation and start compaction immediately
- c.agent.Summarize(context.Background(), c.sessionID)
- return util.CmdHandler(dialogs.CloseDialogMsg{})
- }
- return nil
-}
-
-func (c *compactDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
- switch msg := msg.(type) {
- case tea.WindowSizeMsg:
- c.wWidth = msg.Width
- c.wHeight = msg.Height
- cmd := c.SetSize()
- return c, cmd
- case tea.KeyPressMsg:
- switch {
- case key.Matches(msg, c.keyMap.ChangeSelection):
- c.selected = (c.selected + 1) % 2
- return c, nil
- case key.Matches(msg, c.keyMap.Select):
- if c.selected == 0 {
- c.agent.Summarize(context.Background(), c.sessionID)
- return c, util.CmdHandler(dialogs.CloseDialogMsg{})
- } else {
- return c, util.CmdHandler(dialogs.CloseDialogMsg{})
- }
- case key.Matches(msg, c.keyMap.Y):
- c.agent.Summarize(context.Background(), c.sessionID)
- return c, util.CmdHandler(dialogs.CloseDialogMsg{})
- case key.Matches(msg, c.keyMap.N):
- return c, util.CmdHandler(dialogs.CloseDialogMsg{})
- case key.Matches(msg, c.keyMap.Close):
- return c, util.CmdHandler(dialogs.CloseDialogMsg{})
- }
- }
- return c, nil
-}
-
-func (c *compactDialogCmp) renderButtons() string {
- t := styles.CurrentTheme()
- baseStyle := t.S().Base
-
- buttons := []core.ButtonOpts{
- {
- Text: "Yes",
- UnderlineIndex: 0, // "Y"
- Selected: c.selected == 0,
- },
- {
- Text: "No",
- UnderlineIndex: 0, // "N"
- Selected: c.selected == 1,
- },
- }
-
- content := core.SelectableButtons(buttons, " ")
-
- return baseStyle.AlignHorizontal(lipgloss.Right).Width(c.width - 4).Render(content)
-}
-
-func (c *compactDialogCmp) render() string {
- t := styles.CurrentTheme()
- baseStyle := t.S().Base
-
- title := "Compact Session"
- titleView := core.Title(title, c.width-4)
- explanation := t.S().Text.
- Width(c.width - 4).
- Render("This will summarize the current session and reset the context. The conversation history will be condensed into a summary to free up context space while preserving important information.")
-
- question := t.S().Text.
- Width(c.width - 4).
- Render("Do you want to continue?")
-
- content := baseStyle.Render(lipgloss.JoinVertical(
- lipgloss.Left,
- explanation,
- "",
- question,
- ))
-
- buttons := c.renderButtons()
- dialogContent := lipgloss.JoinVertical(
- lipgloss.Top,
- titleView,
- "",
- content,
- "",
- buttons,
- "",
- )
-
- return baseStyle.
- Padding(0, 1).
- Border(lipgloss.RoundedBorder()).
- BorderForeground(t.BorderFocus).
- Width(c.width).
- Render(dialogContent)
-}
-
-func (c *compactDialogCmp) View() string {
- return c.render()
-}
-
-// SetSize sets the size of the component.
-func (c *compactDialogCmp) SetSize() tea.Cmd {
- c.width = min(90, c.wWidth)
- c.height = min(15, c.wHeight)
- return nil
-}
-
-func (c *compactDialogCmp) Position() (int, int) {
- row := (c.wHeight / 2) - (c.height / 2)
- col := (c.wWidth / 2) - (c.width / 2)
- return row, col
-}
-
-// ID implements CompactDialog.
-func (c *compactDialogCmp) ID() dialogs.DialogID {
- return CompactDialogID
-}
@@ -1,71 +0,0 @@
-package compact
-
-import (
- "github.com/charmbracelet/bubbles/v2/key"
-)
-
-// KeyMap defines the key bindings for the compact dialog.
-type KeyMap struct {
- ChangeSelection key.Binding
- Select key.Binding
- Y key.Binding
- N key.Binding
- Close key.Binding
-}
-
-// DefaultKeyMap returns the default key bindings for the compact dialog.
-func DefaultKeyMap() KeyMap {
- return KeyMap{
- ChangeSelection: key.NewBinding(
- key.WithKeys("tab", "left", "right", "h", "l"),
- key.WithHelp("tab/←/→", "toggle selection"),
- ),
- Select: key.NewBinding(
- key.WithKeys("enter"),
- key.WithHelp("enter", "confirm"),
- ),
- Y: key.NewBinding(
- key.WithKeys("y"),
- key.WithHelp("y", "yes"),
- ),
- N: key.NewBinding(
- key.WithKeys("n"),
- key.WithHelp("n", "no"),
- ),
- Close: key.NewBinding(
- key.WithKeys("esc", "alt+esc"),
- key.WithHelp("esc", "cancel"),
- ),
- }
-}
-
-// KeyBindings implements layout.KeyMapProvider
-func (k KeyMap) KeyBindings() []key.Binding {
- return []key.Binding{
- k.ChangeSelection,
- k.Select,
- k.Y,
- k.N,
- k.Close,
- }
-}
-
-// FullHelp implements help.KeyMap.
-func (k KeyMap) FullHelp() [][]key.Binding {
- m := [][]key.Binding{}
- slice := k.KeyBindings()
- for i := 0; i < len(slice); i += 4 {
- end := min(i+4, len(slice))
- m = append(m, slice[i:end])
- }
- return m
-}
-
-// ShortHelp implements help.KeyMap.
-func (k KeyMap) ShortHelp() []key.Binding {
- return []key.Binding{
- k.ChangeSelection,
- k.Select,
- k.Close,
- }
-}
@@ -21,7 +21,6 @@ import (
"github.com/charmbracelet/crush/internal/tui/components/core/status"
"github.com/charmbracelet/crush/internal/tui/components/dialogs"
"github.com/charmbracelet/crush/internal/tui/components/dialogs/commands"
- "github.com/charmbracelet/crush/internal/tui/components/dialogs/compact"
"github.com/charmbracelet/crush/internal/tui/components/dialogs/filepicker"
"github.com/charmbracelet/crush/internal/tui/components/dialogs/models"
"github.com/charmbracelet/crush/internal/tui/components/dialogs/permissions"
@@ -178,9 +177,13 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
)
// Compact
case commands.CompactMsg:
- return a, util.CmdHandler(dialogs.OpenDialogMsg{
- Model: compact.NewCompactDialogCmp(a.app.AgentCoordinator, msg.SessionID, true),
- })
+ return a, func() tea.Msg {
+ err := a.app.AgentCoordinator.Summarize(context.Background(), msg.SessionID)
+ if err != nil {
+ return util.ReportError(err)()
+ }
+ return nil
+ }
case commands.QuitMsg:
return a, util.CmdHandler(dialogs.OpenDialogMsg{
Model: quit.NewQuitDialog(),
@@ -251,38 +254,6 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
a.app.Permissions.Deny(msg.Permission)
}
return a, nil
- // Agent Events
- // TODO: HANDLE AUTO COMPACT
- // case pubsub.Event[agent.AgentEvent]:
- // payload := msg.Payload
- //
- // // Forward agent events to dialogs
- // if a.dialog.HasDialogs() && a.dialog.ActiveDialogID() == compact.CompactDialogID {
- // u, dialogCmd := a.dialog.Update(payload)
- // if model, ok := u.(dialogs.DialogCmp); ok {
- // a.dialog = model
- // }
- //
- // cmds = append(cmds, dialogCmd)
- // }
- //
- // // Handle auto-compact logic
- // if payload.Done && payload.Type == agent.AgentEventTypeResponse && a.selectedSessionID != "" {
- // // Get current session to check token usage
- // session, err := a.app.Sessions.Get(context.Background(), a.selectedSessionID)
- // if err == nil {
- // model := a.app.AgentCoordinator.Model()
- // contextWindow := model.CatwalkCfg.ContextWindow
- // tokens := session.CompletionTokens + session.PromptTokens
- // if (tokens >= int64(float64(contextWindow)*0.95)) && !config.Get().Options.DisableAutoSummarize { // Show compact confirmation dialog
- // cmds = append(cmds, util.CmdHandler(dialogs.OpenDialogMsg{
- // Model: compact.NewCompactDialogCmp(a.app.AgentCoordinator, a.selectedSessionID, false),
- // }))
- // }
- // }
- // }
- //
- // return a, tea.Batch(cmds...)
case splash.OnboardingCompleteMsg:
item, ok := a.pages[a.currentPage]
if !ok {