heyio 0.42.0 → 1.0.0

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.
Files changed (100) hide show
  1. package/README.md +40 -52
  2. package/dist/api/auth.js +35 -38
  3. package/dist/api/server.js +157 -1139
  4. package/dist/config.js +49 -32
  5. package/dist/copilot/agents.js +72 -1055
  6. package/dist/copilot/client.js +6 -17
  7. package/dist/copilot/io-scheduler.js +55 -139
  8. package/dist/copilot/model-router.js +100 -72
  9. package/dist/copilot/orchestrator.js +91 -515
  10. package/dist/copilot/scheduler.js +67 -189
  11. package/dist/copilot/skills.js +41 -366
  12. package/dist/copilot/system-message.js +40 -200
  13. package/dist/copilot/tools.js +191 -2042
  14. package/dist/daemon.js +54 -201
  15. package/dist/index.js +15 -133
  16. package/dist/mcp/config.js +23 -31
  17. package/dist/mcp/index.js +2 -3
  18. package/dist/mcp/registry.js +33 -88
  19. package/dist/notify.js +18 -100
  20. package/dist/paths.js +13 -24
  21. package/dist/setup.js +35 -0
  22. package/dist/store/db.js +111 -297
  23. package/dist/store/feed.js +29 -97
  24. package/dist/store/instances.js +56 -121
  25. package/dist/store/schedules.js +21 -73
  26. package/dist/store/squads.js +35 -186
  27. package/dist/store/tasks.js +25 -168
  28. package/dist/telegram/bot.js +20 -312
  29. package/dist/telegram/handlers.js +39 -3
  30. package/dist/watchdog.js +31 -45
  31. package/dist/wiki/fs.js +38 -155
  32. package/dist/wiki/search.js +31 -44
  33. package/package.json +5 -8
  34. package/web-dist/assets/ChatView-EFFiln1H.js +11 -0
  35. package/web-dist/assets/FeedView-bN4NMOL7.js +6 -0
  36. package/web-dist/assets/LoginView-CNtasq3n.js +1 -0
  37. package/web-dist/assets/McpView-C2CHiwsi.js +1 -0
  38. package/web-dist/assets/SchedulesView-CyilLban.js +1 -0
  39. package/web-dist/assets/SettingsView-1wLXKEF4.js +1 -0
  40. package/web-dist/assets/SkillsView-BLsD-0u0.js +1 -0
  41. package/web-dist/assets/SquadDetailView-CsCw2ZLp.js +21 -0
  42. package/web-dist/assets/SquadsView-DQ3vFlyO.js +6 -0
  43. package/web-dist/assets/WikiView-19M3oqnq.js +21 -0
  44. package/web-dist/assets/api-WGvTsXaE.js +1 -0
  45. package/web-dist/assets/index-D7M5O-_l.css +1 -0
  46. package/web-dist/assets/index-DZOS9syn.js +95 -0
  47. package/web-dist/assets/plus-BOvyX1BC.js +6 -0
  48. package/web-dist/assets/trash-2-DHoetkC4.js +6 -0
  49. package/web-dist/favicon.svg +4 -1
  50. package/web-dist/index.html +7 -10
  51. package/dist/api/logout.test.js +0 -129
  52. package/dist/api/mcp.test.js +0 -285
  53. package/dist/api/wiki.test.js +0 -283
  54. package/dist/auth/session-logic.js +0 -79
  55. package/dist/auth/session-logic.test.js +0 -201
  56. package/dist/copilot/auto-complete-instance.test.js +0 -104
  57. package/dist/copilot/cron.js +0 -136
  58. package/dist/copilot/event-summary.js +0 -286
  59. package/dist/copilot/instance-deactivate.test.js +0 -119
  60. package/dist/copilot/model-router.test.js +0 -71
  61. package/dist/copilot/review-backfill.js +0 -57
  62. package/dist/copilot/session-timeout.js +0 -112
  63. package/dist/copilot/session-timeout.test.js +0 -372
  64. package/dist/copilot/skills.test.js +0 -55
  65. package/dist/copilot/universes.js +0 -469
  66. package/dist/instance-watchdog.js +0 -104
  67. package/dist/instance-watchdog.test.js +0 -183
  68. package/dist/mcp/client.js +0 -109
  69. package/dist/mcp/client.test.js +0 -99
  70. package/dist/mcp/config.test.js +0 -49
  71. package/dist/mcp/registry.test.js +0 -79
  72. package/dist/notify.test.js +0 -232
  73. package/dist/store/feed.test.js +0 -279
  74. package/dist/store/instances.test.js +0 -310
  75. package/dist/store/io-schedules.js +0 -63
  76. package/dist/store/notifications.js +0 -79
  77. package/dist/store/notifications.test.js +0 -197
  78. package/dist/store/schedule-runs.js +0 -46
  79. package/dist/store/squads.test.js +0 -405
  80. package/dist/store/tasks.test.js +0 -150
  81. package/dist/store/worktrees.js +0 -83
  82. package/dist/tui/index.js +0 -286
  83. package/dist/update.js +0 -81
  84. package/dist/watchdog.test.js +0 -83
  85. package/dist/wiki/wiki-squad.test.js +0 -54
  86. package/web-dist/assets/AgentActivityView-CedxxE6K.js +0 -1
  87. package/web-dist/assets/ChatView-DMkYQo_V.js +0 -4
  88. package/web-dist/assets/FeedView-BH4q-31V.js +0 -1
  89. package/web-dist/assets/InboxView-BVwVP4EW.js +0 -1
  90. package/web-dist/assets/LoginView-DRPDhnwu.js +0 -1
  91. package/web-dist/assets/McpView-D8yWz-lq.js +0 -1
  92. package/web-dist/assets/SchedulesView-BzzyncGF.js +0 -1
  93. package/web-dist/assets/SettingsTabs.vue_vue_type_script_setup_true_lang-oW3ySu7Y.js +0 -1
  94. package/web-dist/assets/SkillsView-oxpYuhx7.js +0 -1
  95. package/web-dist/assets/SquadsView-CaKUIKlq.js +0 -1
  96. package/web-dist/assets/StatusIndicator.vue_vue_type_script_setup_true_lang-8U15Qp_Q.js +0 -1
  97. package/web-dist/assets/WikiView-C5jXUlfW.js +0 -1
  98. package/web-dist/assets/index-BrWzNw-N.css +0 -10
  99. package/web-dist/assets/index-f67odrrt.js +0 -81
  100. package/web-dist/icons.svg +0 -24
@@ -1,201 +1,50 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { getDb } from "./db.js";
2
- import { nextCharacter, randomUniverse, getOrCreateUniverse } from "../copilot/universes.js";
3
- // ---------------------------------------------------------------------------
4
- // Squad color palette — universe-inspired distinct colors
5
- // ---------------------------------------------------------------------------
6
- export const SQUAD_COLOR_PALETTE = [
7
- "#ff6b35", // A-Team orange
8
- "#ffd000", // Thundercats gold
9
- "#5fff87", // GI Joe green
10
- "#c4a7ff", // Ghostbusters purple
11
- "#00d9ff", // Transformers cyan
12
- "#ff9800", // extra amber
13
- "#9c27b0", // extra violet
14
- "#2196f3", // extra blue
15
- "#e91e63", // extra pink
16
- "#00bcd4", // extra teal
17
- "#8bc34a", // extra lime
18
- "#ff5722", // extra deep-orange
19
- ];
20
- /**
21
- * Pick a color for a new squad. Prefers an unused palette color; when all
22
- * are taken, cycles through the palette by position (modular assignment).
23
- * This guarantees we never throw and always return a valid hex color.
24
- */
25
- export function pickSquadColor(existingColors) {
26
- const used = new Set(existingColors.filter(Boolean));
27
- const unused = SQUAD_COLOR_PALETTE.filter((c) => !used.has(c));
28
- if (unused.length > 0)
29
- return unused[0];
30
- // All palette colors taken — cycle by squad count
31
- return SQUAD_COLOR_PALETTE[used.size % SQUAD_COLOR_PALETTE.length];
32
- }
33
- export function createSquad(slug, name, projectPath, universeId) {
3
+ export function createSquad(name, universe, repoUrl) {
34
4
  const db = getDb();
35
- const universe = universeId
36
- ? getOrCreateUniverse(universeId).id
37
- : randomUniverse().id;
38
- const existingSquads = listSquads();
39
- const color = pickSquadColor(existingSquads.map((s) => s.color));
40
- db.prepare("INSERT INTO squads (slug, name, project_path, universe, color) VALUES (?, ?, ?, ?, ?)").run(slug, name, projectPath, universe, color);
41
- return getSquad(slug);
5
+ const id = randomUUID();
6
+ db.prepare("INSERT INTO squads (id, name, universe, repo_url) VALUES (?, ?, ?, ?)").run(id, name, universe, repoUrl ?? null);
7
+ return db.prepare("SELECT * FROM squads WHERE id = ?").get(id);
42
8
  }
43
- export function getSquad(slug) {
44
- return getDb()
45
- .prepare("SELECT * FROM squads WHERE slug = ?")
46
- .get(slug);
9
+ export function getSquad(id) {
10
+ const db = getDb();
11
+ return db.prepare("SELECT * FROM squads WHERE id = ?").get(id);
47
12
  }
48
13
  export function listSquads() {
49
- return getDb().prepare("SELECT * FROM squads ORDER BY created_at DESC").all();
50
- }
51
- export function updateSquadSession(slug, sessionId) {
52
- getDb()
53
- .prepare("UPDATE squads SET copilot_session_id = ?, updated_at = CURRENT_TIMESTAMP WHERE slug = ?")
54
- .run(sessionId, slug);
55
- }
56
- export function updateSquadStatus(slug, status) {
57
- getDb()
58
- .prepare("UPDATE squads SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE slug = ?")
59
- .run(status, slug);
60
- }
61
- export function updateSquadModel(slug, model) {
62
- getDb()
63
- .prepare("UPDATE squads SET model = ?, updated_at = CURRENT_TIMESTAMP WHERE slug = ?")
64
- .run(model, slug);
65
- }
66
- export function deleteSquad(slug) {
67
14
  const db = getDb();
68
- db.prepare("DELETE FROM squad_agents WHERE squad_slug = ?").run(slug);
69
- db.prepare("DELETE FROM squad_decisions WHERE squad_slug = ?").run(slug);
70
- db.prepare("DELETE FROM squads WHERE slug = ?").run(slug);
71
- }
72
- // ---------------------------------------------------------------------------
73
- // Squad Agent CRUD
74
- // ---------------------------------------------------------------------------
75
- /**
76
- * Add a named agent to a squad. Automatically assigns the next character
77
- * from the squad's universe pool.
78
- */
79
- export function addSquadAgent(squadSlug, roleTitle, charter, modelTier = "medium") {
80
- const squad = getSquad(squadSlug);
81
- if (!squad)
82
- throw new Error(`Squad not found: ${squadSlug}`);
83
- const universeId = squad.universe ?? randomUniverse().id;
84
- // If squad doesn't have a universe yet, set it
85
- if (!squad.universe) {
86
- getDb()
87
- .prepare("UPDATE squads SET universe = ? WHERE slug = ?")
88
- .run(universeId, squadSlug);
89
- }
90
- // Ensure universe is registered in runtime array (handles restart for custom universes)
91
- getOrCreateUniverse(universeId);
92
- const existingAgents = listSquadAgents(squadSlug);
93
- const usedNames = existingAgents.map((a) => a.character_name);
94
- const character = nextCharacter(universeId, usedNames);
95
- if (!character) {
96
- throw new Error(`All characters in the "${universeId}" universe are assigned. Remove an agent first.`);
97
- }
98
- getDb()
99
- .prepare(`INSERT INTO squad_agents (squad_slug, character_name, role_title, charter, model_tier, personality)
100
- VALUES (?, ?, ?, ?, ?, ?)`)
101
- .run(squadSlug, character.name, roleTitle, charter, modelTier, character.personality);
102
- return getSquadAgent(squadSlug, character.name);
103
- }
104
- export function getSquadAgent(squadSlug, characterName) {
105
- return getDb()
106
- .prepare("SELECT * FROM squad_agents WHERE squad_slug = ? AND character_name = ?")
107
- .get(squadSlug, characterName);
108
- }
109
- export function listSquadAgents(squadSlug) {
110
- return getDb()
111
- .prepare("SELECT * FROM squad_agents WHERE squad_slug = ? ORDER BY id ASC")
112
- .all(squadSlug);
113
- }
114
- export function removeSquadAgent(squadSlug, characterName) {
115
- const result = getDb()
116
- .prepare("DELETE FROM squad_agents WHERE squad_slug = ? AND character_name = ?")
117
- .run(squadSlug, characterName);
118
- return result.changes > 0;
15
+ const squads = db.prepare("SELECT * FROM squads ORDER BY created_at DESC").all();
16
+ const agents = db.prepare("SELECT * FROM agents ORDER BY created_at").all();
17
+ return { squads, agents };
119
18
  }
120
- export function updateAgentSession(squadSlug, characterName, sessionId) {
121
- getDb()
122
- .prepare("UPDATE squad_agents SET copilot_session_id = ? WHERE squad_slug = ? AND character_name = ?")
123
- .run(sessionId, squadSlug, characterName);
124
- }
125
- export function updateAgentStatus(squadSlug, characterName, status) {
126
- getDb()
127
- .prepare("UPDATE squad_agents SET status = ? WHERE squad_slug = ? AND character_name = ?")
128
- .run(status, squadSlug, characterName);
129
- }
130
- /**
131
- * Clear an agent's persisted copilot_session_id. Used during error recovery
132
- * so the next task creates a fresh session instead of trying to resume a
133
- * poisoned one.
134
- */
135
- export function clearAgentSession(squadSlug, characterName) {
136
- getDb()
137
- .prepare("UPDATE squad_agents SET copilot_session_id = NULL WHERE squad_slug = ? AND character_name = ?")
138
- .run(squadSlug, characterName);
139
- }
140
- /**
141
- * Reset any agent left in a non-idle status from a previous daemon run.
142
- * The in-memory Copilot sessions don't survive a restart, so persisted
143
- * "working" or "error" rows can never be accurate after startup. Returns
144
- * the number of rows reset for logging.
145
- */
146
- export function reconcileAgentStatuses() {
147
- const info = getDb()
148
- .prepare("UPDATE squad_agents SET status = 'idle' WHERE status IN ('working', 'error')")
149
- .run();
150
- return info.changes;
151
- }
152
- /**
153
- * Mirror of reconcileAgentStatuses for squads themselves.
154
- */
155
- export function reconcileSquadStatuses() {
156
- const info = getDb()
157
- .prepare("UPDATE squads SET status = 'idle' WHERE status IN ('working', 'error')")
158
- .run();
159
- return info.changes;
160
- }
161
- export function logDecision(squadSlug, decision, context) {
162
- getDb()
163
- .prepare("INSERT INTO squad_decisions (squad_slug, decision, context) VALUES (?, ?, ?)")
164
- .run(squadSlug, decision, context ?? null);
19
+ export function deleteSquad(id) {
20
+ const db = getDb();
21
+ db.prepare("DELETE FROM squads WHERE id = ?").run(id);
165
22
  }
166
- export function getDecisions(squadSlug, limit = 20) {
167
- return getDb()
168
- .prepare("SELECT * FROM squad_decisions WHERE squad_slug = ? ORDER BY created_at DESC, id DESC LIMIT ?")
169
- .all(squadSlug, limit);
23
+ export function updateSquadRules(id, rules) {
24
+ const db = getDb();
25
+ db.prepare("UPDATE squads SET rules = ?, updated_at = datetime('now') WHERE id = ?").run(rules, id);
170
26
  }
171
- export function getDecisionsSummary(squadSlug) {
172
- const decisions = getDecisions(squadSlug);
173
- if (decisions.length === 0)
174
- return "No decisions recorded.";
175
- return decisions
176
- .reverse()
177
- .map((d) => {
178
- const ctx = d.context ? ` (${d.context})` : "";
179
- return `- [${d.created_at}] ${d.decision}${ctx}`;
180
- })
181
- .join("\n");
27
+ export function addAgent(squadId, input) {
28
+ const db = getDb();
29
+ const id = randomUUID();
30
+ db.prepare(`INSERT INTO agents (id, squad_id, character_name, role_title, persona, is_lead, is_qa, is_test)
31
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(id, squadId, input.character_name, input.role_title, input.persona, input.is_lead ? 1 : 0, input.is_qa ? 1 : 0, input.is_test ? 1 : 0);
32
+ return db.prepare("SELECT * FROM agents WHERE id = ?").get(id);
182
33
  }
183
- export function setSquadLead(squadSlug, characterName) {
34
+ export function getAgentsForSquad(squadId) {
184
35
  const db = getDb();
185
- const tx = db.transaction(() => {
186
- db.prepare("UPDATE squad_agents SET is_lead = 0 WHERE squad_slug = ?").run(squadSlug);
187
- db.prepare("UPDATE squad_agents SET is_lead = 1 WHERE squad_slug = ? AND character_name = ?").run(squadSlug, characterName);
188
- });
189
- tx();
36
+ return db
37
+ .prepare("SELECT * FROM agents WHERE squad_id = ? ORDER BY created_at")
38
+ .all(squadId);
190
39
  }
191
- export function getSquadLead(squadSlug) {
192
- return getDb()
193
- .prepare("SELECT * FROM squad_agents WHERE squad_slug = ? AND is_lead = 1 LIMIT 1")
194
- .get(squadSlug);
40
+ export function getLeadForSquad(squadId) {
41
+ const db = getDb();
42
+ return db
43
+ .prepare("SELECT * FROM agents WHERE squad_id = ? AND is_lead = 1 LIMIT 1")
44
+ .get(squadId);
195
45
  }
196
- export function setSquadQA(squadSlug, characterName, isQA) {
197
- getDb()
198
- .prepare("UPDATE squad_agents SET is_qa = ? WHERE squad_slug = ? AND character_name = ?")
199
- .run(isQA ? 1 : 0, squadSlug, characterName);
46
+ export function updateAgentStatus(agentId, status) {
47
+ const db = getDb();
48
+ db.prepare("UPDATE agents SET status = ? WHERE id = ?").run(status, agentId);
200
49
  }
201
50
  //# sourceMappingURL=squads.js.map
@@ -1,178 +1,35 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { getDb } from "./db.js";
2
- export function createTask(taskId, agentSlug, description, originChannel, instanceId) {
3
+ export function createTask(squadId, description, instanceId, agentId) {
3
4
  const db = getDb();
4
- db.prepare("INSERT INTO agent_tasks (task_id, agent_slug, description, origin_channel, instance_id) VALUES (?, ?, ?, ?, ?)").run(taskId, agentSlug, description, originChannel ?? null, instanceId ?? null);
5
- return getTask(taskId);
5
+ const id = randomUUID();
6
+ db.prepare(`INSERT INTO tasks (id, squad_id, instance_id, agent_id, description, status)
7
+ VALUES (?, ?, ?, ?, ?, 'pending')`).run(id, squadId, instanceId ?? null, agentId ?? null, description);
8
+ return db.prepare("SELECT * FROM tasks WHERE id = ?").get(id);
6
9
  }
7
- export function getTask(taskId) {
8
- return getDb()
9
- .prepare("SELECT * FROM agent_tasks WHERE task_id = ?")
10
- .get(taskId);
11
- }
12
- export function getActiveTasks() {
13
- return getDb()
14
- .prepare("SELECT * FROM agent_tasks WHERE status = 'running' ORDER BY started_at DESC")
15
- .all();
16
- }
17
- export function completeTask(taskId, result) {
18
- getDb()
19
- .prepare("UPDATE agent_tasks SET status = 'done', result = ?, completed_at = CURRENT_TIMESTAMP WHERE task_id = ?")
20
- .run(result, taskId);
21
- }
22
- export function failTask(taskId, error) {
23
- getDb()
24
- .prepare("UPDATE agent_tasks SET status = 'failed', result = ?, completed_at = CURRENT_TIMESTAMP WHERE task_id = ?")
25
- .run(error, taskId);
26
- }
27
- export function clearStaleTasks() {
28
- getDb()
29
- .prepare("UPDATE agent_tasks SET status = 'failed', result = 'Marked stale on startup', completed_at = CURRENT_TIMESTAMP WHERE status = 'running'")
30
- .run();
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
- /**
43
- * Per-agent task count for the most recent `limit` tasks belonging to a
44
- * squad. Matches tasks routed to the squad itself (`agent_slug = squadSlug`)
45
- * AND tasks routed to a named agent on the squad (`agent_slug LIKE 'squadSlug:%'`).
46
- * Used by squad_status to surface fan-out imbalance.
47
- */
48
- export function getSquadWorkDistribution(squadSlug, limit = 20) {
49
- const rows = getDb()
50
- .prepare(`SELECT agent_slug FROM agent_tasks
51
- WHERE agent_slug = ? OR agent_slug LIKE ?
52
- ORDER BY datetime(started_at) DESC, task_id DESC
53
- LIMIT ?`)
54
- .all(squadSlug, `${squadSlug}:%`, limit);
55
- const counts = new Map();
56
- for (const row of rows) {
57
- counts.set(row.agent_slug, (counts.get(row.agent_slug) ?? 0) + 1);
58
- }
59
- const perAgent = Array.from(counts.entries())
60
- .map(([agent_slug, count]) => ({ agent_slug, count }))
61
- .sort((a, b) => b.count - a.count);
62
- return { total: rows.length, perAgent };
63
- }
64
- export function createReview(taskId, squadSlug, reviewerCharacter, approved, comments) {
10
+ export function getTasksForSquad(squadId) {
65
11
  const db = getDb();
66
- const info = db
67
- .prepare("INSERT INTO squad_task_reviews (task_id, squad_slug, reviewer_character, approved, comments) VALUES (?, ?, ?, ?, ?)")
68
- .run(taskId, squadSlug, reviewerCharacter, approved ? 1 : 0, comments ?? null);
69
12
  return db
70
- .prepare("SELECT * FROM squad_task_reviews WHERE id = ?")
71
- .get(info.lastInsertRowid);
13
+ .prepare("SELECT * FROM tasks WHERE squad_id = ? ORDER BY created_at DESC")
14
+ .all(squadId);
72
15
  }
73
- export function getTaskReviews(taskId) {
74
- return getDb()
75
- .prepare("SELECT * FROM squad_task_reviews WHERE task_id = ? ORDER BY created_at ASC, id ASC")
76
- .all(taskId);
77
- }
78
- /**
79
- * Per-character delegation stats for a squad.
80
- *
81
- * Returns one row PER CHARACTER NAME passed in `characterNames`, plus an
82
- * extra row with character_name="" for any tasks routed to the bare squad
83
- * slug (legacy lead tasks). Always returns a row for every requested
84
- * character, even if they have never been delegated to (task_count: 0,
85
- * last_delegated_at: null).
86
- *
87
- * Reads from the agent_stats view. Filters with `agent_slug = ?`
88
- * (for the bare slug) and `agent_slug = ?` for each `<slug>:<char>`.
89
- */
90
- export function getAgentTaskStats(squadSlug, characterNames) {
91
- // Build the full set of agent_slug values we care about
92
- const bareSlug = squadSlug;
93
- const namedSlugs = characterNames.map((c) => `${squadSlug}:${c}`);
94
- const allSlugs = [bareSlug, ...namedSlugs];
95
- const placeholders = allSlugs.map(() => "?").join(", ");
96
- const rows = getDb()
97
- .prepare(`SELECT agent_slug, task_count, last_delegated_at FROM agent_stats WHERE agent_slug IN (${placeholders})`)
98
- .all(...allSlugs);
99
- const bySlug = new Map();
100
- for (const row of rows)
101
- bySlug.set(row.agent_slug, row);
102
- const results = [];
103
- // Bare slug row (legacy lead tasks routed without a named agent)
104
- const bareRow = bySlug.get(bareSlug);
105
- results.push({
106
- character_name: "",
107
- agent_slug: bareSlug,
108
- task_count: bareRow?.task_count ?? 0,
109
- last_delegated_at: bareRow?.last_delegated_at ?? null,
110
- });
111
- // One row per requested character
112
- for (const char of characterNames) {
113
- const slug = `${squadSlug}:${char}`;
114
- const row = bySlug.get(slug);
115
- results.push({
116
- character_name: char,
117
- agent_slug: slug,
118
- task_count: row?.task_count ?? 0,
119
- last_delegated_at: row?.last_delegated_at ?? null,
120
- });
16
+ export function updateTaskStatus(taskId, status, result) {
17
+ const db = getDb();
18
+ if (result !== undefined) {
19
+ db.prepare("UPDATE tasks SET status = ?, result = ?, updated_at = datetime('now') WHERE id = ?").run(status, result, taskId);
121
20
  }
122
- return results;
123
- }
124
- /**
125
- * Pick the stalest specialist in a squad. "Stalest" = the character who
126
- * has been delegated to least recently (oldest last_delegated_at), with
127
- * never-delegated agents considered staler than any delegated agent.
128
- *
129
- * Excludes character names listed in `excludeCharacters` (use this to
130
- * skip the lead). Returns null if the squad has no eligible agents OR if
131
- * all eligible agents have been delegated to within `freshIfWithinHours`
132
- * (default 48). The threshold is meant to suppress the hint when the
133
- * squad is already distributing well.
134
- *
135
- * On tie (e.g. two agents have never been delegated), returns the one
136
- * that sorts first by character_name (deterministic).
137
- */
138
- export function getStalestSpecialist(squadSlug, characterNames, options) {
139
- const exclude = new Set(options?.excludeCharacters ?? []);
140
- const freshThresholdHours = options?.freshIfWithinHours ?? 48;
141
- const stats = getAgentTaskStats(squadSlug, characterNames);
142
- // Filter: named agents only (skip the bare-slug "" row), skip excluded
143
- const eligible = stats.filter((s) => s.character_name !== "" && !exclude.has(s.character_name));
144
- if (eligible.length === 0)
145
- return null;
146
- const now = Date.now();
147
- // Sort: never-delegated (null) first, then ascending by last_delegated_at
148
- eligible.sort((a, b) => {
149
- if (a.last_delegated_at === null && b.last_delegated_at === null) {
150
- return a.character_name.localeCompare(b.character_name);
151
- }
152
- if (a.last_delegated_at === null)
153
- return -1;
154
- if (b.last_delegated_at === null)
155
- return 1;
156
- const tA = new Date(a.last_delegated_at + "Z").getTime();
157
- const tB = new Date(b.last_delegated_at + "Z").getTime();
158
- if (tA !== tB)
159
- return tA - tB;
160
- return a.character_name.localeCompare(b.character_name);
161
- });
162
- const stalest = eligible[0];
163
- let staleHours = null;
164
- if (stalest.last_delegated_at !== null) {
165
- const delegatedAt = new Date(stalest.last_delegated_at + "Z").getTime();
166
- staleHours = Math.round((now - delegatedAt) / 3_600_000);
167
- // Squad is distributing well — suppress the hint
168
- if (staleHours < freshThresholdHours)
169
- return null;
21
+ else {
22
+ db.prepare("UPDATE tasks SET status = ?, updated_at = datetime('now') WHERE id = ?").run(status, taskId);
170
23
  }
171
- // null last_delegated_at means never-delegated: always considered stale
172
- return {
173
- character_name: stalest.character_name,
174
- last_delegated_at: stalest.last_delegated_at,
175
- staleHours,
176
- };
24
+ }
25
+ export function getTask(taskId) {
26
+ const db = getDb();
27
+ return db.prepare("SELECT * FROM tasks WHERE id = ?").get(taskId);
28
+ }
29
+ export function getActiveTasksForInstance(instanceId) {
30
+ const db = getDb();
31
+ return db
32
+ .prepare("SELECT * FROM tasks WHERE instance_id = ? AND status NOT IN ('done', 'failed') ORDER BY created_at")
33
+ .all(instanceId);
177
34
  }
178
35
  //# sourceMappingURL=tasks.js.map