package sqlite import ( "context" "database/sql" "errors" "fmt" "inbox/internal/domain/role" ) type builtinRoleSeed struct { Definition role.Definition SystemPrompt string } var builtinRoleSeeds = []builtinRoleSeed{ { Definition: role.Definition{ Name: "user", Title: "User", ExecutorKind: role.ExecutorKindHuman, Description: "Interactive human participant.", IsEnabled: false, IsBuiltin: true, SortOrder: -100, }, SystemPrompt: `你是 user。 这个角色代表真实用户,不会由 AI runtime 自动执行。`, }, { Definition: role.Definition{ Name: "leader", Title: "Leader", ExecutorKind: role.ExecutorKindCodex, Description: "Runs on the host, talks to the user, maintains the task graph, and orchestrates execution.", IsEnabled: true, IsBuiltin: true, SortOrder: 100, }, SystemPrompt: `你是 leader。 你运行在宿主机上,是当前主题唯一的编排者。你负责与用户沟通、澄清目标、维护 task graph、启动执行,并汇总最终结果。lane 由系统从任务图自动派生。 规则: - 先理解用户目标,再拆分任务;不要直接跳过澄清。 - 所有 task 都必须明确目标、依赖、验收标准。 - worker 的问题和总结都只作为编排输入,不要把它们直接当作完成证明。 - worker 只能向你升级阻塞;是否打扰用户由你决定。 - 你可以创建、更新、启动、停止 lane 与 task,但不要代替 worker 在容器里完成实现。`, }, { Definition: role.Definition{ Name: "worker", Title: "Worker", ExecutorKind: role.ExecutorKindCodex, Description: "Runs inside a per-lane container, executes tasks in that lane's worktree, and reports back to the leader.", IsEnabled: true, IsBuiltin: true, SortOrder: 200, }, SystemPrompt: `你是 worker。 你运行在一个 lane 对应的独立 worktree 与容器中。你只负责执行当前被分派的 task,并通过 inbox 向 leader 汇报进度、结果或阻塞。 规则: - 只处理当前 task,不要自行扩展范围。 - 在开始前阅读 task、验收标准和 lane 上下文。 - 遇到阻塞时,先通过 inbox 向 leader 发一条具体问题,再结束当前 task,并在总结里写清阻塞点与所需决策。 - 行为变化时补充必要的验证与测试。`, }, } func (s *Store) ensureBuiltinRoles(ctx context.Context) error { tx, err := s.db.BeginTx(ctx, nil) if err != nil { return fmt.Errorf("begin builtin role seed: %w", err) } defer tx.Rollback() now := s.now() for _, seed := range builtinRoleSeeds { if err := seedBuiltinRoleTx(ctx, tx, now, seed); err != nil { return err } } if err := tx.Commit(); err != nil { return fmt.Errorf("commit builtin role seed: %w", err) } return nil } func seedBuiltinRoleTx(ctx context.Context, tx *sql.Tx, now string, seed builtinRoleSeed) error { if _, err := getRoleTx(ctx, tx, seed.Definition.Name); err != nil { if !errors.Is(err, sql.ErrNoRows) { return err } if err := insertBuiltinRoleTx(ctx, tx, seed, now); err != nil { return fmt.Errorf("insert builtin role %q: %w", seed.Definition.Name, err) } } if _, err := getRolePromptTx(ctx, tx, seed.Definition.Name, "", role.PromptSystem); err != nil { if !errors.Is(err, sql.ErrNoRows) { return err } if _, err := tx.ExecContext(ctx, ` INSERT INTO role_prompts(id, role_name, workspace_id, prompt_kind, content_markdown, version, updated_by, created_at, updated_at) VALUES(?, ?, NULL, ?, ?, 1, 'builtin-seed', ?, ?) `, "builtin-role-prompt-"+seed.Definition.Name, seed.Definition.Name, string(role.PromptSystem), seed.SystemPrompt, now, now, ); err != nil { return fmt.Errorf("insert builtin role prompt %q: %w", seed.Definition.Name, err) } } return nil } func insertBuiltinRoleTx(ctx context.Context, tx *sql.Tx, seed builtinRoleSeed, now string) error { _, err := tx.ExecContext(ctx, ` INSERT INTO roles(name, title, executor_kind, description, is_enabled, is_builtin, sort_order, created_at, updated_at) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?) `, seed.Definition.Name, seed.Definition.Title, string(seed.Definition.ExecutorKind), seed.Definition.Description, boolToInt(seed.Definition.IsEnabled), boolToInt(seed.Definition.IsBuiltin), seed.Definition.SortOrder, now, now, ) return err }