Files
gnoma/internal/skill/embed_test.go
vikingowl 3873f90f83 feat: local model reliability — SDK retries, capability probing, init skill, context compaction
Three compounding bugs prevented tool calling with llama.cpp:
- Stream parser set argsComplete on partial JSON (e.g. "{"), dropping
  subsequent argument deltas — fix: use json.Valid to detect completeness
- Missing tool_choice default — llama.cpp needs explicit "auto" to
  activate its GBNF grammar constraint; now set when tools are present
- Tool names in history used internal format (fs.ls) while definitions
  used API format (fs_ls) — now re-sanitized in translateMessage

Additional changes:
- Disable SDK retries for local providers (500s are deterministic)
- Dynamic capability probing via /props (llama.cpp) and /api/show
  (Ollama), replacing hardcoded model prefix list
- Engine respects forced arm ToolUse capability when router is active
- Bundled /init skill with Go template blocks, context-aware for local
  vs cloud models, deduplication rules against CLAUDE.md
- Tool result compaction for local models — previous round results
  replaced with size markers to stay within small context windows
- Text-only fallback when tool-parse errors occur on local models
- "text-only" TUI indicator when model lacks tool support
- Session ResetError for retry after stream failures
- AllowedTools per-turn filtering in engine buildRequest
2026-04-13 02:01:01 +02:00

239 lines
5.3 KiB
Go

package skill
import (
"testing"
)
func TestBundledSkills_NotEmpty(t *testing.T) {
skills, err := BundledSkills()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(skills) == 0 {
t.Error("expected at least one bundled skill")
}
}
func TestBundledSkills_BatchExists(t *testing.T) {
skills, err := BundledSkills()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var batch *Skill
for _, s := range skills {
if s.Frontmatter.Name == "batch" {
batch = s
break
}
}
if batch == nil {
t.Fatal("batch skill not found in bundled skills")
}
if batch.Frontmatter.Description == "" {
t.Error("batch skill missing description")
}
if batch.Body == "" {
t.Error("batch skill has empty body")
}
if batch.Source != "bundled" {
t.Errorf("batch skill source = %q, want %q", batch.Source, "bundled")
}
}
func TestBundledSkills_AllParseClean(t *testing.T) {
skills, err := BundledSkills()
if err != nil {
t.Fatalf("BundledSkills() error: %v", err)
}
for _, s := range skills {
if s.Frontmatter.Name == "" {
t.Errorf("bundled skill has empty name: %+v", s)
}
}
}
func TestBundledSkills_InitExists(t *testing.T) {
skills, err := BundledSkills()
if err != nil {
t.Fatalf("BundledSkills() error: %v", err)
}
var init *Skill
for _, s := range skills {
if s.Frontmatter.Name == "init" {
init = s
break
}
}
if init == nil {
t.Fatal("init skill not found in bundled skills")
}
if init.Frontmatter.Description == "" {
t.Error("init skill missing description")
}
if init.Body == "" {
t.Error("init skill has empty body")
}
}
func TestBundledSkills_InitRender_Local(t *testing.T) {
skills, err := BundledSkills()
if err != nil {
t.Fatalf("BundledSkills() error: %v", err)
}
var init *Skill
for _, s := range skills {
if s.Frontmatter.Name == "init" {
init = s
break
}
}
if init == nil {
t.Fatal("init skill not found")
}
rendered, err := init.Render(TemplateData{
ProjectRoot: "/tmp/myproject",
Local: true,
})
if err != nil {
t.Fatalf("Render() error: %v", err)
}
// Local mode should use sequential fs_* tools, not spawn_elfs for orchestration
if !contains(rendered, "fs_ls") {
t.Error("local render should contain fs_ls")
}
if !contains(rendered, "fs_read") {
t.Error("local render should contain fs_read")
}
if contains(rendered, "Use spawn_elfs") {
t.Error("local render should NOT instruct to use spawn_elfs")
}
if !contains(rendered, "/tmp/myproject") {
t.Error("local render should contain ProjectRoot")
}
}
func TestBundledSkills_InitRender_Cloud(t *testing.T) {
skills, err := BundledSkills()
if err != nil {
t.Fatalf("BundledSkills() error: %v", err)
}
var init *Skill
for _, s := range skills {
if s.Frontmatter.Name == "init" {
init = s
break
}
}
if init == nil {
t.Fatal("init skill not found")
}
rendered, err := init.Render(TemplateData{
ProjectRoot: "/tmp/myproject",
Local: false,
})
if err != nil {
t.Fatalf("Render() error: %v", err)
}
// Cloud mode should use spawn_elfs
if !contains(rendered, "spawn_elfs") {
t.Error("cloud render should contain spawn_elfs")
}
if !contains(rendered, "Elf 1") {
t.Error("cloud render should contain Elf 1")
}
if !contains(rendered, "/tmp/myproject") {
t.Error("cloud render should contain ProjectRoot")
}
if !contains(rendered, "creating") {
t.Error("cloud render (no Args) should say 'creating'")
}
}
func TestBundledSkills_InitRender_CloudUpdate(t *testing.T) {
skills, err := BundledSkills()
if err != nil {
t.Fatalf("BundledSkills() error: %v", err)
}
var init *Skill
for _, s := range skills {
if s.Frontmatter.Name == "init" {
init = s
break
}
}
if init == nil {
t.Fatal("init skill not found")
}
rendered, err := init.Render(TemplateData{
ProjectRoot: "/tmp/myproject",
Args: "/tmp/myproject/AGENTS.md",
Local: false,
})
if err != nil {
t.Fatalf("Render() error: %v", err)
}
// Cloud update mode should have Elf 4 for review
if !contains(rendered, "Elf 4") {
t.Error("cloud update render should contain Elf 4")
}
if !contains(rendered, "updating") {
t.Error("cloud update render should say 'updating'")
}
if !contains(rendered, "/tmp/myproject/AGENTS.md") {
t.Error("cloud update render should contain existing path")
}
}
func TestBundledSkills_InitRender_LocalUpdate(t *testing.T) {
skills, err := BundledSkills()
if err != nil {
t.Fatalf("BundledSkills() error: %v", err)
}
var init *Skill
for _, s := range skills {
if s.Frontmatter.Name == "init" {
init = s
break
}
}
if init == nil {
t.Fatal("init skill not found")
}
rendered, err := init.Render(TemplateData{
ProjectRoot: "/tmp/myproject",
Args: "/tmp/myproject/AGENTS.md",
Local: true,
})
if err != nil {
t.Fatalf("Render() error: %v", err)
}
// Local update should mention existing file
if !contains(rendered, "existing AGENTS.md") {
t.Error("local update render should mention existing file")
}
if contains(rendered, "Use spawn_elfs") {
t.Error("local update render should NOT instruct to use spawn_elfs")
}
}
func contains(s, substr string) bool {
return len(s) > 0 && len(substr) > 0 && stringContains(s, substr)
}
func stringContains(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}