Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
221 lines
7.1 KiB
Go
221 lines
7.1 KiB
Go
package runtimeprep
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
func TestTuttiAgentStableSkillsReuseRootAcrossSessions(t *testing.T) {
|
|
stateDir := t.TempDir()
|
|
storeRoot := filepath.Join(stateDir, "agent", "skill-bundles")
|
|
preparer := TuttiAgentPreparer{
|
|
ResolveAuthSource: func(context.Context, PrepareInput) (string, error) {
|
|
return "", nil
|
|
},
|
|
StableSkillBundleRoot: storeRoot,
|
|
StableSystemSkillBundleRoot: filepath.Join(stateDir, "agent", "system-skill-bundles"),
|
|
}
|
|
wantRoot := ""
|
|
for _, sessionID := range []string{"session-a", "session-b"} {
|
|
runtimeRoot := filepath.Join(stateDir, "agent", "runs", sessionID)
|
|
result, err := preparer.Prepare(t.Context(), ProviderPrepareInput{
|
|
PrepareInput: testResolvedInput(t, PrepareInput{
|
|
AgentSessionID: sessionID,
|
|
AgentTargetID: "local:tutti-agent",
|
|
Provider: "tutti-agent",
|
|
CLICommand: "tutti",
|
|
}),
|
|
RuntimeRoot: runtimeRoot,
|
|
Store: LocalStore{StateDir: stateDir},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Prepare(%s) error = %v", sessionID, err)
|
|
}
|
|
var roots []string
|
|
if err := json.Unmarshal(
|
|
[]byte(envValue(result.Env, "TUTTI_AGENT_EXTRA_SKILL_ROOTS_JSON")),
|
|
&roots,
|
|
); err != nil {
|
|
t.Fatalf("Prepare(%s) extra roots = %#v: %v", sessionID, result.Env, err)
|
|
}
|
|
if len(roots) != 1 || !filepath.IsAbs(roots[0]) {
|
|
t.Fatalf("Prepare(%s) roots = %#v, want one absolute root", sessionID, roots)
|
|
}
|
|
if root := envValue(result.Env, "TUTTI_AGENT_STABLE_SYSTEM_SKILLS_ROOT"); root != filepath.Join(stateDir, "agent", "system-skill-bundles") {
|
|
t.Fatalf("Prepare(%s) stable system root = %q", sessionID, root)
|
|
}
|
|
if wantRoot == "" {
|
|
wantRoot = roots[0]
|
|
} else if roots[0] != wantRoot {
|
|
t.Fatalf("stable roots differ: %q != %q", roots[0], wantRoot)
|
|
}
|
|
home := filepath.Join(runtimeRoot, "tutti-agent-home")
|
|
if _, err := os.Stat(filepath.Join(home, "skills")); !os.IsNotExist(err) {
|
|
t.Fatalf("Prepare(%s) created legacy skills root, err = %v", sessionID, err)
|
|
}
|
|
instructions, err := os.ReadFile(filepath.Join(home, "AGENTS.md"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(string(instructions), sessionID) {
|
|
t.Fatalf("Prepare(%s) embedded session id in AGENTS.md", sessionID)
|
|
}
|
|
}
|
|
if _, err := os.Stat(filepath.Join(wantRoot, tuttiSkillName, "SKILL.md")); err != nil {
|
|
t.Fatalf("stable tutti-cli skill missing: %v", err)
|
|
}
|
|
if !strings.Contains(filepath.ToSlash(wantRoot), "/agent/skill-bundles/v1/") {
|
|
t.Fatalf("stable root = %q, want versioned content-addressed path", wantRoot)
|
|
}
|
|
}
|
|
|
|
func TestTuttiAgentAddsVerifiedConnectorSkillRoots(t *testing.T) {
|
|
stateDir := t.TempDir()
|
|
connectorRoot := filepath.Join(stateDir, "connectors", "lark-cli", "skills")
|
|
if err := os.MkdirAll(connectorRoot, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
preparer := TuttiAgentPreparer{
|
|
ResolveAuthSource: func(context.Context, PrepareInput) (string, error) { return "", nil },
|
|
StableSkillBundleRoot: filepath.Join(stateDir, "agent", "skill-bundles"),
|
|
}
|
|
result, err := preparer.Prepare(t.Context(), ProviderPrepareInput{
|
|
PrepareInput: testResolvedInput(t, PrepareInput{
|
|
AgentSessionID: "session-a",
|
|
AgentTargetID: "local:tutti-agent",
|
|
Provider: "tutti-agent",
|
|
CLICommand: "tutti",
|
|
ConnectorRoutingHints: []ConnectorRoutingHint{
|
|
{ConnectorKey: "lark-cli", SkillRoot: connectorRoot},
|
|
{ConnectorKey: "lark-cli-copy", SkillRoot: connectorRoot},
|
|
},
|
|
}),
|
|
RuntimeRoot: filepath.Join(stateDir, "agent", "runs", "session-a"),
|
|
Store: LocalStore{StateDir: stateDir},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var roots []string
|
|
if err := json.Unmarshal([]byte(envValue(result.Env, "TUTTI_AGENT_EXTRA_SKILL_ROOTS_JSON")), &roots); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(roots) != 2 || roots[1] != connectorRoot {
|
|
t.Fatalf("extra roots = %#v, want stable provider root followed by deduplicated connector root", roots)
|
|
}
|
|
}
|
|
|
|
func TestConnectorSkillRootsRejectsRelativeOrSymlinkRoots(t *testing.T) {
|
|
if _, err := connectorSkillRoots([]ConnectorRoutingHint{{ConnectorKey: "demo", SkillRoot: "relative/skills"}}); err == nil {
|
|
t.Fatal("relative connector Skill root was accepted")
|
|
}
|
|
realRoot := t.TempDir()
|
|
link := filepath.Join(t.TempDir(), "skills")
|
|
if err := os.Symlink(realRoot, link); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := connectorSkillRoots([]ConnectorRoutingHint{{ConnectorKey: "demo", SkillRoot: link}}); err == nil {
|
|
t.Fatal("symlink connector Skill root was accepted")
|
|
}
|
|
}
|
|
|
|
func TestStableSkillBundleDigestChangesWithContentNotMapOrder(t *testing.T) {
|
|
specA := providerSkillSpec{
|
|
baseName: "sample",
|
|
skillID: "example/sample",
|
|
files: map[string]string{
|
|
"SKILL.md": "sample",
|
|
"references/guide.md": "guide",
|
|
},
|
|
}
|
|
specB := providerSkillSpec{
|
|
baseName: "sample",
|
|
skillID: "example/sample",
|
|
files: map[string]string{
|
|
"references/guide.md": "guide",
|
|
"SKILL.md": "sample",
|
|
},
|
|
}
|
|
first, _, err := canonicalizeStableProviderSkills("tutti-agent", []providerSkillSpec{specA})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second, _, err := canonicalizeStableProviderSkills("tutti-agent", []providerSkillSpec{specB})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
firstJSON, _ := json.Marshal(first)
|
|
secondJSON, _ := json.Marshal(second)
|
|
if string(firstJSON) != string(secondJSON) {
|
|
t.Fatalf("canonical bundle depends on map order:\n%s\n%s", firstJSON, secondJSON)
|
|
}
|
|
specB.files["SKILL.md"] = "changed"
|
|
changed, _, err := canonicalizeStableProviderSkills("tutti-agent", []providerSkillSpec{specB})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
changedJSON, _ := json.Marshal(changed)
|
|
if string(firstJSON) == string(changedJSON) {
|
|
t.Fatal("canonical bundle did not change with skill content")
|
|
}
|
|
}
|
|
|
|
func TestStableSkillBundleConcurrentMaterialization(t *testing.T) {
|
|
input := testResolvedInput(t, PrepareInput{
|
|
AgentSessionID: "session-a",
|
|
AgentTargetID: "local:tutti-agent",
|
|
Provider: "tutti-agent",
|
|
CLICommand: "tutti",
|
|
})
|
|
storeRoot := filepath.Join(t.TempDir(), "skill-bundles")
|
|
const workers = 8
|
|
roots := make(chan string, workers)
|
|
errors := make(chan error, workers)
|
|
var group sync.WaitGroup
|
|
for index := 0; index < workers; index++ {
|
|
group.Add(1)
|
|
go func() {
|
|
defer group.Done()
|
|
root, err := materializeStableProviderSkills(storeRoot, input)
|
|
if err != nil {
|
|
errors <- err
|
|
return
|
|
}
|
|
roots <- root
|
|
}()
|
|
}
|
|
group.Wait()
|
|
close(roots)
|
|
close(errors)
|
|
for err := range errors {
|
|
t.Fatalf("materializeStableProviderSkills() error = %v", err)
|
|
}
|
|
want := ""
|
|
for root := range roots {
|
|
if want == "" {
|
|
want = root
|
|
} else if root != want {
|
|
t.Fatalf("concurrent roots differ: %q != %q", root, want)
|
|
}
|
|
}
|
|
if _, err := os.Stat(filepath.Join(want, tuttiSkillName, "SKILL.md")); err != nil {
|
|
t.Fatalf("concurrent bundle is incomplete: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestStableSkillBundleRejectsEscapingSkillName(t *testing.T) {
|
|
_, _, err := canonicalizeStableProviderSkills("tutti-agent", []providerSkillSpec{{
|
|
baseName: "../escape",
|
|
skillID: "example/escape",
|
|
files: map[string]string{"SKILL.md": "escape"},
|
|
}})
|
|
if err == nil {
|
|
t.Fatal("canonicalizeStableProviderSkills() error = nil, want escaping name rejection")
|
|
}
|
|
}
|