Add auto-summary settings
This commit is contained in:
@@ -55,6 +55,10 @@ func Run(ctx context.Context, opts Options) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("app: load presets: %w", err)
|
||||
}
|
||||
appSettings, settingsPath, err := loadSettings()
|
||||
if err != nil {
|
||||
logf("settings load: %v", err)
|
||||
}
|
||||
|
||||
// Ensure the per-project scratchpad dir exists so MCP and the UI
|
||||
// can read/write into it. SPEC §3.
|
||||
@@ -158,18 +162,35 @@ func Run(ctx context.Context, opts Options) error {
|
||||
go sess.runClassifier(ctx)
|
||||
|
||||
st := &uiState{
|
||||
sess: sess,
|
||||
presets: presets,
|
||||
launcher: launcher,
|
||||
pads: pads,
|
||||
chromeWake: make(chan struct{}, 1),
|
||||
trust: trustStore,
|
||||
timers: host.timers,
|
||||
hostCols: cols,
|
||||
hostRows: rows,
|
||||
stdinTTY: term.IsTerminal(int(os.Stdin.Fd())),
|
||||
metrics: metrics,
|
||||
sess: sess,
|
||||
presets: presets,
|
||||
launcher: launcher,
|
||||
pads: pads,
|
||||
chromeWake: make(chan struct{}, 1),
|
||||
trust: trustStore,
|
||||
timers: host.timers,
|
||||
hostCols: cols,
|
||||
hostRows: rows,
|
||||
stdinTTY: term.IsTerminal(int(os.Stdin.Fd())),
|
||||
metrics: metrics,
|
||||
settings: appSettings,
|
||||
settingsPath: settingsPath,
|
||||
ctx: ctx,
|
||||
}
|
||||
st.summaries = newSummaryManager(sess, opts.ProjectDir, presets, func() autoSummarySettings {
|
||||
st.settingsMu.Lock()
|
||||
defer st.settingsMu.Unlock()
|
||||
return st.settings.AutoSummary.clone()
|
||||
}, func() {
|
||||
st.markChromeDirty()
|
||||
st.markSidebarDirty()
|
||||
}, func(_ string, result summaryState) {
|
||||
if result.Error != "" {
|
||||
st.flashError(fmt.Sprintf("summary: %v", result.Error))
|
||||
return
|
||||
}
|
||||
st.flashTransient("summary updated")
|
||||
})
|
||||
sess.SetMetrics(metrics)
|
||||
host.attention = st
|
||||
host.focus = st
|
||||
@@ -177,6 +198,7 @@ func Run(ctx context.Context, opts Options) error {
|
||||
host.scratch = st
|
||||
st.lastExit.Store(-1)
|
||||
sess.Subscribe(st)
|
||||
go st.summaries.run(ctx)
|
||||
|
||||
st.enterScreen()
|
||||
st.renderEmptyState()
|
||||
@@ -398,7 +420,6 @@ type uiState struct {
|
||||
// switch resets the offset cleanly.
|
||||
padOffsetName string
|
||||
|
||||
|
||||
// activeAgentID tracks which top-level agent tab "owns" the agent
|
||||
// tree section of the sidebar. It only updates when focus lands on
|
||||
// an agent (or one of its sub-agents), so the agent tree stays
|
||||
@@ -432,6 +453,12 @@ type uiState struct {
|
||||
// check on the disabled path.
|
||||
metrics *metricsTracker
|
||||
|
||||
settingsMu sync.Mutex
|
||||
settings settings
|
||||
settingsPath string
|
||||
ctx context.Context
|
||||
summaries *summaryManager
|
||||
|
||||
// chromeCacheMu guards the last-rendered byte cache for each chrome
|
||||
// element. The tab bar, sidebar, and status line all repaint on
|
||||
// many state changes and on every PTY chunk, but their content
|
||||
@@ -478,6 +505,33 @@ func (st *uiState) dbgf(format string, args ...any) {
|
||||
logf(format, args...)
|
||||
}
|
||||
|
||||
func (st *uiState) activeSummaryText(width int) string {
|
||||
if width <= 0 || st.summaries == nil {
|
||||
return ""
|
||||
}
|
||||
st.settingsMu.Lock()
|
||||
enabled := st.settings.AutoSummary.Enabled
|
||||
st.settingsMu.Unlock()
|
||||
if !enabled {
|
||||
return ""
|
||||
}
|
||||
st.mu.Lock()
|
||||
active := st.activeAgentID
|
||||
st.mu.Unlock()
|
||||
if active == "" {
|
||||
return ""
|
||||
}
|
||||
sum := st.summaries.Summary(active)
|
||||
text := strings.TrimSpace(sum.Text)
|
||||
if text == "" {
|
||||
return ""
|
||||
}
|
||||
if visibleLen(text) > width {
|
||||
text = clipRunes(text, width-1) + "…"
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
// trustRequest is one outstanding SPEC §7 trust prompt: an agent tried
|
||||
// to spawn / start / restart against an untrusted command preset and
|
||||
// the host wants user confirmation before the next attempt succeeds.
|
||||
@@ -707,6 +761,9 @@ func (st *uiState) scratchpadsChanged() {
|
||||
// on whatever the user was watching; the new child is still surfaced in
|
||||
// the sidebar/tab bar so it's reachable via the palette or select_process.
|
||||
func (st *uiState) OnChildSpawned(c *Child) {
|
||||
if st.summaries != nil {
|
||||
st.summaries.RegisterChild(c)
|
||||
}
|
||||
if c.ParentID != "" {
|
||||
st.mu.Lock()
|
||||
if st.palette != nil {
|
||||
@@ -781,6 +838,9 @@ func (st *uiState) OnChildStateChanged(string, IdleState) {
|
||||
// OnChildExited drops focus and shows the empty state if it was the
|
||||
// focused child.
|
||||
func (st *uiState) OnChildExited(c *Child) {
|
||||
if st.summaries != nil {
|
||||
st.summaries.UnregisterChild(c.ID)
|
||||
}
|
||||
st.lastExit.Store(int32(c.ExitCode()))
|
||||
st.marquee.reset()
|
||||
layout := st.layoutSnapshot()
|
||||
@@ -868,6 +928,9 @@ func (st *uiState) OnPTYOut(childID string, chunk []byte) {
|
||||
if st.metrics != nil {
|
||||
entry = time.Now()
|
||||
}
|
||||
if st.summaries != nil {
|
||||
st.summaries.ObserveOutput(childID)
|
||||
}
|
||||
layout := st.layoutSnapshot()
|
||||
st.mu.Lock()
|
||||
focus := st.focusedID
|
||||
@@ -1361,6 +1424,9 @@ func (st *uiState) processStdin(chunk []byte) {
|
||||
// writes so claude / codex / opencode don't treat a
|
||||
// "text\r" batch as a paste.
|
||||
_ = c.InjectAsUser(forward)
|
||||
if st.summaries != nil {
|
||||
st.summaries.ObserveHumanInput(c.ID, forward)
|
||||
}
|
||||
if prev != OwnerUser {
|
||||
go st.drawStatusLine()
|
||||
}
|
||||
@@ -1763,7 +1829,10 @@ func (st *uiState) scrollFocusedViewportToBottom() {
|
||||
}
|
||||
|
||||
func (st *uiState) openPaletteLocked() {
|
||||
st.palette = newPalette(st.sess.Children(), st.focusedID, st.focusedPad, st.presets)
|
||||
st.settingsMu.Lock()
|
||||
appSettings := st.settings.clone()
|
||||
st.settingsMu.Unlock()
|
||||
st.palette = newPalette(st.sess.Children(), st.focusedID, st.focusedPad, st.presets, appSettings)
|
||||
// Push a "no kitty flags" entry onto the host terminal's keyboard
|
||||
// stack so palette input arrives in plain legacy form regardless of
|
||||
// what the focused child pushed. Codex/ratatui enables kitty mode
|
||||
@@ -1936,9 +2005,85 @@ func (st *uiState) closePalette(action paletteAction) {
|
||||
|
||||
case "proc-restart":
|
||||
st.handleProcRestart(action.childID)
|
||||
|
||||
case "settings-close":
|
||||
st.applySettingsAction(action)
|
||||
restoreView()
|
||||
st.drawTabBar()
|
||||
st.drawSidebar()
|
||||
st.drawStatusLine()
|
||||
|
||||
case "settings-test":
|
||||
st.applySettingsAction(action)
|
||||
restoreView()
|
||||
st.drawTabBar()
|
||||
st.drawSidebar()
|
||||
st.drawStatusLine()
|
||||
go st.testSummarizer()
|
||||
|
||||
case "settings-run-now":
|
||||
st.applySettingsAction(action)
|
||||
restoreView()
|
||||
st.drawTabBar()
|
||||
st.drawSidebar()
|
||||
st.drawStatusLine()
|
||||
st.runSummaryNow()
|
||||
}
|
||||
}
|
||||
|
||||
func (st *uiState) applySettingsAction(action paletteAction) {
|
||||
if action.settings == nil {
|
||||
return
|
||||
}
|
||||
next := action.settings.clone()
|
||||
st.settingsMu.Lock()
|
||||
path := st.settingsPath
|
||||
st.settingsMu.Unlock()
|
||||
if err := saveSettings(path, next); err != nil {
|
||||
st.flashError(fmt.Sprintf("save settings: %v", err))
|
||||
return
|
||||
}
|
||||
st.settingsMu.Lock()
|
||||
st.settings = next
|
||||
st.settingsMu.Unlock()
|
||||
}
|
||||
|
||||
func (st *uiState) testSummarizer() {
|
||||
if st.summaries == nil {
|
||||
return
|
||||
}
|
||||
base := st.ctx
|
||||
if base == nil {
|
||||
base = context.Background()
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(base, summaryTimeout)
|
||||
defer cancel()
|
||||
if err := st.summaries.Test(ctx); err != nil {
|
||||
st.flashError(fmt.Sprintf("summarizer test: %v", err))
|
||||
return
|
||||
}
|
||||
st.flashTransient("summarizer test passed")
|
||||
}
|
||||
|
||||
func (st *uiState) runSummaryNow() {
|
||||
if st.summaries == nil {
|
||||
return
|
||||
}
|
||||
st.mu.Lock()
|
||||
active := st.activeAgentID
|
||||
st.mu.Unlock()
|
||||
if active == "" {
|
||||
st.flashError("no active top-level agent to summarize")
|
||||
return
|
||||
}
|
||||
ctx := st.ctx
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
st.summaries.RunNow(ctx, active)
|
||||
st.flashTransient("summary requested")
|
||||
}
|
||||
|
||||
func (st *uiState) handlePadDelete(name string) {
|
||||
if name == "" || st.pads == nil {
|
||||
st.repaintFocused()
|
||||
|
||||
Reference in New Issue
Block a user