heyio 1.2.4 → 1.3.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/dist/api/server.js +267 -12
- package/dist/config.js +6 -0
- package/dist/copilot/agents.js +61 -4
- package/dist/copilot/ceremonies.js +12 -2
- package/dist/copilot/io-scheduler.js +9 -1
- package/dist/copilot/orchestrator.js +2 -0
- package/dist/copilot/scheduler.js +4 -0
- package/dist/copilot/skills.js +138 -6
- package/dist/copilot/token-tracker.js +89 -0
- package/dist/copilot/tools.js +27 -5
- package/dist/paths.js +1 -0
- package/dist/store/agent-events.js +19 -0
- package/dist/store/audit-log.js +71 -0
- package/dist/store/conversations.js +150 -0
- package/dist/store/db.js +111 -0
- package/dist/store/schedules.js +5 -1
- package/dist/store/squad-colors.js +21 -0
- package/dist/store/squads.js +6 -1
- package/dist/store/tasks.js +43 -0
- package/dist/store/token-usage.js +94 -0
- package/dist/wiki/backlinks.js +51 -0
- package/dist/wiki/fs.js +63 -1
- package/package.json +1 -1
- package/web-dist/assets/AuditLogView-xgSZ2MOJ.js +6 -0
- package/web-dist/assets/ChatView-BU3Jvu5y.js +11 -0
- package/web-dist/assets/FeedView-BwkWbe1p.js +6 -0
- package/web-dist/assets/HistoryView-Doh9Y3Na.js +1 -0
- package/web-dist/assets/LoginView-CoTEOrwE.js +1 -0
- package/web-dist/assets/{MarkdownContent.vue_vue_type_script_setup_true_lang-CEo_ckIb.js → MarkdownContent.vue_vue_type_script_setup_true_lang-CObjuCHH.js} +1 -1
- package/web-dist/assets/McpView-ByXoAnED.js +1 -0
- package/web-dist/assets/SchedulesView-BkUdRYwk.js +1 -0
- package/web-dist/assets/SettingsView-_q-IpzFy.js +1 -0
- package/web-dist/assets/SkillsView-_FkOdD2U.js +15 -0
- package/web-dist/assets/SquadDetailView-CV6_n_If.js +31 -0
- package/web-dist/assets/SquadHealthView-DmQqPq7H.js +11 -0
- package/web-dist/assets/SquadsView-Dkhtu5MQ.js +6 -0
- package/web-dist/assets/UsageView-CCS6pp6n.js +16 -0
- package/web-dist/assets/WikiView-CpXzff_L.js +31 -0
- package/web-dist/assets/api-CaqVk-rG.js +1 -0
- package/web-dist/assets/arrow-left-CkDjCT7Z.js +6 -0
- package/web-dist/assets/git-branch-Bu9s__XL.js +6 -0
- package/web-dist/assets/index-D3DNfwXI.css +1 -0
- package/web-dist/assets/{index-BQdXxKfc.js → index-DfdD_qE4.js} +56 -36
- package/web-dist/assets/{plus-Cvp1w2CO.js → plus-GvGwcjX5.js} +1 -1
- package/web-dist/assets/{x-O3fBd1Cr.js → save-fQ_rr5hX.js} +2 -7
- package/web-dist/assets/search-C3fxUixl.js +6 -0
- package/web-dist/assets/squad-colors-B8B_Y-lz.js +1 -0
- package/web-dist/assets/{trash-2-Cr3vrmL5.js → trash-2-Ba_1SAua.js} +1 -1
- package/web-dist/assets/triangle-alert-BTBlX3kg.js +6 -0
- package/web-dist/assets/x-CJifAZQa.js +6 -0
- package/web-dist/favicon.svg +9 -3
- package/web-dist/index.html +2 -2
- package/web-dist/logo.svg +10 -0
- package/web-dist/assets/ChatView-mZaaw3pd.js +0 -11
- package/web-dist/assets/FeedView-BHacQwXQ.js +0 -6
- package/web-dist/assets/LoginView-B6aSD9II.js +0 -1
- package/web-dist/assets/McpView-BAVRUHIE.js +0 -1
- package/web-dist/assets/SchedulesView-dOd1SQiP.js +0 -1
- package/web-dist/assets/SettingsView-CCDeEsVg.js +0 -1
- package/web-dist/assets/SkillsView-gCfQ35FQ.js +0 -1
- package/web-dist/assets/SquadDetailView-CQhFfZTc.js +0 -21
- package/web-dist/assets/SquadsView-CZFxtOao.js +0 -6
- package/web-dist/assets/WikiView-B0cuUFfm.js +0 -26
- package/web-dist/assets/api-DdW5uOZf.js +0 -1
- package/web-dist/assets/index-BbSJ0cfF.css +0 -1
package/dist/api/server.js
CHANGED
|
@@ -6,14 +6,20 @@ import { loadConfig, saveConfig } from "../config.js";
|
|
|
6
6
|
import { createAuthMiddleware } from "./auth.js";
|
|
7
7
|
import { sendToOrchestrator } from "../copilot/orchestrator.js";
|
|
8
8
|
import { listSquads, getSquad, getAgentsForSquad } from "../store/squads.js";
|
|
9
|
-
import { getTasksForSquad } from "../store/tasks.js";
|
|
10
|
-
import { getInstancesForSquad } from "../store/instances.js";
|
|
9
|
+
import { getTasksForSquad, getSquadTaskMetrics } from "../store/tasks.js";
|
|
10
|
+
import { getInstancesForSquad, destroyInstance } from "../store/instances.js";
|
|
11
|
+
import { getAgentEvents } from "../store/agent-events.js";
|
|
12
|
+
import { getAuditLog, countAuditLog } from "../store/audit-log.js";
|
|
11
13
|
import { getFeedItems, markFeedItemRead, deleteFeedItem, getUnreadCount, } from "../store/feed.js";
|
|
12
14
|
import { listSchedules, createSchedule, deleteSchedule, toggleSchedule } from "../store/schedules.js";
|
|
13
15
|
import { listServers, toggleMcpServer, addMcpServer, removeMcpServer } from "../mcp/index.js";
|
|
14
|
-
import { listSkills, addSkill, removeSkill, getSkillContent, updateSkillContent } from "../copilot/skills.js";
|
|
15
|
-
import { readPage, writePage, deletePage, listPages } from "../wiki/fs.js";
|
|
16
|
+
import { listSkills, addSkill, createSkill, removeSkill, getSkillContent, updateSkillContent, discoverSkills, installFromSource, fetchRemoteSkillPreview } from "../copilot/skills.js";
|
|
17
|
+
import { readPage, writePage, deletePage, listPages, listTemplates, readTemplate, writeTemplate, deleteTemplate } from "../wiki/fs.js";
|
|
16
18
|
import { searchPages } from "../wiki/search.js";
|
|
19
|
+
import { getBacklinks } from "../wiki/backlinks.js";
|
|
20
|
+
import { saveMessage, getConversation, listConversations, searchConversations, deleteConversation, } from "../store/conversations.js";
|
|
21
|
+
import { getTokenUsageSummary, getTokenUsageBySquad, getTokenUsageByAgent, getDailyTokenUsage, } from "../store/token-usage.js";
|
|
22
|
+
import { DEFAULT_MODEL_PRICING } from "../copilot/token-tracker.js";
|
|
17
23
|
import { randomUUID } from "node:crypto";
|
|
18
24
|
const __filename = fileURLToPath(import.meta.url);
|
|
19
25
|
const __dirname = dirname(__filename);
|
|
@@ -57,22 +63,94 @@ export async function startApiServer(config) {
|
|
|
57
63
|
});
|
|
58
64
|
// --- Chat ---
|
|
59
65
|
app.post("/api/message", async (req, res) => {
|
|
60
|
-
const { prompt } = req.body;
|
|
66
|
+
const { prompt, conversationId: clientConvId } = req.body;
|
|
61
67
|
if (!prompt || typeof prompt !== "string") {
|
|
62
68
|
res.status(400).json({ error: "prompt is required" });
|
|
63
69
|
return;
|
|
64
70
|
}
|
|
71
|
+
const conversationId = (typeof clientConvId === "string" && clientConvId) ? clientConvId : randomUUID();
|
|
72
|
+
// Persist the user message
|
|
73
|
+
saveMessage(conversationId, "user", prompt, "web");
|
|
65
74
|
// Stream response via SSE, send final to HTTP response
|
|
66
75
|
await sendToOrchestrator(prompt, "web", (content, done) => {
|
|
67
76
|
broadcast("message_delta", { content, done });
|
|
68
77
|
if (done) {
|
|
69
|
-
|
|
78
|
+
// Persist the assistant response
|
|
79
|
+
saveMessage(conversationId, "assistant", content, "web");
|
|
80
|
+
res.json({ content, conversationId });
|
|
70
81
|
}
|
|
71
82
|
});
|
|
72
83
|
});
|
|
84
|
+
// --- History ---
|
|
85
|
+
app.get("/api/history", (req, res) => {
|
|
86
|
+
const q = req.query.q;
|
|
87
|
+
const from = req.query.from;
|
|
88
|
+
const to = req.query.to;
|
|
89
|
+
const limit = parseInt(req.query.limit) || 50;
|
|
90
|
+
const offset = parseInt(req.query.offset) || 0;
|
|
91
|
+
if (q) {
|
|
92
|
+
res.json(searchConversations(q, { limit, offset, from, to }));
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
res.json(listConversations({ limit, offset, from, to }));
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
app.get("/api/history/:id", (req, res) => {
|
|
99
|
+
const messages = getConversation(req.params.id);
|
|
100
|
+
if (messages.length === 0) {
|
|
101
|
+
res.status(404).json({ error: "Conversation not found" });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
res.json(messages);
|
|
105
|
+
});
|
|
106
|
+
app.delete("/api/history/:id", (req, res) => {
|
|
107
|
+
deleteConversation(req.params.id);
|
|
108
|
+
res.json({ ok: true });
|
|
109
|
+
});
|
|
73
110
|
// --- Squads ---
|
|
74
111
|
app.get("/api/squads", (_req, res) => {
|
|
75
|
-
|
|
112
|
+
const data = listSquads();
|
|
113
|
+
const instanceCounts = {};
|
|
114
|
+
for (const squad of data.squads) {
|
|
115
|
+
instanceCounts[squad.id] = getInstancesForSquad(squad.id).length;
|
|
116
|
+
}
|
|
117
|
+
res.json({ ...data, instanceCounts });
|
|
118
|
+
});
|
|
119
|
+
// --- Squad Health Dashboard ---
|
|
120
|
+
app.get("/api/squads/health", (_req, res) => {
|
|
121
|
+
const { squads, agents } = listSquads();
|
|
122
|
+
const health = squads.map((squad) => {
|
|
123
|
+
const squadAgents = agents.filter((a) => a.squad_id === squad.id);
|
|
124
|
+
const instances = getInstancesForSquad(squad.id);
|
|
125
|
+
const metrics = getSquadTaskMetrics(squad.id);
|
|
126
|
+
return {
|
|
127
|
+
id: squad.id,
|
|
128
|
+
name: squad.name,
|
|
129
|
+
universe: squad.universe,
|
|
130
|
+
agentCount: squadAgents.length,
|
|
131
|
+
activeInstanceCount: instances.length,
|
|
132
|
+
activeInstances: instances.map((inst) => ({
|
|
133
|
+
id: inst.id,
|
|
134
|
+
branch: inst.branch,
|
|
135
|
+
lastActivity: inst.last_activity,
|
|
136
|
+
})),
|
|
137
|
+
tasksTotal: metrics.tasksTotal,
|
|
138
|
+
tasksCompleted: metrics.tasksCompleted,
|
|
139
|
+
tasksCompletedRecent: metrics.tasksCompletedRecent,
|
|
140
|
+
tasksPending: metrics.tasksPending,
|
|
141
|
+
tasksInProgress: metrics.tasksInProgress,
|
|
142
|
+
tasksFailed: metrics.tasksFailed,
|
|
143
|
+
avgCycleTimeMinutes: metrics.avgCycleTimeMinutes,
|
|
144
|
+
isStalled: metrics.isStalled,
|
|
145
|
+
recentTasks: metrics.recentTasks.map((t) => ({
|
|
146
|
+
id: t.id,
|
|
147
|
+
description: t.description,
|
|
148
|
+
status: t.status,
|
|
149
|
+
updatedAt: t.updated_at,
|
|
150
|
+
})),
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
res.json({ health });
|
|
76
154
|
});
|
|
77
155
|
app.get("/api/squads/:id", (req, res) => {
|
|
78
156
|
const squad = getSquad(req.params.id);
|
|
@@ -85,6 +163,37 @@ export async function startApiServer(config) {
|
|
|
85
163
|
const instances = getInstancesForSquad(req.params.id);
|
|
86
164
|
res.json({ squad, agents, tasks, instances });
|
|
87
165
|
});
|
|
166
|
+
app.delete("/api/instances/:id", async (req, res) => {
|
|
167
|
+
try {
|
|
168
|
+
await destroyInstance(req.params.id);
|
|
169
|
+
res.json({ ok: true });
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
const msg = err?.message ?? "Unknown error";
|
|
173
|
+
const status = msg.toLowerCase().includes("not found") ? 404 : 500;
|
|
174
|
+
res.status(status).json({ error: msg });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
// --- Task Events ---
|
|
178
|
+
app.get("/api/tasks/:taskId/events", (req, res) => {
|
|
179
|
+
const events = getAgentEvents(req.params.taskId);
|
|
180
|
+
res.json(events);
|
|
181
|
+
});
|
|
182
|
+
// --- Audit Log ---
|
|
183
|
+
app.get("/api/audit-log", (req, res) => {
|
|
184
|
+
const squad_id = req.query.squad_id;
|
|
185
|
+
const agent_id = req.query.agent_id;
|
|
186
|
+
const action_type = req.query.action_type;
|
|
187
|
+
const from = req.query.from;
|
|
188
|
+
const to = req.query.to;
|
|
189
|
+
const limit = parseInt(req.query.limit) || 50;
|
|
190
|
+
const offset = parseInt(req.query.offset) || 0;
|
|
191
|
+
const filters = { squad_id, agent_id, action_type, from, to, limit, offset };
|
|
192
|
+
res.json({
|
|
193
|
+
entries: getAuditLog(filters),
|
|
194
|
+
total: countAuditLog(filters),
|
|
195
|
+
});
|
|
196
|
+
});
|
|
88
197
|
// --- Feed ---
|
|
89
198
|
app.get("/api/feed", (req, res) => {
|
|
90
199
|
const unreadOnly = req.query.unread === "true";
|
|
@@ -129,14 +238,62 @@ export async function startApiServer(config) {
|
|
|
129
238
|
const skills = await listSkills();
|
|
130
239
|
res.json(skills);
|
|
131
240
|
});
|
|
241
|
+
app.get("/api/skills/discover", async (req, res) => {
|
|
242
|
+
const source = req.query.source;
|
|
243
|
+
if (source !== "awesome-copilot" && source !== "skillssh") {
|
|
244
|
+
res.status(400).json({ error: "source must be 'awesome-copilot' or 'skillssh'" });
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const q = req.query.q;
|
|
248
|
+
try {
|
|
249
|
+
const skills = await discoverSkills(source, q);
|
|
250
|
+
res.json(skills);
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
res.status(502).json({ error: err.message });
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
app.get("/api/skills/preview", async (req, res) => {
|
|
257
|
+
const source = req.query.source;
|
|
258
|
+
const slug = req.query.slug;
|
|
259
|
+
if (source !== "awesome-copilot" && source !== "skillssh") {
|
|
260
|
+
res.status(400).json({ error: "source must be 'awesome-copilot' or 'skillssh'" });
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (!slug) {
|
|
264
|
+
res.status(400).json({ error: "slug is required" });
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
const content = await fetchRemoteSkillPreview(source, slug);
|
|
269
|
+
res.json({ content });
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
res.status(502).json({ error: err.message });
|
|
273
|
+
}
|
|
274
|
+
});
|
|
132
275
|
app.post("/api/skills", async (req, res) => {
|
|
133
276
|
try {
|
|
134
|
-
const { url } = req.body;
|
|
135
|
-
if (
|
|
136
|
-
|
|
277
|
+
const { url, source, slug, content } = req.body;
|
|
278
|
+
if (source && slug) {
|
|
279
|
+
if (source !== "awesome-copilot" && source !== "skillssh") {
|
|
280
|
+
res.status(400).json({ error: "source must be 'awesome-copilot' or 'skillssh'" });
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
await installFromSource(source, slug);
|
|
284
|
+
}
|
|
285
|
+
else if (url && typeof url === "string") {
|
|
286
|
+
// Git-clone method
|
|
287
|
+
await addSkill(url);
|
|
288
|
+
}
|
|
289
|
+
else if (slug && typeof slug === "string" && content && typeof content === "string") {
|
|
290
|
+
// Direct-creation method
|
|
291
|
+
await createSkill(slug, content);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
res.status(400).json({ error: "Provide 'url' (git clone), 'source' + 'slug' (community install), or 'slug' + 'content' (direct create)" });
|
|
137
295
|
return;
|
|
138
296
|
}
|
|
139
|
-
await addSkill(url);
|
|
140
297
|
res.status(201).json({ ok: true });
|
|
141
298
|
}
|
|
142
299
|
catch (err) {
|
|
@@ -214,13 +371,66 @@ export async function startApiServer(config) {
|
|
|
214
371
|
const results = await searchPages(query);
|
|
215
372
|
res.json(results);
|
|
216
373
|
});
|
|
374
|
+
app.get("/api/wiki/backlinks/*path", async (req, res) => {
|
|
375
|
+
const raw = req.params.path;
|
|
376
|
+
const pagePath = Array.isArray(raw) ? raw.join("/") : raw;
|
|
377
|
+
const backlinks = await getBacklinks(pagePath);
|
|
378
|
+
res.json(backlinks);
|
|
379
|
+
});
|
|
380
|
+
// --- Wiki Templates ---
|
|
381
|
+
app.get("/api/wiki/templates/squad", async (_req, res) => {
|
|
382
|
+
const files = await listTemplates();
|
|
383
|
+
res.json(files);
|
|
384
|
+
});
|
|
385
|
+
app.get("/api/wiki/template/squad/*path", async (req, res) => {
|
|
386
|
+
try {
|
|
387
|
+
const raw = req.params.path;
|
|
388
|
+
const templatePath = Array.isArray(raw) ? raw.join("/") : raw;
|
|
389
|
+
const content = await readTemplate(templatePath);
|
|
390
|
+
res.json({ path: templatePath, content });
|
|
391
|
+
}
|
|
392
|
+
catch (err) {
|
|
393
|
+
res.status(404).json({ error: err.message });
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
app.put("/api/wiki/template/squad/*path", async (req, res) => {
|
|
397
|
+
const raw = req.params.path;
|
|
398
|
+
const templatePath = Array.isArray(raw) ? raw.join("/") : raw;
|
|
399
|
+
const { content } = req.body;
|
|
400
|
+
await writeTemplate(templatePath, content);
|
|
401
|
+
res.json({ ok: true });
|
|
402
|
+
});
|
|
403
|
+
app.delete("/api/wiki/template/squad/*path", async (req, res) => {
|
|
404
|
+
try {
|
|
405
|
+
const raw = req.params.path;
|
|
406
|
+
const templatePath = Array.isArray(raw) ? raw.join("/") : raw;
|
|
407
|
+
await deleteTemplate(templatePath);
|
|
408
|
+
res.json({ ok: true });
|
|
409
|
+
}
|
|
410
|
+
catch (err) {
|
|
411
|
+
res.status(404).json({ error: err.message });
|
|
412
|
+
}
|
|
413
|
+
});
|
|
217
414
|
// --- Schedules ---
|
|
218
415
|
app.get("/api/schedules", (_req, res) => {
|
|
219
416
|
const type = undefined; // return all
|
|
220
417
|
res.json(listSchedules(type));
|
|
221
418
|
});
|
|
222
419
|
app.post("/api/schedules", (req, res) => {
|
|
223
|
-
const
|
|
420
|
+
const { type, cron, squad_id, agenda, prompt } = req.body ?? {};
|
|
421
|
+
if (type !== "squad" && type !== "io") {
|
|
422
|
+
res.status(400).json({ error: "type must be 'squad' or 'io'" });
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (!cron || typeof cron !== "string") {
|
|
426
|
+
res.status(400).json({ error: "cron is required" });
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
if (!squad_id || typeof squad_id !== "string" || !squad_id.trim()) {
|
|
430
|
+
res.status(400).json({ error: "squad_id is required" });
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
const schedule = createSchedule({ type, cron, squad_id, agenda, prompt });
|
|
224
434
|
res.json(schedule);
|
|
225
435
|
});
|
|
226
436
|
app.put("/api/schedules/:id", (req, res) => {
|
|
@@ -284,6 +494,51 @@ export async function startApiServer(config) {
|
|
|
284
494
|
saveConfig(updates);
|
|
285
495
|
res.json({ ok: true });
|
|
286
496
|
});
|
|
497
|
+
// --- Token Usage ---
|
|
498
|
+
app.get("/api/token-usage/summary", (req, res) => {
|
|
499
|
+
const since = req.query.since;
|
|
500
|
+
res.json(getTokenUsageSummary({ since }));
|
|
501
|
+
});
|
|
502
|
+
app.get("/api/token-usage/by-squad", (req, res) => {
|
|
503
|
+
const since = req.query.since;
|
|
504
|
+
res.json(getTokenUsageBySquad({ since }));
|
|
505
|
+
});
|
|
506
|
+
app.get("/api/token-usage/by-agent", (req, res) => {
|
|
507
|
+
const since = req.query.since;
|
|
508
|
+
const squadId = req.query.squad_id;
|
|
509
|
+
res.json(getTokenUsageByAgent({ since, squadId }));
|
|
510
|
+
});
|
|
511
|
+
app.get("/api/token-usage/daily", (req, res) => {
|
|
512
|
+
const days = parseInt(req.query.days) || 30;
|
|
513
|
+
res.json(getDailyTokenUsage(days));
|
|
514
|
+
});
|
|
515
|
+
app.get("/api/token-usage/pricing", (_req, res) => {
|
|
516
|
+
const config = loadConfig();
|
|
517
|
+
const merged = { ...DEFAULT_MODEL_PRICING, ...(config.modelPricing ?? {}) };
|
|
518
|
+
res.json(merged);
|
|
519
|
+
});
|
|
520
|
+
app.put("/api/token-usage/pricing", (req, res) => {
|
|
521
|
+
const pricing = req.body;
|
|
522
|
+
if (typeof pricing !== "object" || pricing === null) {
|
|
523
|
+
res.status(400).json({ error: "Expected object body" });
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
saveConfig({ modelPricing: pricing });
|
|
527
|
+
res.json({ ok: true });
|
|
528
|
+
});
|
|
529
|
+
app.get("/api/token-usage/alert-threshold", (_req, res) => {
|
|
530
|
+
const config = loadConfig();
|
|
531
|
+
res.json({ tokenAlertThreshold: config.tokenAlertThreshold ?? null });
|
|
532
|
+
});
|
|
533
|
+
app.put("/api/token-usage/alert-threshold", (req, res) => {
|
|
534
|
+
const { tokenAlertThreshold } = req.body;
|
|
535
|
+
if (tokenAlertThreshold !== null && typeof tokenAlertThreshold !== "number") {
|
|
536
|
+
res.status(400).json({ error: "tokenAlertThreshold must be a number or null" });
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
saveConfig({ tokenAlertThreshold: tokenAlertThreshold ?? undefined });
|
|
540
|
+
res.json({ ok: true });
|
|
541
|
+
});
|
|
287
542
|
// --- Health (unauthenticated) ---
|
|
288
543
|
app.get("/health", (_req, res) => {
|
|
289
544
|
res.json({ status: "ok", version: process.env.npm_package_version ?? "unknown" });
|
package/dist/config.js
CHANGED
|
@@ -2,6 +2,10 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { PATHS } from "./paths.js";
|
|
5
|
+
const ModelPriceSchema = z.object({
|
|
6
|
+
inputPer1M: z.number(),
|
|
7
|
+
outputPer1M: z.number(),
|
|
8
|
+
});
|
|
5
9
|
const ConfigSchema = z.object({
|
|
6
10
|
telegramBotToken: z.string().optional(),
|
|
7
11
|
authorizedUserId: z.number().optional(),
|
|
@@ -17,6 +21,8 @@ const ConfigSchema = z.object({
|
|
|
17
21
|
.default("meaningful"),
|
|
18
22
|
backgroundNotifyTelegram: z.boolean().default(true),
|
|
19
23
|
watchdogEnabled: z.boolean().default(true),
|
|
24
|
+
modelPricing: z.record(z.string(), ModelPriceSchema).optional(),
|
|
25
|
+
tokenAlertThreshold: z.number().optional(),
|
|
20
26
|
});
|
|
21
27
|
let cachedConfig;
|
|
22
28
|
export function loadConfig() {
|
package/dist/copilot/agents.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { approveAll } from "@github/copilot-sdk";
|
|
2
2
|
import { getClient } from "./client.js";
|
|
3
|
-
import { getLeadForSquad, getAgentsForSquad, updateAgentStatus } from "../store/squads.js";
|
|
3
|
+
import { getLeadForSquad, getAgentsForSquad, updateAgentStatus, getSquad } from "../store/squads.js";
|
|
4
4
|
import { createTask, updateTaskStatus } from "../store/tasks.js";
|
|
5
5
|
import { touchInstanceActivity } from "../store/instances.js";
|
|
6
6
|
import { selectModel, classifyComplexity } from "./model-router.js";
|
|
7
7
|
import { postFeedItem } from "../store/feed.js";
|
|
8
|
+
import { attachTokenTracker } from "./token-tracker.js";
|
|
9
|
+
import { addAuditEntry } from "../store/audit-log.js";
|
|
10
|
+
import { addAgentEvent } from "../store/agent-events.js";
|
|
8
11
|
export async function delegateTask(squadId, task, instanceId) {
|
|
9
12
|
const lead = getLeadForSquad(squadId);
|
|
10
13
|
if (!lead) {
|
|
@@ -21,6 +24,8 @@ export async function delegateTask(squadId, task, instanceId) {
|
|
|
21
24
|
// Select model based on task complexity
|
|
22
25
|
const tier = classifyComplexity(task);
|
|
23
26
|
const model = await selectModel(tier);
|
|
27
|
+
// Audit: task delegated
|
|
28
|
+
addAuditEntry("task_delegated", `Task delegated to ${lead.character_name} (${lead.role_title})`, { task: task.slice(0, 500), model }, { squad_id: squadId, agent_id: lead.id, task_id: taskRecord.id });
|
|
24
29
|
// Create ephemeral agent session for the lead
|
|
25
30
|
const client = await getClient();
|
|
26
31
|
const agentRoster = agents
|
|
@@ -70,25 +75,77 @@ ${lead.persona ? `## Personality:\n${lead.persona}` : ""}
|
|
|
70
75
|
bufferExhaustionThreshold: 0.95,
|
|
71
76
|
},
|
|
72
77
|
});
|
|
78
|
+
const flushTokens = attachTokenTracker(session, {
|
|
79
|
+
squadId,
|
|
80
|
+
agentId: lead.id,
|
|
81
|
+
taskId: taskRecord.id,
|
|
82
|
+
});
|
|
73
83
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
// Mark task as in progress and record start event
|
|
85
|
+
updateTaskStatus(taskRecord.id, "in_progress");
|
|
86
|
+
addAgentEvent(taskRecord.id, "status", `Task started by ${lead.character_name}`, {
|
|
87
|
+
agent: lead.character_name,
|
|
88
|
+
role: lead.role_title,
|
|
89
|
+
task,
|
|
90
|
+
});
|
|
91
|
+
// Capture streaming message deltas and broadcast via SSE
|
|
92
|
+
let accumulatedMessage = "";
|
|
93
|
+
const { broadcast } = await import("../api/server.js");
|
|
94
|
+
const unsubscribeDelta = session.on("assistant.message_delta", (event) => {
|
|
95
|
+
const delta = event.data?.deltaContent ?? "";
|
|
96
|
+
if (delta) {
|
|
97
|
+
accumulatedMessage += delta;
|
|
98
|
+
broadcast("agent_event", {
|
|
99
|
+
taskId: taskRecord.id,
|
|
100
|
+
type: "message_delta",
|
|
101
|
+
summary: accumulatedMessage,
|
|
102
|
+
payload: { delta, accumulated: accumulatedMessage },
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
try {
|
|
107
|
+
const response = await session.sendAndWait({ prompt: `Task delegated to you:\n\n${task}` }, 600_000);
|
|
108
|
+
result = response?.data?.content ?? "Task completed (no response content).";
|
|
109
|
+
// Record the final message event if we have meaningful content
|
|
110
|
+
if (accumulatedMessage.trim()) {
|
|
111
|
+
addAgentEvent(taskRecord.id, "message", accumulatedMessage, {
|
|
112
|
+
agent: lead.character_name,
|
|
113
|
+
content: accumulatedMessage,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
unsubscribeDelta();
|
|
119
|
+
}
|
|
76
120
|
}
|
|
77
121
|
finally {
|
|
122
|
+
flushTokens();
|
|
78
123
|
await session.disconnect();
|
|
79
124
|
}
|
|
80
125
|
}
|
|
81
126
|
catch (err) {
|
|
82
127
|
const errMsg = err instanceof Error ? err.message : "Unknown error";
|
|
128
|
+
addAgentEvent(taskRecord.id, "status", `Task failed: ${errMsg}`, { error: errMsg });
|
|
83
129
|
updateTaskStatus(taskRecord.id, "failed", errMsg);
|
|
84
130
|
updateAgentStatus(lead.id, "idle");
|
|
131
|
+
// Audit: task failed
|
|
132
|
+
addAuditEntry("task_failed", `Task failed: ${errMsg.slice(0, 200)}`, { error: errMsg }, { squad_id: squadId, agent_id: lead.id, task_id: taskRecord.id });
|
|
85
133
|
throw err;
|
|
86
134
|
}
|
|
87
135
|
// Update task and agent status
|
|
88
136
|
updateTaskStatus(taskRecord.id, "done", result);
|
|
89
137
|
updateAgentStatus(lead.id, "idle");
|
|
138
|
+
// Audit: task completed
|
|
139
|
+
addAuditEntry("task_completed", `Task completed by ${lead.character_name}`, { result: result.slice(0, 500) }, { squad_id: squadId, agent_id: lead.id, task_id: taskRecord.id });
|
|
140
|
+
// Record completion event
|
|
141
|
+
addAgentEvent(taskRecord.id, "status", `Task completed by ${lead.character_name}`, {
|
|
142
|
+
agent: lead.character_name,
|
|
143
|
+
result: result.slice(0, 500),
|
|
144
|
+
});
|
|
90
145
|
// Post to feed
|
|
91
|
-
|
|
146
|
+
const squad = getSquad(squadId);
|
|
147
|
+
const squadSource = squad ? `squad-${squad.slug}` : `squad-${squadId}`;
|
|
148
|
+
postFeedItem(squadSource, `Task completed by ${lead.character_name}`, result.slice(0, 2000));
|
|
92
149
|
return result;
|
|
93
150
|
}
|
|
94
151
|
//# sourceMappingURL=agents.js.map
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { approveAll } from "@github/copilot-sdk";
|
|
2
2
|
import { getClient } from "./client.js";
|
|
3
|
-
import { getLeadForSquad, getAgentsForSquad } from "../store/squads.js";
|
|
3
|
+
import { getLeadForSquad, getAgentsForSquad, getSquad } from "../store/squads.js";
|
|
4
4
|
import { selectModel } from "./model-router.js";
|
|
5
5
|
import { postFeedItem } from "../store/feed.js";
|
|
6
|
+
import { attachTokenTracker } from "./token-tracker.js";
|
|
6
7
|
function buildFacilitatorPrompt(lead, agents, task) {
|
|
7
8
|
const roster = agents
|
|
8
9
|
.filter((a) => !a.is_lead)
|
|
@@ -96,6 +97,7 @@ export async function planningMeeting(squadId, task) {
|
|
|
96
97
|
systemMessage: { content: buildSpecialistPrompt(agent, task) },
|
|
97
98
|
onPermissionRequest: approveAll,
|
|
98
99
|
});
|
|
100
|
+
const flushTokens = attachTokenTracker(session, { squadId, agentId: agent.id });
|
|
99
101
|
try {
|
|
100
102
|
const response = await session.sendAndWait({ prompt: "Please provide your planning input for this task." }, 60_000);
|
|
101
103
|
return {
|
|
@@ -105,6 +107,7 @@ export async function planningMeeting(squadId, task) {
|
|
|
105
107
|
};
|
|
106
108
|
}
|
|
107
109
|
finally {
|
|
110
|
+
flushTokens();
|
|
108
111
|
await session.disconnect();
|
|
109
112
|
}
|
|
110
113
|
}));
|
|
@@ -124,6 +127,10 @@ export async function planningMeeting(squadId, task) {
|
|
|
124
127
|
systemMessage: { content: buildFacilitatorPrompt(lead, agents, task) },
|
|
125
128
|
onPermissionRequest: approveAll,
|
|
126
129
|
});
|
|
130
|
+
const flushFacilitatorTokens = attachTokenTracker(facilitatorSession, {
|
|
131
|
+
squadId,
|
|
132
|
+
agentId: lead.id,
|
|
133
|
+
});
|
|
127
134
|
let plan;
|
|
128
135
|
try {
|
|
129
136
|
const prompt = `Here is the input gathered from your team:\n\n${inputsSummary}\n\nNow synthesize this into a clear, structured action plan.`;
|
|
@@ -131,6 +138,7 @@ export async function planningMeeting(squadId, task) {
|
|
|
131
138
|
plan = response?.data?.content ?? "Planning meeting completed but no plan was produced.";
|
|
132
139
|
}
|
|
133
140
|
finally {
|
|
141
|
+
flushFacilitatorTokens();
|
|
134
142
|
await facilitatorSession.disconnect();
|
|
135
143
|
}
|
|
136
144
|
return {
|
|
@@ -143,7 +151,9 @@ export async function squadMeeting(squadId, task, executeAfter) {
|
|
|
143
151
|
const summary = `## Planning Meeting Complete\n\n**Participants:** ${result.participants.join(", ")}\n\n${result.plan}`;
|
|
144
152
|
if (!executeAfter) {
|
|
145
153
|
// Post to feed and wait for user to trigger execution
|
|
146
|
-
|
|
154
|
+
const squad = getSquad(squadId);
|
|
155
|
+
const squadSource = squad ? `squad-${squad.slug}` : `squad-${squadId}`;
|
|
156
|
+
postFeedItem(squadSource, "Planning meeting complete — awaiting approval", summary);
|
|
147
157
|
return summary;
|
|
148
158
|
}
|
|
149
159
|
// Execute: delegate with the plan as additional context
|
|
@@ -15,14 +15,22 @@ function checkIoSchedules() {
|
|
|
15
15
|
continue;
|
|
16
16
|
if (!isDue(schedule.cron, schedule.last_run, now))
|
|
17
17
|
continue;
|
|
18
|
+
if (!schedule.squad_id) {
|
|
19
|
+
console.warn(`[io-scheduler] Schedule ${schedule.id} skipped: missing squad_id.`);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
18
22
|
updateScheduleLastRun(schedule.id);
|
|
19
|
-
sendToOrchestrator(schedule
|
|
23
|
+
sendToOrchestrator(buildSquadScopedPrompt(schedule), "io-scheduler", (_text, done) => {
|
|
20
24
|
if (done) {
|
|
21
25
|
console.log(`[io-scheduler] Schedule ${schedule.id} completed.`);
|
|
22
26
|
}
|
|
23
27
|
});
|
|
24
28
|
}
|
|
25
29
|
}
|
|
30
|
+
export function buildSquadScopedPrompt(schedule) {
|
|
31
|
+
const squadId = schedule.squad_id ?? "unknown";
|
|
32
|
+
return `[Squad Schedule] Run for squad ${squadId}. Prompt: ${schedule.prompt}`;
|
|
33
|
+
}
|
|
26
34
|
function isDue(cron, lastRun, now) {
|
|
27
35
|
const parts = cron.split(" ");
|
|
28
36
|
if (parts.length !== 5)
|
|
@@ -5,6 +5,7 @@ import { buildSystemMessage } from "./system-message.js";
|
|
|
5
5
|
import { createTools } from "./tools.js";
|
|
6
6
|
import { loadSkillDirectories } from "./skills.js";
|
|
7
7
|
import { resetClient } from "./client.js";
|
|
8
|
+
import { addAuditEntry } from "../store/audit-log.js";
|
|
8
9
|
let orchestratorSession;
|
|
9
10
|
let sessionCreatePromise;
|
|
10
11
|
let healthCheckInterval;
|
|
@@ -80,6 +81,7 @@ function startHealthCheck(client, opts) {
|
|
|
80
81
|
healthCheckInterval.unref();
|
|
81
82
|
}
|
|
82
83
|
export async function sendToOrchestrator(prompt, source, callback) {
|
|
84
|
+
addAuditEntry("message_received", `Message from ${source}: ${prompt.slice(0, 200)}`, { source, prompt: prompt.slice(0, 1000) });
|
|
83
85
|
messageQueue.push({ prompt, source, callback });
|
|
84
86
|
if (!processing)
|
|
85
87
|
processQueue();
|
|
@@ -16,6 +16,10 @@ function checkSquadSchedules() {
|
|
|
16
16
|
continue;
|
|
17
17
|
if (!isDue(schedule.cron, schedule.last_run, now))
|
|
18
18
|
continue;
|
|
19
|
+
if (!schedule.squad_id) {
|
|
20
|
+
console.warn(`[scheduler] Schedule ${schedule.id} skipped: missing squad_id.`);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
19
23
|
updateScheduleLastRun(schedule.id);
|
|
20
24
|
const agenda = schedule.agenda || "triage";
|
|
21
25
|
const prompt = `[Squad Schedule] Run "${agenda}" stand-up for squad ${schedule.squad_id}. Agenda: ${agenda}`;
|