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:
2026-05-14 19:09:35 +01:00
parent 7649587f9a
commit 3622c41fd0
25 changed files with 1951 additions and 163 deletions

View File

@@ -79,10 +79,39 @@ func (l *Launcher) LaunchAgent(p *preset.Preset, displayName, initialPrompt, par
}
env = append(env, p.MCPInjection.Var+"="+mcpConfigPath)
case "config_file":
// SPEC §10 mentions merging into an external config file. We
// expose the config_path via an env var the user can read
// at preset-creation time; full merge is deferred.
// Merge patterm's MCP entry into a vendored copy of the
// user's existing config file, then point the child at the
// vendored copy via the preset's home_var. The real config
// file is never modified.
envAssign, _, mErr := mcpConfigMerge(p, p.MCPInjection, identity, l.bin, l.mcpSocket)
if mErr != nil {
_ = os.Remove(mcpConfigPath)
return nil, mErr
}
env = append(env, envAssign)
env = append(env, "PATTERM_MCP_CONFIG="+mcpConfigPath)
case "cli_override":
// Inline -c key=value overrides for agents that accept
// them (codex's `-c mcp_servers.patterm.command=...`). No
// filesystem footprint, so the user's real config and auth
// are untouched.
extra, err := mcpCLIOverrideArgs(p, p.MCPInjection, identity, l.bin, l.mcpSocket)
if err != nil {
_ = os.Remove(mcpConfigPath)
return nil, err
}
argv = append(argv, extra...)
case "config_env":
// Read the user's config, merge patterm in, and pass the
// merged document inline via an env var (opencode's
// OPENCODE_CONFIG_CONTENT). Nothing is written to disk and
// XDG_CONFIG_HOME stays as the user set it.
assignment, err := mcpConfigEnv(p, p.MCPInjection, identity, l.bin, l.mcpSocket)
if err != nil {
_ = os.Remove(mcpConfigPath)
return nil, err
}
env = append(env, assignment)
default:
return nil, fmt.Errorf("preset %s: unknown mcp_injection.kind %q", p.Name, p.MCPInjection.Kind)
}
@@ -114,7 +143,10 @@ func (l *Launcher) LaunchAgent(p *preset.Preset, displayName, initialPrompt, par
if initialPrompt == "" {
return
}
_ = c.InjectAsOrchestrator([]byte(initialPrompt + "\n"))
// InjectAsOrchestrator splits Enter onto its own PTY write so
// claude / codex / opencode treat the CR as a key event
// rather than the tail end of a multi-byte paste.
_ = c.InjectAsOrchestrator([]byte(initialPrompt + "\r"))
}()
return c, nil
}