Files
patterm/internal/preset/preset_test.go
Harry Bayliss 0725375755 Hold codex in thinking while a turn is running
Codex uses the osc_title_stability idle strategy, but it draws its
progress in the pane body ('Working … esc to interrupt'), not the OSC
title. The title goes stable mid-turn, so ~2s later the classifier
declared codex idle while it was still working. Add a thinking-promoter
pattern ((?i)esc to interrupt) to the codex built-in preset; classify()
checks promoter regexes against the rendered screen before the
title-stability verdict, so codex stays in thinking until the turn's
in-progress footer actually disappears.

Resolves the [CODEX IDLE] TODO item.
2026-05-25 12:43:56 +01:00

132 lines
3.7 KiB
Go

package preset
import (
"os"
"path/filepath"
"testing"
)
func TestLoadUsesBuiltInDefaultsWithoutWritingConfig(t *testing.T) {
configHome := filepath.Join(t.TempDir(), "config")
t.Setenv("XDG_CONFIG_HOME", configHome)
set, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
if _, err := os.Stat(filepath.Join(configHome, "patterm")); !os.IsNotExist(err) {
t.Fatalf("Load created config dir or unexpected stat error: %v", err)
}
if len(set.Agents) != 3 {
t.Fatalf("agents len = %d, want 3", len(set.Agents))
}
claude := presetByName(set.Agents, "claude")
if claude == nil {
t.Fatal("missing built-in claude preset")
}
if claude.IdleDetection == nil || len(claude.IdleDetection.PermissionPatterns) == 0 {
t.Fatalf("built-in claude missing permission patterns: %+v", claude.IdleDetection)
}
codex := presetByName(set.Agents, "codex")
if codex == nil {
t.Fatal("missing built-in codex preset")
}
if codex.IdleDetection == nil || len(codex.IdleDetection.ThinkingPatterns) == 0 {
t.Fatalf("built-in codex missing thinking patterns: %+v", codex.IdleDetection)
}
}
func TestLoadMergesUserOverlayIntoBuiltInPreset(t *testing.T) {
configHome := t.TempDir()
t.Setenv("XDG_CONFIG_HOME", configHome)
dir := filepath.Join(configHome, "patterm", "presets", "agents")
if err := os.MkdirAll(dir, 0o700); err != nil {
t.Fatal(err)
}
writeFile(t, filepath.Join(dir, "claude.json"), `{
"name": "claude",
"argv": ["claude", "--model", "sonnet"],
"idle_detection": { "idle_threshold_ms": 3500 }
}`)
set, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
claude := presetByName(set.Agents, "claude")
if claude == nil {
t.Fatal("missing claude preset")
}
if got := claude.Argv; len(got) != 3 || got[0] != "claude" || got[2] != "sonnet" {
t.Fatalf("argv = %#v", got)
}
if claude.IdleDetection.IdleThresholdMS != 3500 {
t.Fatalf("idle threshold = %d", claude.IdleDetection.IdleThresholdMS)
}
if len(claude.IdleDetection.PermissionPatterns) == 0 {
t.Fatalf("permission patterns were not inherited: %+v", claude.IdleDetection)
}
if claude.MCPInjection == nil || claude.MCPInjection.Kind != "flag" {
t.Fatalf("mcp injection was not inherited: %+v", claude.MCPInjection)
}
}
func TestLoadCanDisableBuiltInPreset(t *testing.T) {
configHome := t.TempDir()
t.Setenv("XDG_CONFIG_HOME", configHome)
dir := filepath.Join(configHome, "patterm", "presets", "agents")
if err := os.MkdirAll(dir, 0o700); err != nil {
t.Fatal(err)
}
writeFile(t, filepath.Join(dir, "opencode.json"), `{"name":"opencode","disabled":true}`)
set, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
if presetByName(set.Agents, "opencode") != nil {
t.Fatal("opencode preset was not disabled")
}
if presetByName(set.Agents, "claude") == nil || presetByName(set.Agents, "codex") == nil {
t.Fatalf("other built-ins missing: %+v", set.Agents)
}
}
func TestLoadAddsCustomUserPreset(t *testing.T) {
configHome := t.TempDir()
t.Setenv("XDG_CONFIG_HOME", configHome)
dir := filepath.Join(configHome, "patterm", "presets", "processes")
if err := os.MkdirAll(dir, 0o700); err != nil {
t.Fatal(err)
}
writeFile(t, filepath.Join(dir, "test.json"), `{"name":"test","argv":["go","test","./..."]}`)
set, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
proc := presetByName(set.Processes, "test")
if proc == nil {
t.Fatal("missing custom process preset")
}
if proc.Kind != KindCommand {
t.Fatalf("kind = %q", proc.Kind)
}
}
func presetByName(ps []*Preset, name string) *Preset {
for _, p := range ps {
if p.Name == name {
return p
}
}
return nil
}
func writeFile(t *testing.T, path, body string) {
t.Helper()
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
t.Fatal(err)
}
}