Files

172 lines
4.7 KiB
Go

package sqlite
import (
"context"
"database/sql"
"errors"
"fmt"
"inbox/internal/domain/skill"
)
type builtinSkillSeed struct {
Definition skill.Definition
RoleNames []string
}
const inboxSkillMarkdown = `---
name: inbox
description: "Use Inbox V2 from a role runtime. Supports two operations only: send and ask. Use this when you need to post a workflow message or a blocking question into an existing topic with inbox api."
---
# Inbox
Use this skill when a role needs to communicate through Inbox V2.
This skill has two operations only:
- send: post a normal workflow message into an existing topic
- ask: post a blocking question that needs an answer from another role
## Before using either operation
Resolve the topic id first if you only know the workspace and topic slug:
inbox api GET "/api/v2/topics?workspace_id=<workspace_id>"
Pick the topic record you want, then use its id in the message API below.
## send
Use send for handoffs, updates, decisions, or summaries.
inbox api POST /api/v2/topics/<topic_id>/messages --data '{
"workspace_id": "<workspace_id>",
"from_role_name": "<current_role>",
"to_expr": "<target_role>",
"type": "chat",
"stage": "execution",
"body_markdown": "<message markdown>"
}'
Rules:
- Set type to the actual intent: chat, proposal, decision, or summary.
- Keep stage aligned with the current workflow state.
- Add reply_to_message_id when replying to a specific message.
## ask
Use ask when you are blocked and need a concrete answer.
inbox api POST /api/v2/topics/<topic_id>/messages --data '{
"workspace_id": "<workspace_id>",
"from_role_name": "<current_role>",
"to_expr": "<target_role>",
"type": "question",
"stage": "<current_stage>",
"body_markdown": "<single concrete question and the decision you need>"
}'
Rules:
- Ask one concrete blocker per message.
- State the decision, artifact, or approval you need back.
- Keep stage aligned with the task or planning stage you are in.
- Prefer ask over vague status pings.`
var builtinSkillSeeds = []builtinSkillSeed{
{
Definition: skill.Definition{
SkillKey: "inbox",
Name: "Inbox",
Description: "Use Inbox V2 from a role runtime with two operations: send and ask.",
SourceType: "capability",
ContentMarkdown: inboxSkillMarkdown,
Status: "active",
},
RoleNames: []string{
"leader",
"worker",
},
},
}
func (s *Store) ensureBuiltinSkills(ctx context.Context) error {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("begin builtin skill seed: %w", err)
}
defer tx.Rollback()
now := s.now()
for _, seed := range builtinSkillSeeds {
if err := s.seedBuiltinSkillTx(ctx, tx, now, seed); err != nil {
return err
}
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("commit builtin skill seed: %w", err)
}
return nil
}
func (s *Store) seedBuiltinSkillTx(ctx context.Context, tx *sql.Tx, now string, seed builtinSkillSeed) error {
item, err := getSkillByKeyTx(ctx, tx, seed.Definition.SkillKey)
if err != nil {
if !errors.Is(err, sql.ErrNoRows) {
return err
}
item = seed.Definition
item.ID = "builtin-skill-" + seed.Definition.SkillKey
item.Version = 1
item.CreatedAt = now
item.UpdatedAt = now
if _, err := tx.ExecContext(ctx, `
INSERT INTO skills(id, skill_key, name, description, source_type, content_markdown, status, version, created_at, updated_at)
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`,
item.ID,
item.SkillKey,
item.Name,
item.Description,
item.SourceType,
item.ContentMarkdown,
item.Status,
item.Version,
item.CreatedAt,
item.UpdatedAt,
); err != nil {
return fmt.Errorf("insert builtin skill %q: %w", item.SkillKey, err)
}
}
for _, roleName := range seed.RoleNames {
if err := seedBuiltinRoleSkillBindingTx(ctx, tx, now, roleName, item.ID, item.SkillKey); err != nil {
return err
}
}
return nil
}
func seedBuiltinRoleSkillBindingTx(ctx context.Context, tx *sql.Tx, now, roleName, skillID, skillKey string) error {
if _, err := getRoleSkillBindingTx(ctx, tx, roleName, "", skillID); err == nil {
return nil
} else if !errors.Is(err, sql.ErrNoRows) {
return err
}
if _, err := tx.ExecContext(ctx, `
INSERT INTO role_skill_bindings(id, role_name, workspace_id, skill_id, is_enabled, sort_order, config_json, version, updated_by, created_at, updated_at)
VALUES(?, ?, NULL, ?, 1, 100, ?, 1, 'builtin-seed', ?, ?)
`,
"builtin-role-skill-binding-"+roleName+"-"+skillKey,
roleName,
skillID,
`{"category":"capability"}`,
now,
now,
); err != nil {
return fmt.Errorf("insert builtin role skill binding %q/%q: %w", roleName, skillKey, err)
}
return nil
}