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.
Files changed (65) hide show
  1. package/dist/api/server.js +267 -12
  2. package/dist/config.js +6 -0
  3. package/dist/copilot/agents.js +61 -4
  4. package/dist/copilot/ceremonies.js +12 -2
  5. package/dist/copilot/io-scheduler.js +9 -1
  6. package/dist/copilot/orchestrator.js +2 -0
  7. package/dist/copilot/scheduler.js +4 -0
  8. package/dist/copilot/skills.js +138 -6
  9. package/dist/copilot/token-tracker.js +89 -0
  10. package/dist/copilot/tools.js +27 -5
  11. package/dist/paths.js +1 -0
  12. package/dist/store/agent-events.js +19 -0
  13. package/dist/store/audit-log.js +71 -0
  14. package/dist/store/conversations.js +150 -0
  15. package/dist/store/db.js +111 -0
  16. package/dist/store/schedules.js +5 -1
  17. package/dist/store/squad-colors.js +21 -0
  18. package/dist/store/squads.js +6 -1
  19. package/dist/store/tasks.js +43 -0
  20. package/dist/store/token-usage.js +94 -0
  21. package/dist/wiki/backlinks.js +51 -0
  22. package/dist/wiki/fs.js +63 -1
  23. package/package.json +1 -1
  24. package/web-dist/assets/AuditLogView-xgSZ2MOJ.js +6 -0
  25. package/web-dist/assets/ChatView-BU3Jvu5y.js +11 -0
  26. package/web-dist/assets/FeedView-BwkWbe1p.js +6 -0
  27. package/web-dist/assets/HistoryView-Doh9Y3Na.js +1 -0
  28. package/web-dist/assets/LoginView-CoTEOrwE.js +1 -0
  29. 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
  30. package/web-dist/assets/McpView-ByXoAnED.js +1 -0
  31. package/web-dist/assets/SchedulesView-BkUdRYwk.js +1 -0
  32. package/web-dist/assets/SettingsView-_q-IpzFy.js +1 -0
  33. package/web-dist/assets/SkillsView-_FkOdD2U.js +15 -0
  34. package/web-dist/assets/SquadDetailView-CV6_n_If.js +31 -0
  35. package/web-dist/assets/SquadHealthView-DmQqPq7H.js +11 -0
  36. package/web-dist/assets/SquadsView-Dkhtu5MQ.js +6 -0
  37. package/web-dist/assets/UsageView-CCS6pp6n.js +16 -0
  38. package/web-dist/assets/WikiView-CpXzff_L.js +31 -0
  39. package/web-dist/assets/api-CaqVk-rG.js +1 -0
  40. package/web-dist/assets/arrow-left-CkDjCT7Z.js +6 -0
  41. package/web-dist/assets/git-branch-Bu9s__XL.js +6 -0
  42. package/web-dist/assets/index-D3DNfwXI.css +1 -0
  43. package/web-dist/assets/{index-BQdXxKfc.js → index-DfdD_qE4.js} +56 -36
  44. package/web-dist/assets/{plus-Cvp1w2CO.js → plus-GvGwcjX5.js} +1 -1
  45. package/web-dist/assets/{x-O3fBd1Cr.js → save-fQ_rr5hX.js} +2 -7
  46. package/web-dist/assets/search-C3fxUixl.js +6 -0
  47. package/web-dist/assets/squad-colors-B8B_Y-lz.js +1 -0
  48. package/web-dist/assets/{trash-2-Cr3vrmL5.js → trash-2-Ba_1SAua.js} +1 -1
  49. package/web-dist/assets/triangle-alert-BTBlX3kg.js +6 -0
  50. package/web-dist/assets/x-CJifAZQa.js +6 -0
  51. package/web-dist/favicon.svg +9 -3
  52. package/web-dist/index.html +2 -2
  53. package/web-dist/logo.svg +10 -0
  54. package/web-dist/assets/ChatView-mZaaw3pd.js +0 -11
  55. package/web-dist/assets/FeedView-BHacQwXQ.js +0 -6
  56. package/web-dist/assets/LoginView-B6aSD9II.js +0 -1
  57. package/web-dist/assets/McpView-BAVRUHIE.js +0 -1
  58. package/web-dist/assets/SchedulesView-dOd1SQiP.js +0 -1
  59. package/web-dist/assets/SettingsView-CCDeEsVg.js +0 -1
  60. package/web-dist/assets/SkillsView-gCfQ35FQ.js +0 -1
  61. package/web-dist/assets/SquadDetailView-CQhFfZTc.js +0 -21
  62. package/web-dist/assets/SquadsView-CZFxtOao.js +0 -6
  63. package/web-dist/assets/WikiView-B0cuUFfm.js +0 -26
  64. package/web-dist/assets/api-DdW5uOZf.js +0 -1
  65. package/web-dist/assets/index-BbSJ0cfF.css +0 -1
@@ -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
- res.json({ content });
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
- res.json(listSquads());
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 (!url || typeof url !== "string") {
136
- res.status(400).json({ error: "Missing 'url' in request body" });
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 schedule = createSchedule(req.body);
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() {
@@ -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
- const response = await session.sendAndWait({ prompt: `Task delegated to you:\n\n${task}` }, 600_000);
75
- result = response?.data?.content ?? "Task completed (no response content).";
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
- postFeedItem(`squad-${squadId}`, `Task completed by ${lead.character_name}`, result.slice(0, 2000));
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
- postFeedItem(`squad-${squadId}`, "Planning meeting complete — awaiting approval", summary);
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.prompt, "io-scheduler", (_text, done) => {
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}`;