chore(repo): reinitialize repository
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
package runtimecodex
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"inbox/internal/app/runtimeconfig"
|
||||
"inbox/internal/domain/role"
|
||||
)
|
||||
|
||||
const (
|
||||
WorkspaceDir = ".inbox/runtime-codex"
|
||||
ContainerDir = "/workspace/.inbox/runtime-codex"
|
||||
ContainerHome = "/root"
|
||||
ContainerCodex = "/root/.codex"
|
||||
configFilename = "config.toml"
|
||||
authFilename = "auth.json"
|
||||
)
|
||||
|
||||
type RoleCatalog interface {
|
||||
ListRoles(ctx context.Context) ([]role.Definition, error)
|
||||
}
|
||||
|
||||
type RoleResolver interface {
|
||||
ResolveRole(ctx context.Context, workspaceID, roleName string) (runtimeconfig.ResolvedRole, error)
|
||||
}
|
||||
|
||||
type Materializer struct {
|
||||
roles RoleCatalog
|
||||
resolver RoleResolver
|
||||
}
|
||||
|
||||
func NewMaterializer(roles RoleCatalog, resolver RoleResolver) *Materializer {
|
||||
return &Materializer{
|
||||
roles: roles,
|
||||
resolver: resolver,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Materializer) Sync(ctx context.Context, workspaceID, workspaceRoot string) error {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(workspaceRoot) == "" {
|
||||
return fmt.Errorf("workspace root is required")
|
||||
}
|
||||
items, err := m.roles.ListRoles(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("list roles for runtime codex config: %w", err)
|
||||
}
|
||||
|
||||
baseDir := filepath.Join(workspaceRoot, ".inbox")
|
||||
liveDir := filepath.Join(workspaceRoot, filepath.FromSlash(WorkspaceDir))
|
||||
if err := os.MkdirAll(baseDir, 0755); err != nil {
|
||||
return fmt.Errorf("ensure runtime codex base dir: %w", err)
|
||||
}
|
||||
|
||||
tmpDir, err := os.MkdirTemp(baseDir, "runtime-codex-")
|
||||
if err != nil {
|
||||
return fmt.Errorf("create runtime codex temp dir: %w", err)
|
||||
}
|
||||
cleanup := func() { _ = os.RemoveAll(tmpDir) }
|
||||
|
||||
for _, item := range items {
|
||||
item = role.NormalizeDefinition(item)
|
||||
if !item.IsEnabled || item.ExecutorKind != role.ExecutorKindCodex {
|
||||
continue
|
||||
}
|
||||
resolved, err := m.resolver.ResolveRole(ctx, workspaceID, item.Name)
|
||||
if err != nil {
|
||||
cleanup()
|
||||
return fmt.Errorf("resolve role %q for runtime codex config: %w", item.Name, err)
|
||||
}
|
||||
if _, err := WriteResolvedRoleHome(tmpDir, resolved); err != nil {
|
||||
cleanup()
|
||||
return fmt.Errorf("write runtime codex home for %q: %w", item.Name, err)
|
||||
}
|
||||
if err := writeResolvedRoleSkills(tmpDir, resolved); err != nil {
|
||||
cleanup()
|
||||
return fmt.Errorf("write runtime codex skills for %q: %w", item.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
_ = os.RemoveAll(liveDir)
|
||||
if err := os.Rename(tmpDir, liveDir); err != nil {
|
||||
cleanup()
|
||||
return fmt.Errorf("publish runtime codex dir: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ContainerHomeDir(roleName string) string {
|
||||
return path.Join(ContainerDir, sanitizeRolePath(roleName))
|
||||
}
|
||||
|
||||
func ContainerUserHomeDir() string {
|
||||
return ContainerHome
|
||||
}
|
||||
|
||||
func ContainerCodexDir() string {
|
||||
return ContainerCodex
|
||||
}
|
||||
|
||||
func WorkspaceHomeDir(workspaceRoot, roleName string) string {
|
||||
return filepath.Join(workspaceRoot, filepath.FromSlash(WorkspaceDir), sanitizeRolePath(roleName))
|
||||
}
|
||||
|
||||
func WorkspaceCodexDir(workspaceRoot, roleName string) string {
|
||||
return filepath.Join(WorkspaceHomeDir(workspaceRoot, roleName), ".codex")
|
||||
}
|
||||
|
||||
func WriteResolvedRoleHome(root string, resolved runtimeconfig.ResolvedRole) (string, error) {
|
||||
roleHome := filepath.Join(root, sanitizeRolePath(resolved.Role.Name))
|
||||
codexDir := filepath.Join(roleHome, ".codex")
|
||||
if err := os.MkdirAll(codexDir, 0755); err != nil {
|
||||
return "", fmt.Errorf("create runtime codex dir for %q: %w", resolved.Role.Name, err)
|
||||
}
|
||||
|
||||
configText := normalizeConfigTOML(resolved.Config.ConfigTOML)
|
||||
if err := os.WriteFile(filepath.Join(codexDir, configFilename), []byte(configText), 0644); err != nil {
|
||||
return "", fmt.Errorf("write config.toml for %q: %w", resolved.Role.Name, err)
|
||||
}
|
||||
|
||||
authBytes, err := normalizeAuthJSON(resolved.Config.AuthJSON)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("normalize auth.json for %q: %w", resolved.Role.Name, err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(codexDir, authFilename), authBytes, 0600); err != nil {
|
||||
return "", fmt.Errorf("write auth.json for %q: %w", resolved.Role.Name, err)
|
||||
}
|
||||
return roleHome, nil
|
||||
}
|
||||
|
||||
func HostContainerRuntimeRoot(projectRoot, workspaceID string) string {
|
||||
return filepath.Join(projectRoot, ".runtime", "container-codex", sanitizeRolePath(workspaceID))
|
||||
}
|
||||
|
||||
func HostContainerCodexDir(projectRoot, workspaceID, roleName string) string {
|
||||
return WorkspaceCodexDir(HostContainerRuntimeRoot(projectRoot, workspaceID), roleName)
|
||||
}
|
||||
|
||||
func HostLeaderRuntimeRoot(projectRoot, workspaceID string) string {
|
||||
return filepath.Join(projectRoot, ".runtime", "leader-codex", sanitizeRolePath(workspaceID))
|
||||
}
|
||||
|
||||
func HostLeaderHomeDir(projectRoot, workspaceID, roleName string) string {
|
||||
return filepath.Join(HostLeaderRuntimeRoot(projectRoot, workspaceID), sanitizeRolePath(roleName))
|
||||
}
|
||||
|
||||
func HostLeaderCodexDir(projectRoot, workspaceID, roleName string) string {
|
||||
return filepath.Join(HostLeaderHomeDir(projectRoot, workspaceID, roleName), ".codex")
|
||||
}
|
||||
|
||||
func sanitizeRolePath(value string) string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return "role"
|
||||
}
|
||||
replacer := strings.NewReplacer(
|
||||
"/", "-",
|
||||
"\\", "-",
|
||||
":", "-",
|
||||
"*", "-",
|
||||
"?", "-",
|
||||
"\"", "-",
|
||||
"<", "-",
|
||||
">", "-",
|
||||
"|", "-",
|
||||
" ", "-",
|
||||
)
|
||||
value = replacer.Replace(value)
|
||||
value = strings.Trim(value, ".-")
|
||||
if value == "" {
|
||||
return "role"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func normalizeConfigTOML(value string) string {
|
||||
return strings.TrimSpace(value)
|
||||
}
|
||||
|
||||
func normalizeAuthJSON(value string) ([]byte, error) {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
value = "{}"
|
||||
}
|
||||
if !json.Valid([]byte(value)) {
|
||||
return nil, fmt.Errorf("invalid json")
|
||||
}
|
||||
|
||||
var decoded any
|
||||
if err := json.Unmarshal([]byte(value), &decoded); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.MarshalIndent(decoded, "", " ")
|
||||
}
|
||||
|
||||
func writeResolvedRoleSkills(root string, resolved runtimeconfig.ResolvedRole) error {
|
||||
if len(resolved.Skills) == 0 {
|
||||
return nil
|
||||
}
|
||||
codexDir := filepath.Join(root, sanitizeRolePath(resolved.Role.Name), ".codex")
|
||||
skillsRoot := filepath.Join(codexDir, "skills")
|
||||
for _, item := range resolved.Skills {
|
||||
skillKey := strings.TrimSpace(item.Skill.SkillKey)
|
||||
if skillKey == "" {
|
||||
skillKey = sanitizeRolePath(item.Skill.ID)
|
||||
}
|
||||
if skillKey == "" {
|
||||
return fmt.Errorf("resolved skill key is required")
|
||||
}
|
||||
targetDir := filepath.Join(skillsRoot, sanitizeRolePath(skillKey))
|
||||
if err := os.MkdirAll(targetDir, 0755); err != nil {
|
||||
return fmt.Errorf("create skill dir %s: %w", targetDir, err)
|
||||
}
|
||||
skillBody := strings.TrimSpace(item.Skill.ContentMarkdown)
|
||||
if skillBody == "" {
|
||||
return fmt.Errorf("skill %q content_markdown is required", skillKey)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(targetDir, "SKILL.md"), []byte(skillBody), 0644); err != nil {
|
||||
return fmt.Errorf("write SKILL.md for %q: %w", skillKey, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user