package ext import ( "context" "os" "path/filepath" "testing" "time" "agent-native/runtime/ext/pkg" ) func TestExtensionRegistersAndHooks(t *testing.T) { dir := t.TempDir() script := filepath.Join(dir, "ext.sh") body := `#!/bin/sh while IFS= read -r line; do case "$line" in *'"type":"init"'*) echo '{"type":"register_tool","name":"ping","description":"p","parameters":{"type":"object"}}' echo '{"type":"register_command","name":"hi"}' echo '{"type":"register_provider","name":"local","protocol":"openai-chat","baseURL":"http://127.0.0.1:9"}' echo '{"type":"ui_request","id":"u1","message":"choose"}' ;; *'"type":"tool_call"'*) echo '{"type":"tool_result","id":"ping","content":"pong"}' ;; *'"type":"event"'*) echo '{"type":"hook_result","id":"ping:tool_call","block":true,"reason":"no"}' ;; esac done ` if err := os.WriteFile(script, []byte(body), 0o755); err != nil { t.Fatal(err) } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() proc, err := Start(ctx, []string{"/bin/sh", script}) if err != nil { t.Fatal(err) } defer proc.Close() deadline := time.Now().Add(2 * time.Second) for len(proc.Tools()) == 0 && time.Now().Before(deadline) { time.Sleep(20 * time.Millisecond) } tools := proc.Tools() if len(tools) != 1 || tools[0].Name() != "ping" { t.Fatalf("tools %+v", tools) } res, err := tools[0].Execute(ctx, []byte(`{}`)) if err != nil || res.Content != "pong" { t.Fatalf("tool: %+v %v", res, err) } block, reason := proc.Hook(ctx, "tool_call", "ping", []byte(`{}`)) if !block || reason != "no" { t.Fatalf("hook %v %s", block, reason) } if len(proc.Commands()) != 1 || len(proc.Providers()) != 1 { t.Fatalf("commands %v providers %v", proc.Commands(), proc.Providers()) } select { case ui := <-proc.UI: if ui.Message != "choose" { t.Fatal(ui) } case <-time.After(time.Second): t.Fatal("missing ui request") } } func TestPackageInstallListConfigRemove(t *testing.T) { src := t.TempDir() if err := os.WriteFile(filepath.Join(src, "agent-native.json"), []byte(`{"name":"demo","extensions":["./ext.sh"]}`), 0o644); err != nil { t.Fatal(err) } mgr := pkg.Manager{Dir: t.TempDir()} manifest, err := mgr.Install(src) if err != nil || manifest.Name != "demo" { t.Fatalf("%+v %v", manifest, err) } if err := mgr.Config("demo", "source", src); err != nil { t.Fatal(err) } list, err := mgr.List() if err != nil || len(list) != 1 || list[0].Config["source"] != src { t.Fatalf("list %+v %v", list, err) } if _, err := mgr.Update("demo"); err != nil { t.Fatal(err) } if err := mgr.Remove("demo"); err != nil { t.Fatal(err) } list, _ = mgr.List() if len(list) != 0 { t.Fatalf("still installed: %+v", list) } }