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.
- package/README.md +40 -52
- package/dist/api/auth.js +35 -38
- package/dist/api/server.js +157 -1139
- package/dist/config.js +49 -32
- package/dist/copilot/agents.js +72 -1055
- package/dist/copilot/client.js +6 -17
- package/dist/copilot/io-scheduler.js +55 -139
- package/dist/copilot/model-router.js +100 -72
- package/dist/copilot/orchestrator.js +91 -515
- package/dist/copilot/scheduler.js +67 -189
- package/dist/copilot/skills.js +41 -366
- package/dist/copilot/system-message.js +40 -200
- package/dist/copilot/tools.js +191 -2042
- package/dist/daemon.js +54 -201
- package/dist/index.js +15 -133
- package/dist/mcp/config.js +23 -31
- package/dist/mcp/index.js +2 -3
- package/dist/mcp/registry.js +33 -88
- package/dist/notify.js +18 -100
- package/dist/paths.js +13 -24
- package/dist/setup.js +35 -0
- package/dist/store/db.js +111 -297
- package/dist/store/feed.js +29 -97
- package/dist/store/instances.js +56 -121
- package/dist/store/schedules.js +21 -73
- package/dist/store/squads.js +35 -186
- package/dist/store/tasks.js +25 -168
- package/dist/telegram/bot.js +20 -312
- package/dist/telegram/handlers.js +39 -3
- package/dist/watchdog.js +31 -45
- package/dist/wiki/fs.js +38 -155
- package/dist/wiki/search.js +31 -44
- package/package.json +5 -8
- package/web-dist/assets/ChatView-EFFiln1H.js +11 -0
- package/web-dist/assets/FeedView-bN4NMOL7.js +6 -0
- package/web-dist/assets/LoginView-CNtasq3n.js +1 -0
- package/web-dist/assets/McpView-C2CHiwsi.js +1 -0
- package/web-dist/assets/SchedulesView-CyilLban.js +1 -0
- package/web-dist/assets/SettingsView-1wLXKEF4.js +1 -0
- package/web-dist/assets/SkillsView-BLsD-0u0.js +1 -0
- package/web-dist/assets/SquadDetailView-CsCw2ZLp.js +21 -0
- package/web-dist/assets/SquadsView-DQ3vFlyO.js +6 -0
- package/web-dist/assets/WikiView-19M3oqnq.js +21 -0
- package/web-dist/assets/api-WGvTsXaE.js +1 -0
- package/web-dist/assets/index-D7M5O-_l.css +1 -0
- package/web-dist/assets/index-DZOS9syn.js +95 -0
- package/web-dist/assets/plus-BOvyX1BC.js +6 -0
- package/web-dist/assets/trash-2-DHoetkC4.js +6 -0
- package/web-dist/favicon.svg +4 -1
- package/web-dist/index.html +7 -10
- package/dist/api/logout.test.js +0 -129
- package/dist/api/mcp.test.js +0 -285
- package/dist/api/wiki.test.js +0 -283
- package/dist/auth/session-logic.js +0 -79
- package/dist/auth/session-logic.test.js +0 -201
- package/dist/copilot/auto-complete-instance.test.js +0 -104
- package/dist/copilot/cron.js +0 -136
- package/dist/copilot/event-summary.js +0 -286
- package/dist/copilot/instance-deactivate.test.js +0 -119
- package/dist/copilot/model-router.test.js +0 -71
- package/dist/copilot/review-backfill.js +0 -57
- package/dist/copilot/session-timeout.js +0 -112
- package/dist/copilot/session-timeout.test.js +0 -372
- package/dist/copilot/skills.test.js +0 -55
- package/dist/copilot/universes.js +0 -469
- package/dist/instance-watchdog.js +0 -104
- package/dist/instance-watchdog.test.js +0 -183
- package/dist/mcp/client.js +0 -109
- package/dist/mcp/client.test.js +0 -99
- package/dist/mcp/config.test.js +0 -49
- package/dist/mcp/registry.test.js +0 -79
- package/dist/notify.test.js +0 -232
- package/dist/store/feed.test.js +0 -279
- package/dist/store/instances.test.js +0 -310
- package/dist/store/io-schedules.js +0 -63
- package/dist/store/notifications.js +0 -79
- package/dist/store/notifications.test.js +0 -197
- package/dist/store/schedule-runs.js +0 -46
- package/dist/store/squads.test.js +0 -405
- package/dist/store/tasks.test.js +0 -150
- package/dist/store/worktrees.js +0 -83
- package/dist/tui/index.js +0 -286
- package/dist/update.js +0 -81
- package/dist/watchdog.test.js +0 -83
- package/dist/wiki/wiki-squad.test.js +0 -54
- package/web-dist/assets/AgentActivityView-CedxxE6K.js +0 -1
- package/web-dist/assets/ChatView-DMkYQo_V.js +0 -4
- package/web-dist/assets/FeedView-BH4q-31V.js +0 -1
- package/web-dist/assets/InboxView-BVwVP4EW.js +0 -1
- package/web-dist/assets/LoginView-DRPDhnwu.js +0 -1
- package/web-dist/assets/McpView-D8yWz-lq.js +0 -1
- package/web-dist/assets/SchedulesView-BzzyncGF.js +0 -1
- package/web-dist/assets/SettingsTabs.vue_vue_type_script_setup_true_lang-oW3ySu7Y.js +0 -1
- package/web-dist/assets/SkillsView-oxpYuhx7.js +0 -1
- package/web-dist/assets/SquadsView-CaKUIKlq.js +0 -1
- package/web-dist/assets/StatusIndicator.vue_vue_type_script_setup_true_lang-8U15Qp_Q.js +0 -1
- package/web-dist/assets/WikiView-C5jXUlfW.js +0 -1
- package/web-dist/assets/index-BrWzNw-N.css +0 -10
- package/web-dist/assets/index-f67odrrt.js +0 -81
- package/web-dist/icons.svg +0 -24
package/dist/store/squads.js
CHANGED
|
@@ -1,201 +1,50 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { getDb } from "./db.js";
|
|
2
|
-
|
|
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
|
|
36
|
-
|
|
37
|
-
|
|
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(
|
|
44
|
-
|
|
45
|
-
|
|
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("
|
|
69
|
-
db.prepare("
|
|
70
|
-
|
|
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
|
|
121
|
-
getDb()
|
|
122
|
-
|
|
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
|
|
167
|
-
|
|
168
|
-
|
|
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
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
|
34
|
+
export function getAgentsForSquad(squadId) {
|
|
184
35
|
const db = getDb();
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
.
|
|
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
|
|
197
|
-
getDb()
|
|
198
|
-
|
|
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
|
package/dist/store/tasks.js
CHANGED
|
@@ -1,178 +1,35 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { getDb } from "./db.js";
|
|
2
|
-
export function createTask(
|
|
3
|
+
export function createTask(squadId, description, instanceId, agentId) {
|
|
3
4
|
const db = getDb();
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
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
|
|
71
|
-
.
|
|
13
|
+
.prepare("SELECT * FROM tasks WHERE squad_id = ? ORDER BY created_at DESC")
|
|
14
|
+
.all(squadId);
|
|
72
15
|
}
|
|
73
|
-
export function
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|