Ctrl K
ComunidadGuías…

internal/app/skill_requirement_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package app

import (
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"

	"github.com/yeomyeonggeori/blueclaw/internal/agentruntime"
	"github.com/yeomyeonggeori/blueclaw/internal/capability"
	"github.com/yeomyeonggeori/blueclaw/internal/config"
)

const skillRequiringAToken = `---
name: internkim-api
description: Drives the company workspace over its public API.
compatibility: Requires python3, network access, and a personal access token in INTERNKIM_TOKEN.
metadata:
  kim.intern.requires-environment: "INTERNKIM_TOKEN"
---
Body.
`

func instructionsForSkillRequiringAToken(t *testing.T) agentInstructions {
	t.Helper()
	deliveredSkillsPath := t.TempDir()
	skillDirectoryPath := filepath.Join(deliveredSkillsPath, "internkim-api")
	if errorValue := os.MkdirAll(skillDirectoryPath, 0o755); errorValue != nil {
		t.Fatal(errorValue)
	}
	if errorValue := os.WriteFile(filepath.Join(skillDirectoryPath, "SKILL.md"), []byte(skillRequiringAToken), 0o600); errorValue != nil {
		t.Fatal(errorValue)
	}
	t.Setenv("BLUECLAW_BUNDLED_SKILLS_PATH", deliveredSkillsPath)
	runtimeConfiguration := config.RuntimeConfiguration{}
	runtimeConfiguration.Terminal.WorkspaceRootPath = t.TempDir()
	return loadAgentInstructions(runtimeConfiguration, agentruntime.NewCapabilityRegistry(capability.Client{}, nil))
}

func skillNames(instructions agentInstructions) []string {
	names := []string{}
	for _, skillInstruction := range instructions.Bundle.Skills {
		names = append(names, skillInstruction.Name)
	}
	return names
}

func TestASkillWhoseVariableThisHostLacksStaysOutOfThePrompt(t *testing.T) {
	t.Setenv("INTERNKIM_TOKEN", "")

	instructions := instructionsForSkillRequiringAToken(t)

	for _, skillName := range skillNames(instructions) {
		if skillName == "internkim-api" {
			t.Fatal("a skill this host cannot run was offered to the agent anyway")
		}
	}
	if len(instructions.UnavailableSkills) != 1 {
		t.Fatalf("expected the skill listed as unavailable, got %+v", instructions.UnavailableSkills)
	}
	unavailableSkill := instructions.UnavailableSkills[0]
	if unavailableSkill.Name != "internkim-api" {
		t.Fatalf("expected internkim-api, got %q", unavailableSkill.Name)
	}
	if len(unavailableSkill.MissingEnvironmentVariables) != 1 || unavailableSkill.MissingEnvironmentVariables[0] != "INTERNKIM_TOKEN" {
		t.Fatalf("the reason must name the variable, got %v", unavailableSkill.MissingEnvironmentVariables)
	}
	if unavailableSkill.Path == "" {
		t.Fatal("the inventory must say where the skill was read from")
	}
}

func TestTheSameSkillIsSelectableOnceItsVariableIsSet(t *testing.T) {
	t.Setenv("INTERNKIM_TOKEN", "ik_a-token")

	instructions := instructionsForSkillRequiringAToken(t)

	selected := false
	for _, skillName := range skillNames(instructions) {
		if skillName == "internkim-api" {
			selected = true
		}
	}
	if !selected {
		t.Fatal("a host that meets the requirement must get the skill")
	}
	if len(instructions.UnavailableSkills) != 0 {
		t.Fatalf("nothing is unavailable here, got %+v", instructions.UnavailableSkills)
	}
}

const skillRequiringACapabilityTool = `---
name: messages
description: Writes to a person on the messenger.
metadata:
  kim.intern.tool-references: "message_send"
---
Body.
`

func TestASkillNamingACapabilityToolIsSelectableFromTheLiveRegistry(t *testing.T) {
	deliveredSkillsPath := t.TempDir()
	skillDirectoryPath := filepath.Join(deliveredSkillsPath, "messages")
	if errorValue := os.MkdirAll(skillDirectoryPath, 0o755); errorValue != nil {
		t.Fatal(errorValue)
	}
	if errorValue := os.WriteFile(filepath.Join(skillDirectoryPath, "SKILL.md"), []byte(skillRequiringACapabilityTool), 0o600); errorValue != nil {
		t.Fatal(errorValue)
	}
	t.Setenv("BLUECLAW_BUNDLED_SKILLS_PATH", deliveredSkillsPath)
	server := httptest.NewServer(http.HandlerFunc(func(responseWriter http.ResponseWriter, _ *http.Request) {
		responseWriter.Header().Set("Content-Type", "application/json")
		_, _ = responseWriter.Write([]byte(`{"deviceCapabilities":[{"name":"message_send"}]}`))
	}))
	defer server.Close()
	runtimeConfiguration := config.RuntimeConfiguration{}
	runtimeConfiguration.Terminal.WorkspaceRootPath = t.TempDir()
	capabilityRegistry := agentruntime.NewCapabilityRegistry(
		capability.Client{Endpoint: server.URL, HTTPClient: server.Client()},
		nil,
	)

	instructions := loadAgentInstructions(runtimeConfiguration, capabilityRegistry)

	if len(instructions.UnavailableSkills) != 0 {
		t.Fatalf("expected the live registry to offer message_send, got %+v", instructions.UnavailableSkills)
	}
	selected := false
	for _, skillName := range skillNames(instructions) {
		if skillName == "messages" {
			selected = true
		}
	}
	if !selected {
		t.Fatalf("expected the skill in the prompt, got %v", skillNames(instructions))
	}
}