Replace agent.skills TEXT field with structured skill/skill_file/agent_skill tables. Skills are workspace-level entities with supporting files, reusable across agents via many-to-many bindings. Backend: migration 008, sqlc queries, CRUD handler, agent-skill junction, structured skill loading in task context snapshot. Daemon: meta skill injection via runtime-native config (.claude/CLAUDE.md for Claude, AGENTS.md for Codex) so agents discover .agent_context/ skills through their native mechanism. Lean prompt without inlined skill content. Frontend: Skills management page, agent Skills tab picker, SDK methods, TypeScript types, workspace store integration. Also removes auto-creation of init issues when creating agents. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
97 lines
2.4 KiB
Go
97 lines
2.4 KiB
Go
package daemon
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestNormalizeServerBaseURL(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
got, err := NormalizeServerBaseURL("ws://localhost:8080/ws")
|
|
if err != nil {
|
|
t.Fatalf("NormalizeServerBaseURL returned error: %v", err)
|
|
}
|
|
if got != "http://localhost:8080" {
|
|
t.Fatalf("expected http://localhost:8080, got %s", got)
|
|
}
|
|
}
|
|
|
|
func TestBuildPromptIncludesIssueAndContext(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
prompt := BuildPrompt(Task{
|
|
Context: TaskContext{
|
|
Issue: IssueContext{
|
|
Title: "Fix failing test",
|
|
Description: "Investigate and fix the test failure.",
|
|
AcceptanceCriteria: []string{"tests pass"},
|
|
},
|
|
Agent: AgentContext{
|
|
Name: "Local Codex",
|
|
Skills: []SkillData{
|
|
{Name: "Concise", Content: "Be concise."},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
// Lean prompt: issue + acceptance criteria only. No inlined skill content.
|
|
for _, want := range []string{
|
|
"Fix failing test",
|
|
"Investigate and fix the test failure.",
|
|
"tests pass",
|
|
} {
|
|
if !strings.Contains(prompt, want) {
|
|
t.Fatalf("prompt missing %q", want)
|
|
}
|
|
}
|
|
|
|
// Skills should NOT be inlined in the prompt (they're in runtime config).
|
|
for _, absent := range []string{"## Agent Skills", "Be concise."} {
|
|
if strings.Contains(prompt, absent) {
|
|
t.Fatalf("prompt should NOT contain %q (skills are in runtime config)", absent)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildPromptTruncatesLongDescription(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
longDesc := strings.Repeat("x", 300)
|
|
prompt := BuildPrompt(Task{
|
|
Context: TaskContext{
|
|
Issue: IssueContext{
|
|
Title: "Long desc",
|
|
Description: longDesc,
|
|
},
|
|
Agent: AgentContext{Name: "Test"},
|
|
},
|
|
})
|
|
|
|
if strings.Contains(prompt, longDesc) {
|
|
t.Fatal("expected long description to be truncated in prompt")
|
|
}
|
|
if !strings.Contains(prompt, "...") {
|
|
t.Fatal("expected truncation marker")
|
|
}
|
|
}
|
|
|
|
func TestIsWorkspaceNotFoundError(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
err := &requestError{
|
|
Method: http.MethodPost,
|
|
Path: "/api/daemon/register",
|
|
StatusCode: http.StatusNotFound,
|
|
Body: `{"error":"workspace not found"}`,
|
|
}
|
|
if !isWorkspaceNotFoundError(err) {
|
|
t.Fatal("expected workspace not found error to be recognized")
|
|
}
|
|
|
|
if isWorkspaceNotFoundError(&requestError{StatusCode: http.StatusInternalServerError, Body: `{"error":"workspace not found"}`}) {
|
|
t.Fatal("did not expect 500 to be treated as workspace not found")
|
|
}
|
|
}
|