feat(builtin-skills): add team-mode skill implementation with tests
This commit is contained in:
@@ -0,0 +1,98 @@
|
|||||||
|
import { describe, expect, test } from "bun:test"
|
||||||
|
|
||||||
|
import { createBuiltinSkills } from "../skills"
|
||||||
|
import { teamModeSkill } from "./team-mode"
|
||||||
|
|
||||||
|
describe("teamModeSkill gating", () => {
|
||||||
|
test("team-mode hidden when disabled", () => {
|
||||||
|
// given
|
||||||
|
const options = {
|
||||||
|
teamModeEnabled: false,
|
||||||
|
disabledSkills: new Set<string>(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const skills = createBuiltinSkills(options)
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(skills.some((skill) => skill.name === "team-mode")).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("team-mode visible when enabled", () => {
|
||||||
|
// given
|
||||||
|
const options = {
|
||||||
|
teamModeEnabled: true,
|
||||||
|
disabledSkills: new Set<string>(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// when
|
||||||
|
const skills = createBuiltinSkills(options)
|
||||||
|
|
||||||
|
// then
|
||||||
|
const skill = skills.find((candidateSkill) => candidateSkill.name === "team-mode")
|
||||||
|
expect(skill).toBeDefined()
|
||||||
|
expect(skill?.name).toBe("team-mode")
|
||||||
|
expect(skill?.description).toBe(teamModeSkill.description)
|
||||||
|
})
|
||||||
|
|
||||||
|
test("team-mode skill has no mcpConfig", () => {
|
||||||
|
// given
|
||||||
|
|
||||||
|
// when
|
||||||
|
const skill = teamModeSkill
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(skill.mcpConfig).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
test("team-mode skill body keeps required keywords", () => {
|
||||||
|
// given
|
||||||
|
const body = teamModeSkill.template
|
||||||
|
|
||||||
|
// when
|
||||||
|
const keywords = [
|
||||||
|
"TeamSpec",
|
||||||
|
"member",
|
||||||
|
"category",
|
||||||
|
"subagent_type",
|
||||||
|
"sisyphus",
|
||||||
|
"atlas",
|
||||||
|
"hephaestus",
|
||||||
|
"oracle",
|
||||||
|
"eligible",
|
||||||
|
]
|
||||||
|
|
||||||
|
// then
|
||||||
|
for (const keyword of keywords) {
|
||||||
|
expect(body).toContain(keyword)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test("team-mode skill separates lead-only and member-safe tools", () => {
|
||||||
|
// given
|
||||||
|
const body = teamModeSkill.template
|
||||||
|
|
||||||
|
// when
|
||||||
|
const leadOnlyTools = ["team_create", "team_delete", "team_shutdown_request"]
|
||||||
|
const universalTools = [
|
||||||
|
"team_send_message",
|
||||||
|
"team_task_create",
|
||||||
|
"team_task_list",
|
||||||
|
"team_task_update",
|
||||||
|
"team_task_get",
|
||||||
|
"team_status",
|
||||||
|
]
|
||||||
|
|
||||||
|
// then
|
||||||
|
expect(body).toContain("## Lead-only tools")
|
||||||
|
expect(body).toContain("## Universal team-run tools")
|
||||||
|
expect(body).toContain("## Global query tool")
|
||||||
|
for (const toolName of leadOnlyTools) {
|
||||||
|
expect(body).toContain(toolName)
|
||||||
|
}
|
||||||
|
for (const toolName of universalTools) {
|
||||||
|
expect(body).toContain(toolName)
|
||||||
|
}
|
||||||
|
expect(body).not.toContain("team_shutdown_request - ask the lead to wind down")
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import type { BuiltinSkill } from "../types"
|
||||||
|
|
||||||
|
export const teamModeSkill: BuiltinSkill = {
|
||||||
|
name: "team-mode",
|
||||||
|
description:
|
||||||
|
"Team orchestration — create and manage parallel agent teams (OFF by default; enable via team_mode.enabled in config). Loading this skill provides usage documentation; the team_* tools are registered globally when team_mode.enabled=true and access-gated by team role.",
|
||||||
|
template: `# Team Mode
|
||||||
|
|
||||||
|
Team mode gives Claude Code Agent Teams parity. It is off by default. Enable it only when you want parallel multi-agent coordination, where each team member is an opencode child session.
|
||||||
|
|
||||||
|
## When to use
|
||||||
|
|
||||||
|
- Split a large job across several agents.
|
||||||
|
- Keep a lead agent focused while member agents work in parallel.
|
||||||
|
- Use worktree mode for isolated code changes, or tmux visualization when you want live session layout.
|
||||||
|
|
||||||
|
## Declare a team
|
||||||
|
|
||||||
|
Create a team at \`~/.omo/teams/{name}/config.json\`.
|
||||||
|
|
||||||
|
You can also pass the same object directly to \`team_create({ inline_spec: ... })\`.
|
||||||
|
|
||||||
|
This TeamSpec uses a lead plus members list. Every canonical member has a \`kind\` discriminator.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
\`\`\`json
|
||||||
|
{
|
||||||
|
"name": "release-squad",
|
||||||
|
"lead": {
|
||||||
|
"kind": "subagent_type",
|
||||||
|
"subagent_type": "sisyphus"
|
||||||
|
},
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"kind": "category",
|
||||||
|
"category": "quick",
|
||||||
|
"prompt": "review small changes and report risks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "subagent_type",
|
||||||
|
"subagent_type": "atlas"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
Inline shorthand is accepted for category members. If \`kind\` is omitted, \`category\` implies \`kind: "category"\`. If a member uses natural planning fields like \`role\`, \`description\`, \`capabilities\`, or an unknown \`kind\`, it becomes a category worker using the current config's first enabled category. If \`kind\` is an unknown string such as a category name, that string is used as the category. \`systemPrompt\` is accepted as a \`prompt\` alias, and \`loadSkills\` is ignored because team members receive their behavior through \`prompt\`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
\`\`\`json
|
||||||
|
{
|
||||||
|
"name": "project-analysis-team",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"name": "structure-analyst",
|
||||||
|
"category": "quick",
|
||||||
|
"systemPrompt": "Analyze directory layouts, module boundaries, and architectural organization."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "quality-analyst",
|
||||||
|
"category": "quick",
|
||||||
|
"systemPrompt": "Analyze tests, CI/CD, build scripts, conventions, and anti-patterns."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Agent 3: Quality/Process Analyst",
|
||||||
|
"role": "Quality/Process Analyst",
|
||||||
|
"capabilities": ["tests", "builds", "CI/CD"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Member schema
|
||||||
|
|
||||||
|
Use \`kind: "category"\` when you want a category-backed worker. It must include both \`category\` and \`prompt\`. D-40: category members always route through \`sisyphus-junior\`.
|
||||||
|
|
||||||
|
Use \`kind: "subagent_type"\` only for eligible agents.
|
||||||
|
|
||||||
|
### Eligible subagent types
|
||||||
|
|
||||||
|
- \`sisyphus\`
|
||||||
|
- \`atlas\`
|
||||||
|
- \`sisyphus-junior\`
|
||||||
|
- \`hephaestus\`
|
||||||
|
|
||||||
|
### Hard rejects
|
||||||
|
|
||||||
|
Do not use \`oracle\`, \`prometheus\`, or other non-eligible agents here. For those, use \`delegate-task\` instead.
|
||||||
|
|
||||||
|
## Lifecycle
|
||||||
|
|
||||||
|
1. Lead creates the team with \`team_create({ teamName: "existing-team" })\` or \`team_create({ inline_spec: { name: "team-name", members: [...] } })\`. Never call \`team_create\` with empty arguments.
|
||||||
|
2. Lead assigns work with \`team_send_message\` or \`team_task_create\`.
|
||||||
|
3. Members report progress with \`team_send_message\` plus \`team_task_update\`.
|
||||||
|
4. Lead and members track progress with \`team_task_list\`, \`team_task_get\`, and \`team_status\`.
|
||||||
|
5. Lead requests shutdown with \`team_shutdown_request\` when the team is ready to wind down.
|
||||||
|
6. The targeted member or the lead handles \`team_approve_shutdown\` or \`team_reject_shutdown\`.
|
||||||
|
7. Lead removes the team with \`team_delete\`.
|
||||||
|
|
||||||
|
## Lead-only tools
|
||||||
|
|
||||||
|
- \`team_create\` - create a team from a declaration.
|
||||||
|
- \`team_delete\` - remove a team.
|
||||||
|
- \`team_shutdown_request\` - start the shutdown flow.
|
||||||
|
|
||||||
|
## Lead or target-member shutdown tools
|
||||||
|
|
||||||
|
- \`team_approve_shutdown\` - approve shutdown for the targeted member.
|
||||||
|
- \`team_reject_shutdown\` - reject shutdown for the targeted member.
|
||||||
|
|
||||||
|
## Universal team-run tools
|
||||||
|
|
||||||
|
- \`team_send_message\` - send a direct message; broadcast is still lead-only.
|
||||||
|
- \`team_task_create\` - create a task for a member.
|
||||||
|
- \`team_task_list\` - list team tasks.
|
||||||
|
- \`team_task_update\` - update task state.
|
||||||
|
- \`team_task_get\` - inspect one task.
|
||||||
|
- \`team_status\` - show live team status.
|
||||||
|
|
||||||
|
## Global query tool
|
||||||
|
|
||||||
|
- \`team_list\` - list known teams.
|
||||||
|
|
||||||
|
## Bounds
|
||||||
|
|
||||||
|
- Max 8 members.
|
||||||
|
- Max 4 parallel workers.
|
||||||
|
- Max 32KB per message.
|
||||||
|
- Max 256KB unread inbox.
|
||||||
|
|
||||||
|
## Failure modes
|
||||||
|
|
||||||
|
- Broadcast is lead-only.
|
||||||
|
- No nested teams.
|
||||||
|
- No peer sync wait; work moves asynchronously.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Team mode is a docs-only skill. The team_* tools are registered globally when \`team_mode.enabled=true\`.
|
||||||
|
Use \`~/.omo/teams/{name}/config.json\` plus worktree or tmux visibility to understand how the team is laid out.
|
||||||
|
`,
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user