heyio 0.1.33 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,8 +18,8 @@ This restriction does NOT apply to:
18
18
  - Any files outside the IO installation directory
19
19
  `;
20
20
  const squadBlock = opts?.squadRoster
21
- ? `\n### Active Squads\n${opts.squadRoster}\n`
22
- : "";
21
+ ? `\n## Active Squads\nThe following squads are available. Route relevant coding requests directly to them.\n\n${opts.squadRoster}\n`
22
+ : `\n## Active Squads\nNo squads created yet. Use \`squad_create\` to set up a project squad.\n`;
23
23
  const osName = process.platform === "darwin" ? "macOS"
24
24
  : process.platform === "win32" ? "Windows"
25
25
  : "Linux";
@@ -47,12 +47,22 @@ When no source tag is present, assume TUI.
47
47
 
48
48
  ## Your Role
49
49
 
50
- You receive messages and decide how to handle them:
50
+ You receive messages and decide how to handle them based on a strict routing priority:
51
51
 
52
- - **Direct answer**: For simple questions, general knowledge, status checks — answer directly.
53
- - **Use tools**: For tasks requiring shell access, file operations, web lookupsuse your tools.
54
- - **Create/delegate to squad**: For coding projects that need persistent context — create a squad with specialized agents.
55
- - **Use a skill**: If you have a skill for the task, use it.
52
+ ### 1. Squad routing (highest priority)
53
+ If **active squads are listed below** and the request is clearly related to one of those projects (coding tasks, bugs, features, issues, PRs, architecture), **delegate immediately to that squad's team lead** using \`squad_delegate\` do NOT plan the work yourself, do NOT break it into subtasks. Just pass the full request to the lead and let the team handle it internally.
54
+
55
+ - The team lead will plan and delegate internally to teammates.
56
+ - You do not need to understand the full scope of the work — the lead does.
57
+ - Multiple squads? Pick the one whose \`project_path\` / name best matches the request.
58
+
59
+ ### 2. Direct tools (medium priority)
60
+ For tasks that require shell access, file operations, web lookups, or knowledge base updates — and that are NOT squad project work — use your tools directly. Use a skill if one is available for the task.
61
+
62
+ ### 3. Direct answer (lowest priority)
63
+ For general questions, conversation, status checks, or anything outside the scope of a squad's project — answer directly.
64
+
65
+ > **Rule**: If a squad exists that covers the topic, always delegate. Never plan or implement squad work yourself.
56
66
  ${squadBlock}
57
67
  ## Squad System
58
68
 
@@ -70,14 +80,24 @@ Squads are persistent project teams with **named specialist agents**. Each squad
70
80
  - Check overall status with \`squad_status\`.
71
81
 
72
82
  ### Delegating Work
73
- After planning tasks with the user, **use \`squad_delegate\` to send each task to the right agent**:
74
- 1. Plan the work with the user (break into concrete tasks).
75
- 2. Call \`squad_delegate\` with the squad slug and task. Optionally specify an \`agent\` (character name) to target a specific specialist. If omitted, the system picks the best available agent.
76
- 3. The agent works autonomously in the background with their specialized system prompt.
77
- 4. Use \`squad_task_status\` to check progress and retrieve results.
78
- 5. Report results back to the user, mentioning the character name.
79
-
80
- You can delegate multiple tasks to different agents in parallel.
83
+ **Do not plan squad work yourself.** When a squad-relevant request arrives:
84
+ 1. Call \`squad_delegate\` with the squad slug and the full request (as-is from the user). Do NOT specify an agent — let it route to the team lead automatically.
85
+ 2. The team lead breaks it down and delegates to teammates internally via \`delegate_to_teammate\`. If no lead is designated, the system falls back to the first idle agent.
86
+ 3. Use \`squad_task_status\` to monitor progress and report results back to the user.
87
+
88
+ Only specify an \`agent\` when the user **explicitly asks** to target a specific squad member by name.
89
+
90
+ ### Team Leads
91
+ Every squad should have a **team lead**. After building the team with \`squad_add_agent\`, designate one agent as the lead using \`squad_set_lead\`. The lead receives delegated tasks (when no specific agent is targeted), breaks them into subtasks, and assigns work to teammates via the lead-only \`delegate_to_teammate\` tool. This keeps coordination inside the squad rather than forcing IO to micro-manage assignments.
92
+
93
+ ### Peer Review & QA Approvals
94
+ When an agent finishes a task, the other squad members automatically review the work and vote APPROVED or REJECTED. Reviews are recorded and emitted as \`task.review\` events.
95
+
96
+ - Designate QA reviewers with \`squad_set_qa\` — typically agents focused on testing, security, or quality.
97
+ - **QA agents have veto power**: if any QA reviewer rejects, the PR stays as a draft.
98
+ - Non-QA rejections are advisory — they're recorded but don't block promotion.
99
+ - When all QA approvals pass (or no QA agents exist) and the task result contains a GitHub PR URL, the PR is automatically promoted from draft to ready via \`gh pr ready\`.
100
+ - Use \`squad_task_reviews\` to inspect the reviews on any completed task.
81
101
 
82
102
  ### Agent Roles Are Dynamic
83
103
  **Do NOT use generic roles** like "developer" or "tester". Analyze the project first and create roles that match its actual technology stack. Examples:
@@ -111,10 +111,14 @@ export function createTools(deps) {
111
111
  ? UNIVERSES.find((u) => u.id === s.universe)?.name ?? s.universe
112
112
  : "none";
113
113
  const agents = deps.listSquadAgents(s.slug);
114
+ const lead = deps.getSquadLead(s.slug);
115
+ const leadLine = lead
116
+ ? `\n ⭐ Team Lead: ${lead.character_name} (${lead.role_title})`
117
+ : "";
114
118
  const agentList = agents.length > 0
115
119
  ? "\n Agents: " + agents.map((a) => `${a.character_name} (${a.role_title})`).join(", ")
116
120
  : "\n Agents: none — use squad_add_agent to build the team";
117
- return `- **${s.name}** (\`${s.slug}\`) — ${s.status} — 🎬 ${universeName}${agentList}\n 📁 ${s.projectPath}`;
121
+ return `- **${s.name}** (\`${s.slug}\`) — ${s.status} — 🎬 ${universeName}${leadLine}${agentList}\n 📁 ${s.projectPath}`;
118
122
  })
119
123
  .join("\n");
120
124
  },
@@ -373,7 +377,11 @@ export function createTools(deps) {
373
377
  const universeName = squad.universe
374
378
  ? UNIVERSES.find((u) => u.id === squad.universe)?.name ?? squad.universe
375
379
  : "none";
376
- const lines = agents.map((a) => `- **${a.character_name}** — ${a.role_title} (${a.model_tier}) — ${a.status}${a.personality ? `\n _${a.personality}_` : ""}`);
380
+ const lines = agents.map((a) => {
381
+ const leadBadge = a.is_lead === 1 ? " ⭐ [LEAD]" : "";
382
+ const qaBadge = a.is_qa === 1 ? " 🛡️ [QA]" : "";
383
+ return `- **${a.character_name}**${leadBadge}${qaBadge} — ${a.role_title} (${a.model_tier}) — ${a.status}${a.personality ? `\n _${a.personality}_` : ""}`;
384
+ });
377
385
  return `**${squad.name}** — 🎬 ${universeName}\n\n${lines.join("\n")}`;
378
386
  },
379
387
  });
@@ -973,7 +981,84 @@ export function createTools(deps) {
973
981
  }
974
982
  },
975
983
  });
976
- return [wikiRead, wikiWrite, wikiSearch, wikiDelete, wikiList, squadCreate, squadRecall, squadStatus, squadLogDecision, squadDelegate, squadTaskStatus, squadDelete, squadAnalyze, squadAddAgent, squadAgents, squadRemoveAgent, skillList, skillInstall, skillRemove, skillSearch, configUpdate, checkUpdate, shell, fileOps, bash, readFile, viewTool, grepTool, strReplaceEditor, github];
984
+ const squadSetQA = defineTool("squad_set_qa", {
985
+ description: "Mark a squad agent as a QA reviewer with veto power. QA agents must approve before a PR is promoted from draft to ready.",
986
+ skipPermission: true,
987
+ parameters: z.object({
988
+ slug: z.string().describe("Squad slug"),
989
+ character_name: z.string().describe("Character name of the agent"),
990
+ is_qa: z
991
+ .boolean()
992
+ .describe("Whether this agent is a QA reviewer (true) or not (false)"),
993
+ }),
994
+ handler: async ({ slug, character_name, is_qa }) => {
995
+ try {
996
+ const squad = deps.getSquad(slug);
997
+ if (!squad)
998
+ return `Squad not found: ${slug}`;
999
+ const agents = deps.listSquadAgents(slug);
1000
+ const target = agents.find((a) => a.character_name === character_name);
1001
+ if (!target) {
1002
+ return `Agent "${character_name}" not found in squad "${slug}".`;
1003
+ }
1004
+ deps.setSquadQA(slug, character_name, is_qa);
1005
+ return is_qa
1006
+ ? `🛡️ ${character_name} (${target.role_title}) is now a QA reviewer for squad "${squad.name}". They have veto power over PR promotion.`
1007
+ : `${character_name} is no longer a QA reviewer for squad "${squad.name}".`;
1008
+ }
1009
+ catch (err) {
1010
+ return `Error: ${err instanceof Error ? err.message : String(err)}`;
1011
+ }
1012
+ },
1013
+ });
1014
+ const squadTaskReviews = defineTool("squad_task_reviews", {
1015
+ description: "Get the peer reviews left on a completed task by the squad. Shows who approved or rejected and any comments.",
1016
+ skipPermission: true,
1017
+ parameters: z.object({
1018
+ task_id: z.string().describe("The task ID to fetch reviews for"),
1019
+ }),
1020
+ handler: async ({ task_id }) => {
1021
+ const reviews = deps.getTaskReviews(task_id);
1022
+ if (reviews.length === 0) {
1023
+ return `No reviews found for task ${task_id}.`;
1024
+ }
1025
+ return reviews
1026
+ .map((r) => {
1027
+ const verdict = r.approved === 1 ? "✅ APPROVED" : "❌ REJECTED";
1028
+ const comments = r.comments ? `\n ${r.comments.replace(/\n/g, "\n ")}` : "";
1029
+ return `- **${r.reviewer_character}** — ${verdict}${comments}`;
1030
+ })
1031
+ .join("\n");
1032
+ },
1033
+ });
1034
+ const squadSetLead = defineTool("squad_set_lead", {
1035
+ description: "Designate an agent as the team lead for their squad. The lead receives delegated tasks (when no specific agent is targeted) and orchestrates the team by divvying subtasks to teammates.",
1036
+ skipPermission: true,
1037
+ parameters: z.object({
1038
+ slug: z.string().describe("Squad slug"),
1039
+ character_name: z
1040
+ .string()
1041
+ .describe("Character name of the agent to make team lead"),
1042
+ }),
1043
+ handler: async ({ slug, character_name }) => {
1044
+ try {
1045
+ const squad = deps.getSquad(slug);
1046
+ if (!squad)
1047
+ return `Squad not found: ${slug}`;
1048
+ const agents = deps.listSquadAgents(slug);
1049
+ const target = agents.find((a) => a.character_name === character_name);
1050
+ if (!target) {
1051
+ return `Agent "${character_name}" not found in squad "${slug}". Use squad_agents to list the roster.`;
1052
+ }
1053
+ deps.setSquadLead(slug, character_name);
1054
+ return `⭐ ${character_name} (${target.role_title}) is now the team lead for squad "${squad.name}".`;
1055
+ }
1056
+ catch (err) {
1057
+ return `Error: ${err instanceof Error ? err.message : String(err)}`;
1058
+ }
1059
+ },
1060
+ });
1061
+ return [wikiRead, wikiWrite, wikiSearch, wikiDelete, wikiList, squadCreate, squadRecall, squadStatus, squadLogDecision, squadDelegate, squadTaskStatus, squadDelete, squadAnalyze, squadAddAgent, squadAgents, squadRemoveAgent, squadSetLead, squadSetQA, squadTaskReviews, skillList, skillInstall, skillRemove, skillSearch, configUpdate, checkUpdate, shell, fileOps, bash, readFile, viewTool, grepTool, strReplaceEditor, github];
977
1062
  }
978
1063
  function walkDirectory(dir, maxDepth = 3, depth = 0) {
979
1064
  if (depth >= maxDepth)
package/dist/store/db.js CHANGED
@@ -70,6 +70,17 @@ export function getDb() {
70
70
  status TEXT NOT NULL DEFAULT 'idle',
71
71
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
72
72
  UNIQUE(squad_slug, character_name)
73
+ )`,
74
+ `ALTER TABLE squad_agents ADD COLUMN is_lead INTEGER NOT NULL DEFAULT 0`,
75
+ `ALTER TABLE squad_agents ADD COLUMN is_qa INTEGER NOT NULL DEFAULT 0`,
76
+ `CREATE TABLE IF NOT EXISTS squad_task_reviews (
77
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
78
+ task_id TEXT NOT NULL,
79
+ squad_slug TEXT NOT NULL,
80
+ reviewer_character TEXT NOT NULL,
81
+ approved INTEGER NOT NULL DEFAULT 0,
82
+ comments TEXT,
83
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
73
84
  )`,
74
85
  ];
75
86
  for (const migration of migrations) {
@@ -74,7 +74,7 @@ export function getSquadAgent(squadSlug, characterName) {
74
74
  }
75
75
  export function listSquadAgents(squadSlug) {
76
76
  return getDb()
77
- .prepare("SELECT * FROM squad_agents WHERE squad_slug = ? ORDER BY created_at")
77
+ .prepare("SELECT * FROM squad_agents WHERE squad_slug = ? ORDER BY id ASC")
78
78
  .all(squadSlug);
79
79
  }
80
80
  export function removeSquadAgent(squadSlug, characterName) {
@@ -115,4 +115,22 @@ export function getDecisionsSummary(squadSlug) {
115
115
  })
116
116
  .join("\n");
117
117
  }
118
+ export function setSquadLead(squadSlug, characterName) {
119
+ const db = getDb();
120
+ const tx = db.transaction(() => {
121
+ db.prepare("UPDATE squad_agents SET is_lead = 0 WHERE squad_slug = ?").run(squadSlug);
122
+ db.prepare("UPDATE squad_agents SET is_lead = 1 WHERE squad_slug = ? AND character_name = ?").run(squadSlug, characterName);
123
+ });
124
+ tx();
125
+ }
126
+ export function getSquadLead(squadSlug) {
127
+ return getDb()
128
+ .prepare("SELECT * FROM squad_agents WHERE squad_slug = ? AND is_lead = 1 LIMIT 1")
129
+ .get(squadSlug);
130
+ }
131
+ export function setSquadQA(squadSlug, characterName, isQA) {
132
+ getDb()
133
+ .prepare("UPDATE squad_agents SET is_qa = ? WHERE squad_slug = ? AND character_name = ?")
134
+ .run(isQA ? 1 : 0, squadSlug, characterName);
135
+ }
118
136
  //# sourceMappingURL=squads.js.map
@@ -29,4 +29,28 @@ export function clearStaleTasks() {
29
29
  .prepare("UPDATE agent_tasks SET status = 'failed', result = 'Marked stale on startup', completed_at = CURRENT_TIMESTAMP WHERE status = 'running'")
30
30
  .run();
31
31
  }
32
+ export function cancelTask(taskId, reason = "Cancelled by user") {
33
+ getDb()
34
+ .prepare("UPDATE agent_tasks SET status = 'cancelled', result = ?, completed_at = CURRENT_TIMESTAMP WHERE task_id = ? AND status = 'running'")
35
+ .run(reason, taskId);
36
+ }
37
+ export function listRecentTasks(limit = 50) {
38
+ return getDb()
39
+ .prepare("SELECT * FROM agent_tasks ORDER BY datetime(started_at) DESC, task_id DESC LIMIT ?")
40
+ .all(limit);
41
+ }
42
+ export function createReview(taskId, squadSlug, reviewerCharacter, approved, comments) {
43
+ const db = getDb();
44
+ const info = db
45
+ .prepare("INSERT INTO squad_task_reviews (task_id, squad_slug, reviewer_character, approved, comments) VALUES (?, ?, ?, ?, ?)")
46
+ .run(taskId, squadSlug, reviewerCharacter, approved ? 1 : 0, comments ?? null);
47
+ return db
48
+ .prepare("SELECT * FROM squad_task_reviews WHERE id = ?")
49
+ .get(info.lastInsertRowid);
50
+ }
51
+ export function getTaskReviews(taskId) {
52
+ return getDb()
53
+ .prepare("SELECT * FROM squad_task_reviews WHERE task_id = ? ORDER BY created_at ASC, id ASC")
54
+ .all(taskId);
55
+ }
32
56
  //# sourceMappingURL=tasks.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "heyio",
3
- "version": "0.1.33",
3
+ "version": "0.2.1",
4
4
  "description": "IO — a personal AI assistant built on the GitHub Copilot SDK",
5
5
  "bin": {
6
6
  "io": "dist/index.js"