144 lines
4.4 KiB
Go
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
|
|
}
|