Land staged session/MCP/chrome work + sidebar clear-J fix
This batches the in-flight [Unreleased] block from CHANGELOG.md into a single commit. Highlights: - Real MCP protocol layer (initialize / tools/list / tools/call) so vendor MCP clients can complete the handshake against the per-PID socket. Legacy direct-dispatch preserved for the harness. - New mcp_injection kinds — cli_override for codex, config_env for opencode — joining the existing env-var and config_file paths so patterm can slot into more agents without touching their real config or auth. - Ctrl+A/D and Ctrl+W/S focus navigation across tabs and intra-tab process lists, recognised in legacy / kitty CSI u / xterm modifyOtherKeys encodings. - Palette macros (sw / k / sp ) and reordering so open sessions surface above spawn-new entries. - Two-row tab bar, sidebar/tabbar/status chrome cache, viewport-wipe on agent spawn, CR-terminated orchestrator injections, and split- Enter PTY writes so paste-detecting TUIs see Enter as a key event. Also fixes the bug logged in TODO: claude's Ctrl+O tool-call expansion emits CSI 0 J, which the viewport renderer was forwarding verbatim — wiping the sidebar to the right of the cursor and leaving the chrome cache convinced nothing had changed. CSI 0 J and CSI 1 J are now translated into per-row ECH sequences clamped to the viewport, same as CSI 2 J and CSI K already were. Agent guides (CLAUDE.md / AGENTS.md) now spell out the TODO->CHANGELOG workflow so completed items land in the changelog rather than as ticked entries left behind in TODO.
This commit is contained in:
@@ -38,6 +38,29 @@ type paletteState struct {
|
||||
items []paletteItem
|
||||
}
|
||||
|
||||
// macroPrefixes maps the palette macro prefix (without trailing space)
|
||||
// to the paletteAction.kind values that should be retained when that
|
||||
// macro is active. Typing `sw <query>` filters to switch entries only,
|
||||
// `k <query>` to kills, `sp <query>` to spawn entries (agents +
|
||||
// processes).
|
||||
var macroPrefixes = map[string][]string{
|
||||
"sw": {"switch"},
|
||||
"k": {"kill"},
|
||||
"sp": {"spawn-agent", "spawn-process"},
|
||||
}
|
||||
|
||||
// detectMacro returns the macro keyword and the remaining query, or
|
||||
// ("", original) if no macro is active. A macro is active when the
|
||||
// query starts with one of the known prefixes followed by a space.
|
||||
func detectMacro(q string) (macro, rest string) {
|
||||
for k := range macroPrefixes {
|
||||
if len(q) > len(k) && q[:len(k)] == k && q[len(k)] == ' ' {
|
||||
return k, q[len(k)+1:]
|
||||
}
|
||||
}
|
||||
return "", q
|
||||
}
|
||||
|
||||
func newPalette(children []*Child, focused string, presets preset.Set) *paletteState {
|
||||
p := &paletteState{children: children, focused: focused, presets: presets}
|
||||
p.rebuild()
|
||||
@@ -47,6 +70,21 @@ func newPalette(children []*Child, focused string, presets preset.Set) *paletteS
|
||||
func (p *paletteState) rebuild() {
|
||||
all := p.allItems()
|
||||
q := strings.ToLower(string(p.query))
|
||||
macro, rest := detectMacro(q)
|
||||
if macro != "" {
|
||||
kinds := macroPrefixes[macro]
|
||||
filtered := all[:0:0]
|
||||
for _, it := range all {
|
||||
for _, k := range kinds {
|
||||
if it.action.kind == k {
|
||||
filtered = append(filtered, it)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
all = filtered
|
||||
q = rest
|
||||
}
|
||||
if q == "" {
|
||||
p.items = all
|
||||
} else {
|
||||
@@ -68,8 +106,32 @@ func (p *paletteState) rebuild() {
|
||||
func (p *paletteState) allItems() []paletteItem {
|
||||
var out []paletteItem
|
||||
|
||||
// Preset commands first — SPEC §4 calls these out as the primary
|
||||
// way to spawn anything. One entry per file under presets/.
|
||||
// Switch entries first — existing open agents/processes should
|
||||
// surface above options to spawn new ones. Hide non-running agents
|
||||
// (e.g. killed ones) so the list doesn't accumulate corpses. Command
|
||||
// processes are session-persistent, so they remain visible after
|
||||
// exit to keep restart_process in reach.
|
||||
for _, c := range p.children {
|
||||
if c.Kind == KindAgent && c.Status() != StatusRunning {
|
||||
continue
|
||||
}
|
||||
label := "Switch to " + c.Name
|
||||
hint := strings.Join(c.Argv, " ")
|
||||
if c.ID == p.focused {
|
||||
label = "• " + label + " (current)"
|
||||
}
|
||||
if c.Status() != StatusRunning {
|
||||
label = label + " [" + string(c.Status()) + "]"
|
||||
}
|
||||
out = append(out, paletteItem{
|
||||
label: label,
|
||||
hint: hint,
|
||||
action: paletteAction{kind: "switch", childID: c.ID},
|
||||
})
|
||||
}
|
||||
|
||||
// Preset commands — SPEC §4 calls these out as the primary way to
|
||||
// spawn anything. One entry per file under presets/.
|
||||
for _, pr := range p.presets.Agents {
|
||||
out = append(out, paletteItem{
|
||||
label: "Spawn agent: " + pr.Name,
|
||||
@@ -85,22 +147,7 @@ func (p *paletteState) allItems() []paletteItem {
|
||||
})
|
||||
}
|
||||
|
||||
// Switch / Kill entries — one per existing child.
|
||||
for _, c := range p.children {
|
||||
label := "Switch to " + c.Name
|
||||
hint := strings.Join(c.Argv, " ")
|
||||
if c.ID == p.focused {
|
||||
label = "• " + label + " (current)"
|
||||
}
|
||||
if c.Status() != StatusRunning {
|
||||
label = label + " [" + string(c.Status()) + "]"
|
||||
}
|
||||
out = append(out, paletteItem{
|
||||
label: label,
|
||||
hint: hint,
|
||||
action: paletteAction{kind: "switch", childID: c.ID},
|
||||
})
|
||||
}
|
||||
// Kill entries last among the action rows, before Quit.
|
||||
for _, c := range p.children {
|
||||
if c.Status() != StatusRunning {
|
||||
continue
|
||||
@@ -447,7 +494,7 @@ func (p *paletteState) render(out writeFlusher, cols, rows int) {
|
||||
b.WriteString(styleBorder + "├" + strings.Repeat("─", width-2) + "┤" + styleReset)
|
||||
row++
|
||||
|
||||
footer := "↵ run · esc close · ↑↓ navigate"
|
||||
footer := "↵ run · esc close · ↑↓ navigate · sw/k/sp <q> filter"
|
||||
fLen := utf8.RuneCountInString(footer)
|
||||
fPad := content - fLen
|
||||
if fPad < 0 {
|
||||
|
||||
Reference in New Issue
Block a user