Files
agent-radar 2ed3415fb7
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
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

656 lines
20 KiB
Go

package runtimeprep
import (
"bytes"
"embed"
"errors"
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"text/template"
"unicode"
"unicode/utf8"
)
const tuttiSkillName = "tutti-cli"
const issueManagerSkillName = "issue-manager"
const workspaceAppSkillName = "workspace-app"
const referenceSkillName = "reference"
const browserUseSkillName = "browser-use"
const computerUseSkillName = "computer-use"
const tuttiHandoffSkillName = "tutti-handoff"
const commandGuideReferencePath = "command-guide.md"
const tuttiModelAllocationSkillName = "tutti-model-allocation"
const tuttiModelAllocationReferencePath = "references/model-tiers.md"
const connectorRoutingIndexMaxRunes = 640
//go:embed skill_templates/*.md policy_templates/*.md
var providerSkillTemplates embed.FS
type providerSkillSpec struct {
baseName string
files map[string]string
skillID string
}
func tuttiCLISkill(input PrepareInput) (string, error) {
return renderProviderSkillTemplate("skill_templates/tutti-cli.md", input, nil)
}
func tuttiHandoffSkill(input PrepareInput) (string, error) {
return renderProviderSkillTemplate("skill_templates/tutti-handoff.md", input, nil)
}
func tuttiModelAllocationSkill(input PrepareInput) (string, error) {
return renderProviderSkillTemplate("skill_templates/tutti-model-allocation.md", input, nil)
}
func tuttiModelAllocationReference(input PrepareInput) (string, error) {
return renderProviderSkillTemplate("skill_templates/tutti-model-allocation-model-tiers.md", input, nil)
}
func issueManagerSkill(input PrepareInput) (string, error) {
return renderProviderSkillTemplate("skill_templates/issue-manager.md", input, nil)
}
func workspaceAppSkill(input PrepareInput) (string, error) {
return renderProviderSkillTemplate("skill_templates/workspace-app.md", input, nil)
}
func referenceSkill(input PrepareInput) (string, error) {
return renderProviderSkillTemplate("skill_templates/reference.md", input, nil)
}
func browserUseSkill(input PrepareInput) (string, error) {
return renderProviderSkillTemplate("skill_templates/browser-use.md", input, nil)
}
func computerUseSkill(input PrepareInput) (string, error) {
return renderProviderSkillTemplate("skill_templates/computer-use.md", input, nil)
}
type runtimeTemplateData struct {
PrepareInput
HostFacts HostFacts
CommandFamilies []string
OutputModes []string
ProfileIntro string
ProfileTitle string
ConnectorRoutingIndex string
EnabledConnectorsIndex string
}
func renderProviderSkillTemplate(path string, input PrepareInput, replacements map[string]string) (string, error) {
content, err := providerSkillTemplates.ReadFile(path)
if err != nil {
return "", fmt.Errorf("read provider skill template %s: %w", path, err)
}
return renderRuntimeTemplate(path, string(content), input, replacements)
}
func renderPolicyTemplate(path string, input PrepareInput) (string, error) {
rendered, err := renderProviderSkillTemplate(path, input, nil)
return strings.TrimSpace(rendered), err
}
func renderRuntimeTemplate(name string, content string, input PrepareInput, replacements map[string]string) (string, error) {
funcs := template.FuncMap{
"args": commandTemplateArguments,
"command": templateCommand(input),
"has": templateHas(input),
"hasAll": templateHasAll(input),
"hasFamily": templateHasFamily(input),
"hasInput": templateHasInput(input),
"inputValues": templateInputValues(input),
"path": templateCommandPath(input),
}
for placeholder, replacement := range replacements {
name := strings.TrimSuffix(strings.TrimPrefix(placeholder, "{{"), "}}")
value := replacement
funcs[name] = func() string { return value }
}
parsed, err := template.New(name).Option("missingkey=error").Funcs(funcs).Parse(content)
if err != nil {
return "", fmt.Errorf("parse runtime template %s: %w", name, err)
}
resolver := input.commandCapabilities
data := runtimeTemplateData{
PrepareInput: input,
HostFacts: resolvedHostFacts(input),
ProfileIntro: resolvedProfileIntro(input),
ProfileTitle: resolvedProfileTitle(input),
ConnectorRoutingIndex: connectorRoutingIndex(input.ConnectorRoutingHints),
EnabledConnectorsIndex: enabledConnectorsIndex(input.EnabledConnectors),
}
data.CLICommand = normalizeCLICommandName(input.CLICommand)
if resolver != nil {
data.CommandFamilies = resolver.Families()
data.OutputModes = resolver.OutputModes()
}
var rendered bytes.Buffer
if err := parsed.Execute(&rendered, data); err != nil {
return "", fmt.Errorf("render runtime template %s: %w", name, err)
}
return rendered.String(), nil
}
// ConnectorRoutingIndex renders the bounded connector alias index. Session
// preparation templates and turn-level routing updates share this projection
// so both surfaces stay byte-identical for the same routing hints.
func ConnectorRoutingIndex(hints []ConnectorRoutingHint) string {
return connectorRoutingIndex(hints)
}
func enabledConnectorsIndex(keys []string) string {
if len(keys) == 0 {
return "none"
}
unique := make([]string, 0, len(keys))
seen := make(map[string]struct{}, len(keys))
for _, key := range keys {
key = strings.TrimSpace(key)
if key == "" {
continue
}
if _, duplicate := seen[key]; duplicate {
continue
}
seen[key] = struct{}{}
unique = append(unique, key)
}
if len(unique) == 0 {
return "none"
}
sort.Strings(unique)
return strings.Join(unique, ", ")
}
func connectorRoutingIndex(hints []ConnectorRoutingHint) string {
if len(hints) == 0 {
return ""
}
ordered := append([]ConnectorRoutingHint(nil), hints...)
sort.SliceStable(ordered, func(left, right int) bool {
return strings.TrimSpace(ordered[left].ConnectorKey) < strings.TrimSpace(ordered[right].ConnectorKey)
})
segments := make([]string, 0, len(ordered))
seenConnectors := make(map[string]struct{}, len(ordered))
for _, hint := range ordered {
key := strings.TrimSpace(hint.ConnectorKey)
if key == "" || !safeConnectorRoutingPromptValue(key) {
continue
}
if _, duplicate := seenConnectors[key]; duplicate {
continue
}
seenConnectors[key] = struct{}{}
seen := map[string]struct{}{strings.ToLower(key): {}}
values := make([]string, 0, len(hint.Aliases)+1)
for _, value := range append([]string{hint.DisplayName}, hint.Aliases...) {
value = strings.TrimSpace(value)
folded := strings.ToLower(value)
if value == "" || !safeConnectorRoutingPromptValue(value) {
continue
}
if _, duplicate := seen[folded]; duplicate {
continue
}
seen[folded] = struct{}{}
values = append(values, value)
}
candidate := strings.Join(append(append([]string(nil), segments...), key), ";")
if utf8.RuneCountInString(candidate) > connectorRoutingIndexMaxRunes {
break
}
segments = append(segments, key)
accepted := make([]string, 0, len(values))
for _, value := range values {
candidateValues := append(append([]string(nil), accepted...), value)
candidateSegment := key + "=" + strings.Join(candidateValues, "|")
candidate = strings.Join(append(append([]string(nil), segments[:len(segments)-1]...), candidateSegment), ";")
if utf8.RuneCountInString(candidate) > connectorRoutingIndexMaxRunes {
break
}
accepted = candidateValues
segments[len(segments)-1] = candidateSegment
}
}
if len(segments) == 0 {
return ""
}
return strings.Join(segments, ";")
}
func safeConnectorRoutingPromptValue(value string) bool {
if len([]rune(value)) > 128 {
return false
}
for _, character := range value {
if unicode.IsLetter(character) || unicode.IsNumber(character) || unicode.IsMark(character) || character == ' ' ||
strings.ContainsRune("-_.+&/()", character) {
continue
}
return false
}
return true
}
func templateCommand(input PrepareInput) func(string, ...commandArguments) (string, error) {
return func(id string, args ...commandArguments) (string, error) {
if input.commandCapabilities == nil {
return "", fmt.Errorf("agent command %q cannot be rendered without a command snapshot", id)
}
return input.commandCapabilities.Command(id, args...)
}
}
func templateCommandPath(input PrepareInput) func(string) (string, error) {
return func(id string) (string, error) {
if input.commandCapabilities == nil {
return "", fmt.Errorf("agent command %q cannot be rendered without a command snapshot", id)
}
return input.commandCapabilities.Path(id)
}
}
func templateHas(input PrepareInput) func(string) bool {
return func(id string) bool {
return input.commandCapabilities != nil && input.commandCapabilities.Has(id)
}
}
func templateHasAll(input PrepareInput) func(...string) bool {
return func(ids ...string) bool {
return input.commandCapabilities != nil && input.commandCapabilities.HasAll(ids...)
}
}
func templateHasFamily(input PrepareInput) func(string) bool {
return func(family string) bool {
return input.commandCapabilities != nil && input.commandCapabilities.HasFamily(family)
}
}
func templateHasInput(input PrepareInput) func(string, string) bool {
return func(id string, name string) bool {
return input.commandCapabilities != nil && input.commandCapabilities.HasInput(id, name)
}
}
func templateInputValues(input PrepareInput) func(string, string) []string {
return func(id string, name string) []string {
if input.commandCapabilities == nil {
return nil
}
return input.commandCapabilities.InputValues(id, name)
}
}
func providerSkills(input PrepareInput) ([]providerSkillSpec, error) {
if input.SkipSkills {
return nil, nil
}
if input.resolved == nil {
return nil, errors.New("provider skills require resolved runtime capabilities")
}
skills := make([]providerSkillSpec, 0, len(input.resolved.Skills))
for _, skill := range input.resolved.Skills {
if !skillSupportsProvider(skill, input.Provider) {
continue
}
id := strings.TrimSpace(skill.ID)
if id == "" {
id = "tutti/" + strings.TrimSpace(skill.Name)
}
skills = append(skills, providerSkillSpec{
baseName: strings.TrimSpace(skill.Name),
files: copySkillBundleFiles(skill.Files),
skillID: id,
})
}
return skills, nil
}
func installProviderNativeSkills(root string, input PrepareInput) ([]string, error) {
skills, err := providerSkills(input)
if err != nil {
return nil, err
}
return installProviderNativeSkillSpecs(root, skills)
}
func installProviderNativeSkillsStable(root string, input PrepareInput) ([]string, error) {
skills, err := providerSkills(input)
if err != nil {
return nil, err
}
return installProviderNativeSkillSpecsStable(root, skills)
}
func installProviderNativeSkillsSessionScoped(root string, input PrepareInput) ([]string, error) {
return installProviderNativeSkillsSessionScopedWithReservedRoots(root, nil, input)
}
func installProviderNativeSkillsSessionScopedWithReservedRoots(
root string,
reservedRoots []string,
input PrepareInput,
) ([]string, error) {
if input.SkipSkills {
return nil, nil
}
skills, err := providerSkills(input)
if err != nil {
return nil, err
}
skillPaths, err := installProviderNativeSkillSpecsStableWithReservedRoots(root, reservedRoots, skills)
if err != nil {
return nil, err
}
if err := removeStaleManagedProviderSkills(root, skillPaths); err != nil {
return nil, err
}
return skillPaths, nil
}
func renderProviderPromptContext(policy string, skillPaths []string) string {
sections := make([]string, 0, 2)
if policy = strings.TrimSpace(policy); policy != "" {
sections = append(sections, policy)
}
var catalog strings.Builder
for _, skillPath := range skillPaths {
skillPath = filepath.Clean(strings.TrimSpace(skillPath))
if skillPath == "." || skillPath == "" {
continue
}
if catalog.Len() == 0 {
catalog.WriteString("## Available Skills\n\n")
catalog.WriteString("These session-injected Skills are available now. Their exact names and materialized files are authoritative:\n\n")
}
fmt.Fprintf(&catalog, "- `$%s`: `%s`\n", filepath.Base(skillPath), filepath.Join(skillPath, "SKILL.md"))
}
if catalog.Len() > 0 {
sections = append(sections, strings.TrimSpace(catalog.String()))
}
return strings.Join(sections, "\n\n")
}
func renderProviderSkillBundle(input PrepareInput) (SkillBundle, error) {
skills, err := providerSkills(input)
if err != nil {
return SkillBundle{}, err
}
records := make([]SkillMaterializationRecord, 0, len(skills))
for _, skill := range skills {
records = append(records, providerSkillSpecRecord(skill))
}
prompt, err := recommendedSystemPrompt(input)
if err != nil {
return SkillBundle{}, err
}
return SkillBundle{
SchemaVersion: 2,
AgentTargetID: strings.TrimSpace(input.AgentTargetID),
Provider: strings.TrimSpace(input.Provider),
AgentSessionID: strings.TrimSpace(input.AgentSessionID),
CLICommand: normalizeCLICommandName(input.CLICommand),
RecommendedSystemPrompt: prompt,
Skills: records,
}, nil
}
func recommendedSystemPrompt(input PrepareInput) (*RecommendedSystemPrompt, error) {
rendered, err := tuttiSkillBundleRecommendedPolicy(input)
if err != nil {
return nil, err
}
content := strings.TrimSpace(rendered)
if content == "" {
return nil, nil
}
return &RecommendedSystemPrompt{
Format: "text/markdown",
Content: content,
}, nil
}
func providerSkillSpecRecord(spec providerSkillSpec) SkillMaterializationRecord {
files := make([]SkillMaterializationFile, 0, len(spec.files))
paths := make([]string, 0, len(spec.files))
for path := range spec.files {
if path == "SKILL.md" {
continue
}
paths = append(paths, path)
}
sort.Strings(paths)
for _, path := range paths {
files = append(files, SkillMaterializationFile{
Content: spec.files[path],
Path: path,
})
}
skillID := strings.TrimSpace(spec.skillID)
if skillID == "" {
skillID = "tutti/" + spec.baseName
}
return SkillMaterializationRecord{
Content: spec.files["SKILL.md"],
Files: files,
SkillID: skillID,
Slug: spec.baseName,
DeliveryMode: "materialized-files",
}
}
func installProviderNativeSkillSpecs(root string, skills []providerSkillSpec) ([]string, error) {
root = strings.TrimSpace(root)
if root == "" {
return nil, fmt.Errorf("provider skill root is required")
}
if err := os.MkdirAll(root, 0o755); err != nil {
return nil, fmt.Errorf("create provider skill root: %w", err)
}
skillPaths := make([]string, 0, len(skills))
for _, spec := range skills {
skillName, err := allocateSkillName(root, spec.baseName)
if err != nil {
return nil, err
}
skillPath := filepath.Join(root, skillName)
if err := installProviderSkillFiles(skillPath, spec); err != nil {
return nil, err
}
skillPaths = append(skillPaths, skillPath)
}
return skillPaths, nil
}
func installProviderNativeSkillSpecsStable(root string, skills []providerSkillSpec) ([]string, error) {
return installProviderNativeSkillSpecsStableWithReservedRoots(root, nil, skills)
}
func installProviderNativeSkillSpecsStableWithReservedRoots(
root string,
reservedRoots []string,
skills []providerSkillSpec,
) ([]string, error) {
root = strings.TrimSpace(root)
if root == "" {
return nil, fmt.Errorf("provider skill root is required")
}
if err := os.MkdirAll(root, 0o755); err != nil {
return nil, fmt.Errorf("create provider skill root: %w", err)
}
skillPaths := make([]string, 0, len(skills))
for _, spec := range skills {
skillName, err := stableManagedSkillName(root, reservedRoots, spec.baseName)
if err != nil {
return nil, err
}
skillPath := filepath.Join(root, skillName)
if err := os.RemoveAll(skillPath); err != nil {
return nil, fmt.Errorf("replace tutti provider skill: %w", err)
}
if err := installProviderSkillFiles(skillPath, spec); err != nil {
return nil, err
}
skillPaths = append(skillPaths, skillPath)
}
return skillPaths, nil
}
func removeStaleManagedProviderSkills(root string, currentPaths []string) error {
keep := make(map[string]struct{}, len(currentPaths))
for _, skillPath := range currentPaths {
keep[filepath.Clean(skillPath)] = struct{}{}
}
entries, err := os.ReadDir(root)
if err != nil {
return fmt.Errorf("read session provider skill root: %w", err)
}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
path := filepath.Join(root, entry.Name())
if _, current := keep[filepath.Clean(path)]; current || !providerSkillDirectoryLooksManaged(path) {
continue
}
if err := os.RemoveAll(path); err != nil {
return fmt.Errorf("remove stale tutti provider skill: %w", err)
}
}
return nil
}
func stableManagedSkillName(root string, reservedRoots []string, baseName string) (string, error) {
baseName = strings.TrimSpace(baseName)
if baseName == "" {
return "", fmt.Errorf("provider skill name is required")
}
candidates := []string{baseName, baseName + "-tutti"}
for index := 2; index <= 99; index++ {
candidates = append(candidates, fmt.Sprintf("%s-tutti-%d", baseName, index))
}
for _, candidate := range candidates {
reserved := false
for _, reservedRoot := range reservedRoots {
if _, err := os.Stat(filepath.Join(reservedRoot, candidate)); err == nil {
reserved = true
break
} else if !os.IsNotExist(err) {
return "", fmt.Errorf("inspect reserved provider skill directory: %w", err)
}
}
if reserved {
continue
}
path := filepath.Join(root, candidate)
info, err := os.Stat(path)
if err == nil {
if info.IsDir() && providerSkillDirectoryLooksManaged(path) {
return candidate, nil
}
continue
}
if !os.IsNotExist(err) {
return "", fmt.Errorf("inspect provider skill directory: %w", err)
}
return candidate, nil
}
return "", fmt.Errorf("allocate stable provider skill directory: exhausted names for %s", baseName)
}
func providerSkillDirectoryLooksManaged(path string) bool {
if _, err := os.Stat(filepath.Join(path, ".tutti-managed-skill")); err == nil {
return true
}
return false
}
func copySkillBundleFiles(files map[string]string) map[string]string {
if len(files) == 0 {
return nil
}
copy := make(map[string]string, len(files))
for path, content := range files {
copy[path] = content
}
return copy
}
func installProviderSkillFiles(skillPath string, spec providerSkillSpec) error {
if err := os.MkdirAll(skillPath, 0o755); err != nil {
return fmt.Errorf("create tutti provider skill directory: %w", err)
}
if _, ok := spec.files["SKILL.md"]; !ok {
return fmt.Errorf("provider skill %s missing SKILL.md", spec.baseName)
}
for relativePath, content := range spec.files {
cleanPath, err := cleanProviderSkillFilePath(relativePath)
if err != nil {
return err
}
targetPath := filepath.Join(skillPath, filepath.FromSlash(cleanPath))
if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil {
return fmt.Errorf("create tutti provider skill file directory: %w", err)
}
if err := os.WriteFile(targetPath, []byte(content), 0o644); err != nil {
return fmt.Errorf("write tutti provider skill: %w", err)
}
}
if err := os.WriteFile(filepath.Join(skillPath, ".tutti-managed-skill"), []byte(spec.skillID+"\n"), 0o644); err != nil {
return fmt.Errorf("write tutti provider skill marker: %w", err)
}
return nil
}
func cleanProviderSkillFilePath(path string) (string, error) {
trimmed := strings.TrimSpace(filepath.ToSlash(path))
if trimmed == "" || strings.HasPrefix(trimmed, "/") {
return "", fmt.Errorf("provider skill file path is invalid: %q", path)
}
cleaned := filepath.ToSlash(filepath.Clean(trimmed))
if cleaned == "." || cleaned == ".." || strings.HasPrefix(cleaned, "../") {
return "", fmt.Errorf("provider skill file path escapes skill directory: %q", path)
}
return cleaned, nil
}
func allocateSkillName(root string, baseName string) (string, error) {
baseName = strings.TrimSpace(baseName)
if baseName == "" {
return "", fmt.Errorf("provider skill name is required")
}
candidates := []string{baseName, baseName + "-tutti"}
for index := 2; index <= 99; index++ {
candidates = append(candidates, fmt.Sprintf("%s-tutti-%d", baseName, index))
}
for _, candidate := range candidates {
if _, err := os.Stat(filepath.Join(root, candidate)); err == nil {
continue
} else if !os.IsNotExist(err) {
return "", fmt.Errorf("inspect provider skill directory: %w", err)
}
return candidate, nil
}
return "", fmt.Errorf("allocate provider skill directory: exhausted names for %s", baseName)
}
func providerSkillRoot(cwd string, provider string) string {
switch strings.TrimSpace(provider) {
case "openclaw", "open-claw":
return filepath.Join(cwd, ".openclaw", "skills")
case "nexight", "tutti":
return filepath.Join(cwd, ".nexight", "skills")
case "hermes", "hermes-agent":
return filepath.Join(cwd, ".agent_context", "skills")
default:
return ""
}
}