Files

144 lines
4.4 KiB
Go

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
}