heyio 1.12.1 → 3.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/dist/api/middleware/auth.d.ts +14 -0
- package/dist/api/middleware/auth.d.ts.map +1 -0
- package/dist/api/middleware/auth.js +66 -0
- package/dist/api/middleware/auth.js.map +1 -0
- package/dist/api/notifications.d.ts +14 -0
- package/dist/api/notifications.d.ts.map +1 -0
- package/dist/api/notifications.js +112 -0
- package/dist/api/notifications.js.map +1 -0
- package/dist/api/routes/activity.d.ts +3 -0
- package/dist/api/routes/activity.d.ts.map +1 -0
- package/dist/api/routes/activity.js +28 -0
- package/dist/api/routes/activity.js.map +1 -0
- package/dist/api/routes/attachments.d.ts +3 -0
- package/dist/api/routes/attachments.d.ts.map +1 -0
- package/dist/api/routes/attachments.js +83 -0
- package/dist/api/routes/attachments.js.map +1 -0
- package/dist/api/routes/config.d.ts +3 -0
- package/dist/api/routes/config.d.ts.map +1 -0
- package/dist/api/routes/config.js +106 -0
- package/dist/api/routes/config.js.map +1 -0
- package/dist/api/routes/conversations.d.ts +3 -0
- package/dist/api/routes/conversations.d.ts.map +1 -0
- package/dist/api/routes/conversations.js +69 -0
- package/dist/api/routes/conversations.js.map +1 -0
- package/dist/api/routes/health.d.ts +3 -0
- package/dist/api/routes/health.d.ts.map +1 -0
- package/dist/api/routes/health.js +16 -0
- package/dist/api/routes/health.js.map +1 -0
- package/dist/api/routes/inbox.d.ts +3 -0
- package/dist/api/routes/inbox.d.ts.map +1 -0
- package/dist/api/routes/inbox.js +88 -0
- package/dist/api/routes/inbox.js.map +1 -0
- package/dist/api/routes/schedules.d.ts +3 -0
- package/dist/api/routes/schedules.d.ts.map +1 -0
- package/dist/api/routes/schedules.js +96 -0
- package/dist/api/routes/schedules.js.map +1 -0
- package/dist/api/routes/skills.d.ts +2 -0
- package/dist/api/routes/skills.d.ts.map +1 -0
- package/dist/api/routes/skills.js +85 -0
- package/dist/api/routes/skills.js.map +1 -0
- package/dist/api/routes/squads.d.ts +3 -0
- package/dist/api/routes/squads.d.ts.map +1 -0
- package/dist/api/routes/squads.js +129 -0
- package/dist/api/routes/squads.js.map +1 -0
- package/dist/api/routes/usage.d.ts +3 -0
- package/dist/api/routes/usage.d.ts.map +1 -0
- package/dist/api/routes/usage.js +55 -0
- package/dist/api/routes/usage.js.map +1 -0
- package/dist/api/routes/wiki.d.ts +2 -0
- package/dist/api/routes/wiki.d.ts.map +1 -0
- package/dist/api/routes/wiki.js +43 -0
- package/dist/api/routes/wiki.js.map +1 -0
- package/dist/api/server.d.ts +7 -0
- package/dist/api/server.d.ts.map +1 -0
- package/dist/api/server.js +136 -634
- package/dist/api/server.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +2 -91
- package/dist/config.js.map +1 -0
- package/dist/copilot/client.d.ts +5 -0
- package/dist/copilot/client.d.ts.map +1 -0
- package/dist/copilot/client.js +19 -11
- package/dist/copilot/client.js.map +1 -0
- package/dist/copilot/health-monitor.d.ts +14 -0
- package/dist/copilot/health-monitor.d.ts.map +1 -0
- package/dist/copilot/health-monitor.js +70 -0
- package/dist/copilot/health-monitor.js.map +1 -0
- package/dist/copilot/orchestrator.d.ts +5 -0
- package/dist/copilot/orchestrator.d.ts.map +1 -0
- package/dist/copilot/orchestrator.js +127 -123
- package/dist/copilot/orchestrator.js.map +1 -0
- package/dist/copilot/tools.d.ts +49 -0
- package/dist/copilot/tools.d.ts.map +1 -0
- package/dist/copilot/tools.js +545 -321
- package/dist/copilot/tools.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +82 -26
- package/dist/index.js.map +1 -0
- package/dist/logging/logger.d.ts +6 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +21 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/models/index.d.ts +6 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +4 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models/pricing.d.ts +25 -0
- package/dist/models/pricing.d.ts.map +1 -0
- package/dist/models/pricing.js +96 -0
- package/dist/models/pricing.js.map +1 -0
- package/dist/models/registry.d.ts +34 -0
- package/dist/models/registry.d.ts.map +1 -0
- package/dist/models/registry.js +109 -0
- package/dist/models/registry.js.map +1 -0
- package/dist/models/token-tracker.d.ts +40 -0
- package/dist/models/token-tracker.d.ts.map +1 -0
- package/dist/models/token-tracker.js +102 -0
- package/dist/models/token-tracker.js.map +1 -0
- package/dist/scheduler/engine.d.ts +9 -0
- package/dist/scheduler/engine.d.ts.map +1 -0
- package/dist/scheduler/engine.js +127 -0
- package/dist/scheduler/engine.js.map +1 -0
- package/dist/skills/index.d.ts +3 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +2 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/store.d.ts +52 -0
- package/dist/skills/store.d.ts.map +1 -0
- package/dist/skills/store.js +148 -0
- package/dist/skills/store.js.map +1 -0
- package/dist/squad/agent.d.ts +46 -0
- package/dist/squad/agent.d.ts.map +1 -0
- package/dist/squad/agent.js +261 -0
- package/dist/squad/agent.js.map +1 -0
- package/dist/squad/autonomy.d.ts +16 -0
- package/dist/squad/autonomy.d.ts.map +1 -0
- package/dist/squad/autonomy.js +63 -0
- package/dist/squad/autonomy.js.map +1 -0
- package/dist/squad/event-bus.d.ts +22 -0
- package/dist/squad/event-bus.d.ts.map +1 -0
- package/dist/squad/event-bus.js +56 -0
- package/dist/squad/event-bus.js.map +1 -0
- package/dist/squad/execution/index.d.ts +12 -0
- package/dist/squad/execution/index.d.ts.map +1 -0
- package/dist/squad/execution/index.js +7 -0
- package/dist/squad/execution/index.js.map +1 -0
- package/dist/squad/execution/instance.d.ts +40 -0
- package/dist/squad/execution/instance.d.ts.map +1 -0
- package/dist/squad/execution/instance.js +138 -0
- package/dist/squad/execution/instance.js.map +1 -0
- package/dist/squad/execution/meeting.d.ts +25 -0
- package/dist/squad/execution/meeting.d.ts.map +1 -0
- package/dist/squad/execution/meeting.js +140 -0
- package/dist/squad/execution/meeting.js.map +1 -0
- package/dist/squad/execution/pr.d.ts +15 -0
- package/dist/squad/execution/pr.d.ts.map +1 -0
- package/dist/squad/execution/pr.js +93 -0
- package/dist/squad/execution/pr.js.map +1 -0
- package/dist/squad/execution/runner.d.ts +22 -0
- package/dist/squad/execution/runner.d.ts.map +1 -0
- package/dist/squad/execution/runner.js +68 -0
- package/dist/squad/execution/runner.js.map +1 -0
- package/dist/squad/execution/tasks.d.ts +11 -0
- package/dist/squad/execution/tasks.d.ts.map +1 -0
- package/dist/squad/execution/tasks.js +85 -0
- package/dist/squad/execution/tasks.js.map +1 -0
- package/dist/squad/execution/worktree.d.ts +26 -0
- package/dist/squad/execution/worktree.d.ts.map +1 -0
- package/dist/squad/execution/worktree.js +111 -0
- package/dist/squad/execution/worktree.js.map +1 -0
- package/dist/squad/hiring.d.ts +32 -0
- package/dist/squad/hiring.d.ts.map +1 -0
- package/dist/squad/hiring.js +200 -0
- package/dist/squad/hiring.js.map +1 -0
- package/dist/squad/index.d.ts +8 -0
- package/dist/squad/index.d.ts.map +1 -0
- package/dist/squad/index.js +6 -0
- package/dist/squad/index.js.map +1 -0
- package/dist/squad/manager.d.ts +48 -0
- package/dist/squad/manager.d.ts.map +1 -0
- package/dist/squad/manager.js +274 -0
- package/dist/squad/manager.js.map +1 -0
- package/dist/squad/name-generator.d.ts +16 -0
- package/dist/squad/name-generator.d.ts.map +1 -0
- package/dist/squad/name-generator.js +113 -0
- package/dist/squad/name-generator.js.map +1 -0
- package/dist/squad/roles/templates.d.ts +5 -0
- package/dist/squad/roles/templates.d.ts.map +1 -0
- package/dist/squad/roles/templates.js +102 -0
- package/dist/squad/roles/templates.js.map +1 -0
- package/dist/squad/skill-parser.d.ts +36 -0
- package/dist/squad/skill-parser.d.ts.map +1 -0
- package/dist/squad/skill-parser.js +83 -0
- package/dist/squad/skill-parser.js.map +1 -0
- package/dist/squad/source-resolver.d.ts +20 -0
- package/dist/squad/source-resolver.d.ts.map +1 -0
- package/dist/squad/source-resolver.js +52 -0
- package/dist/squad/source-resolver.js.map +1 -0
- package/dist/store/activity.d.ts +43 -0
- package/dist/store/activity.d.ts.map +1 -0
- package/dist/store/activity.js +131 -0
- package/dist/store/activity.js.map +1 -0
- package/dist/store/db.d.ts +5 -0
- package/dist/store/db.d.ts.map +1 -0
- package/dist/store/db.js +209 -248
- package/dist/store/db.js.map +1 -0
- package/dist/store/inbox.d.ts +53 -0
- package/dist/store/inbox.d.ts.map +1 -0
- package/dist/store/inbox.js +151 -0
- package/dist/store/inbox.js.map +1 -0
- package/dist/store/schedules.d.ts +53 -0
- package/dist/store/schedules.d.ts.map +1 -0
- package/dist/store/schedules.js +149 -54
- package/dist/store/schedules.js.map +1 -0
- package/dist/wiki/index.d.ts +3 -0
- package/dist/wiki/index.d.ts.map +1 -0
- package/dist/wiki/index.js +2 -0
- package/dist/wiki/index.js.map +1 -0
- package/dist/wiki/store.d.ts +49 -0
- package/dist/wiki/store.d.ts.map +1 -0
- package/dist/wiki/store.js +115 -0
- package/dist/wiki/store.js.map +1 -0
- package/package.json +52 -56
- package/src/api/middleware/auth.ts +76 -0
- package/src/api/notifications.ts +122 -0
- package/src/api/routes/activity.ts +29 -0
- package/src/api/routes/attachments.ts +93 -0
- package/src/api/routes/config.ts +115 -0
- package/src/api/routes/conversations.ts +87 -0
- package/src/api/routes/health.ts +18 -0
- package/src/api/routes/inbox.ts +98 -0
- package/src/api/routes/schedules.ts +121 -0
- package/src/api/routes/skills.ts +105 -0
- package/src/api/routes/squads.ts +145 -0
- package/src/api/routes/usage.ts +57 -0
- package/src/api/routes/wiki.ts +49 -0
- package/src/api/server.ts +186 -0
- package/src/config.ts +3 -0
- package/src/copilot/client.ts +42 -0
- package/src/copilot/health-monitor.ts +85 -0
- package/src/copilot/orchestrator.ts +222 -0
- package/src/copilot/tools.ts +707 -0
- package/src/index.ts +112 -0
- package/src/logging/logger.ts +26 -0
- package/src/models/index.ts +11 -0
- package/src/models/pricing.ts +121 -0
- package/src/models/registry.ts +131 -0
- package/src/models/token-tracker.ts +151 -0
- package/src/scheduler/engine.ts +146 -0
- package/src/skills/index.ts +13 -0
- package/src/skills/store.ts +188 -0
- package/src/squad/agent.ts +326 -0
- package/src/squad/autonomy.ts +78 -0
- package/src/squad/event-bus.ts +71 -0
- package/src/squad/execution/index.ts +17 -0
- package/src/squad/execution/instance.ts +186 -0
- package/src/squad/execution/meeting.ts +191 -0
- package/src/squad/execution/pr.ts +127 -0
- package/src/squad/execution/runner.ts +97 -0
- package/src/squad/execution/tasks.ts +111 -0
- package/src/squad/execution/worktree.ts +138 -0
- package/src/squad/hiring.ts +222 -0
- package/src/squad/index.ts +17 -0
- package/src/squad/manager.ts +337 -0
- package/src/squad/name-generator.ts +135 -0
- package/src/squad/roles/templates.ts +104 -0
- package/src/squad/skill-parser.ts +120 -0
- package/src/squad/source-resolver.ts +57 -0
- package/src/store/activity.ts +176 -0
- package/src/store/db.ts +237 -0
- package/src/store/inbox.ts +199 -0
- package/src/store/schedules.ts +199 -0
- package/src/wiki/index.ts +12 -0
- package/src/wiki/store.ts +139 -0
- package/tsconfig.json +9 -0
- package/LICENSE +0 -21
- package/README.md +0 -333
- package/dist/api/auth.js +0 -46
- package/dist/chat/attachments.js +0 -112
- package/dist/copilot/agents.js +0 -309
- package/dist/copilot/ceremonies.js +0 -174
- package/dist/copilot/gh-token.js +0 -64
- package/dist/copilot/io-scheduler.js +0 -79
- package/dist/copilot/model-router.js +0 -114
- package/dist/copilot/scheduler.js +0 -88
- package/dist/copilot/skills.js +0 -246
- package/dist/copilot/specialist-runner.js +0 -191
- package/dist/copilot/squad-tools.js +0 -258
- package/dist/copilot/system-message.js +0 -86
- package/dist/copilot/token-tracker.js +0 -98
- package/dist/copilot/trigger-schedule.js +0 -33
- package/dist/daemon.js +0 -67
- package/dist/logging.js +0 -27
- package/dist/mcp/config.js +0 -29
- package/dist/mcp/index.js +0 -3
- package/dist/mcp/registry.js +0 -42
- package/dist/notify.js +0 -25
- package/dist/paths.js +0 -17
- package/dist/setup.js +0 -35
- package/dist/store/agent-events.js +0 -19
- package/dist/store/audit-log.js +0 -71
- package/dist/store/conversations.js +0 -164
- package/dist/store/feed.js +0 -44
- package/dist/store/instances.js +0 -75
- package/dist/store/squad-colors.js +0 -23
- package/dist/store/squads.js +0 -60
- package/dist/store/tasks.js +0 -78
- package/dist/store/token-usage.js +0 -94
- package/dist/telegram/bot.js +0 -41
- package/dist/telegram/handlers.js +0 -42
- package/dist/watchdog.js +0 -37
- package/dist/wiki/backlinks.js +0 -51
- package/dist/wiki/fs.js +0 -108
- package/dist/wiki/search.js +0 -47
- package/web-dist/assets/AuditLogView-C5QtUQBq.js +0 -6
- package/web-dist/assets/ChatView-DLu9BMg8.js +0 -1
- package/web-dist/assets/FeedView-6OV-l6Gl.js +0 -6
- package/web-dist/assets/HistoryView-DizPqv0y.js +0 -1
- package/web-dist/assets/LoginView-CG1O9fmR.js +0 -1
- package/web-dist/assets/McpView-TJN-fZvI.js +0 -1
- package/web-dist/assets/SchedulesView-BDFpImX6.js +0 -6
- package/web-dist/assets/SettingsView-D-K1iC1c.js +0 -1
- package/web-dist/assets/SkillsView-DnNmO192.js +0 -15
- package/web-dist/assets/SquadDetailView-BBvrgHzn.js +0 -26
- package/web-dist/assets/SquadHealthView-DF2zF9D3.js +0 -11
- package/web-dist/assets/SquadsView-CPVzko7k.js +0 -6
- package/web-dist/assets/ToggleSwitch.vue_vue_type_script_setup_true_lang-Bk_t9_Rn.js +0 -1
- package/web-dist/assets/UsageView-OcyM5k14.js +0 -16
- package/web-dist/assets/WikiView-KhFqBZI0.js +0 -26
- package/web-dist/assets/api-BiDVwQrs.js +0 -1
- package/web-dist/assets/arrow-left-DFbf2tii.js +0 -6
- package/web-dist/assets/git-branch-BLstr_Gr.js +0 -6
- package/web-dist/assets/index-B-o45ao1.css +0 -1
- package/web-dist/assets/index-DCJUYZtV.js +0 -269
- package/web-dist/assets/pencil-D4Zz_t0y.js +0 -6
- package/web-dist/assets/plus-gmwiZVfr.js +0 -6
- package/web-dist/assets/save-BvtAs5WB.js +0 -6
- package/web-dist/assets/search-BabUvoGD.js +0 -6
- package/web-dist/assets/squad-colors-B8B_Y-lz.js +0 -1
- package/web-dist/assets/trash-2-C21cNLJl.js +0 -6
- package/web-dist/assets/triangle-alert-BrzY_E1n.js +0 -6
- package/web-dist/assets/x-xgLSmc9e.js +0 -6
- package/web-dist/favicon.svg +0 -10
- package/web-dist/index.html +0 -14
- package/web-dist/logo.svg +0 -10
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import {
|
|
3
|
+
type ScheduleTargetType,
|
|
4
|
+
createSchedule,
|
|
5
|
+
deleteSchedule,
|
|
6
|
+
getSchedule,
|
|
7
|
+
listSchedules,
|
|
8
|
+
updateSchedule,
|
|
9
|
+
} from '../../store/schedules.js';
|
|
10
|
+
|
|
11
|
+
export function schedulesRouter(): Router {
|
|
12
|
+
const router = Router();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* GET /api/schedules
|
|
16
|
+
* List all schedules. Query param: enabled=true to filter.
|
|
17
|
+
*/
|
|
18
|
+
router.get('/schedules', async (req, res) => {
|
|
19
|
+
try {
|
|
20
|
+
const enabledOnly = req.query.enabled === 'true';
|
|
21
|
+
const schedules = await listSchedules(enabledOnly || undefined);
|
|
22
|
+
res.json({ schedules });
|
|
23
|
+
} catch {
|
|
24
|
+
res.status(500).json({ error: 'Failed to list schedules' });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* GET /api/schedules/:id
|
|
30
|
+
* Get a single schedule.
|
|
31
|
+
*/
|
|
32
|
+
router.get('/schedules/:id', async (req, res) => {
|
|
33
|
+
try {
|
|
34
|
+
const schedule = await getSchedule(req.params.id);
|
|
35
|
+
if (!schedule) {
|
|
36
|
+
res.status(404).json({ error: 'Schedule not found' });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
res.json({ schedule });
|
|
40
|
+
} catch {
|
|
41
|
+
res.status(500).json({ error: 'Failed to get schedule' });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* POST /api/schedules
|
|
47
|
+
* Create a new schedule.
|
|
48
|
+
* Body: { name, targetType, targetId?, cron, prompt, enabled? }
|
|
49
|
+
*/
|
|
50
|
+
router.post('/schedules', async (req, res) => {
|
|
51
|
+
try {
|
|
52
|
+
const { name, targetType, targetId, cron, prompt, enabled } = req.body as {
|
|
53
|
+
name?: string;
|
|
54
|
+
targetType?: ScheduleTargetType;
|
|
55
|
+
targetId?: string;
|
|
56
|
+
cron?: string;
|
|
57
|
+
prompt?: string;
|
|
58
|
+
enabled?: boolean;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (!name || !targetType || !cron || !prompt) {
|
|
62
|
+
res.status(400).json({ error: 'name, targetType, cron, and prompt are required' });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (targetType !== 'squad' && targetType !== 'orchestrator') {
|
|
67
|
+
res.status(400).json({ error: 'targetType must be "squad" or "orchestrator"' });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const schedule = await createSchedule({ name, targetType, targetId, cron, prompt, enabled });
|
|
72
|
+
res.status(201).json({ schedule });
|
|
73
|
+
} catch (err) {
|
|
74
|
+
const msg = err instanceof Error ? err.message : 'Failed to create schedule';
|
|
75
|
+
res.status(400).json({ error: msg });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* PATCH /api/schedules/:id
|
|
81
|
+
* Update a schedule (partial). Body: { name?, cron?, prompt?, enabled? }
|
|
82
|
+
*/
|
|
83
|
+
router.patch('/schedules/:id', async (req, res) => {
|
|
84
|
+
try {
|
|
85
|
+
const existing = await getSchedule(req.params.id);
|
|
86
|
+
if (!existing) {
|
|
87
|
+
res.status(404).json({ error: 'Schedule not found' });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const { name, cron, prompt, enabled } = req.body as {
|
|
92
|
+
name?: string;
|
|
93
|
+
cron?: string;
|
|
94
|
+
prompt?: string;
|
|
95
|
+
enabled?: boolean;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
await updateSchedule(req.params.id, { name, cron, prompt, enabled });
|
|
99
|
+
const updated = await getSchedule(req.params.id);
|
|
100
|
+
res.json({ schedule: updated });
|
|
101
|
+
} catch (err) {
|
|
102
|
+
const msg = err instanceof Error ? err.message : 'Failed to update schedule';
|
|
103
|
+
res.status(400).json({ error: msg });
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* DELETE /api/schedules/:id
|
|
109
|
+
* Delete a schedule.
|
|
110
|
+
*/
|
|
111
|
+
router.delete('/schedules/:id', async (req, res) => {
|
|
112
|
+
try {
|
|
113
|
+
await deleteSchedule(req.params.id);
|
|
114
|
+
res.json({ status: 'ok' });
|
|
115
|
+
} catch {
|
|
116
|
+
res.status(500).json({ error: 'Failed to delete schedule' });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return router;
|
|
121
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import {
|
|
3
|
+
activateSkill,
|
|
4
|
+
deactivateSkill,
|
|
5
|
+
getActiveSkills,
|
|
6
|
+
getSkill,
|
|
7
|
+
installSkill,
|
|
8
|
+
installSkillFromUrl,
|
|
9
|
+
listInstalledSkills,
|
|
10
|
+
removeSkill,
|
|
11
|
+
} from '../../skills/index.js';
|
|
12
|
+
|
|
13
|
+
export const skillsRouter = Router();
|
|
14
|
+
|
|
15
|
+
// List installed skills
|
|
16
|
+
skillsRouter.get('/skills', async (_req, res) => {
|
|
17
|
+
const skills = listInstalledSkills();
|
|
18
|
+
const orchestratorActivations = await getActiveSkills('orchestrator');
|
|
19
|
+
|
|
20
|
+
const result = skills.map((s) => ({
|
|
21
|
+
name: s.name,
|
|
22
|
+
activatedForOrchestrator: orchestratorActivations.some((a) => a.skillName === s.name),
|
|
23
|
+
preview: s.content.slice(0, 200),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
res.json({ skills: result });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Get a specific skill
|
|
30
|
+
skillsRouter.get('/skills/:name', (req, res) => {
|
|
31
|
+
const skill = getSkill(req.params.name);
|
|
32
|
+
if (!skill) {
|
|
33
|
+
res.status(404).json({ error: `Skill '${req.params.name}' not found` });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
res.json(skill);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Install a skill (from URL or content)
|
|
40
|
+
skillsRouter.post('/skills/install', async (req, res) => {
|
|
41
|
+
const { name, url, content } = req.body;
|
|
42
|
+
|
|
43
|
+
if (!name) {
|
|
44
|
+
res.status(400).json({ error: 'name is required' });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
if (url) {
|
|
50
|
+
const skill = await installSkillFromUrl(name, url);
|
|
51
|
+
res.status(201).json({ installed: true, name: skill.name });
|
|
52
|
+
} else if (content) {
|
|
53
|
+
const skill = installSkill(name, content);
|
|
54
|
+
res.status(201).json({ installed: true, name: skill.name });
|
|
55
|
+
} else {
|
|
56
|
+
res.status(400).json({ error: 'Either "url" or "content" is required' });
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Activate a skill
|
|
64
|
+
skillsRouter.post('/skills/:name/activate', async (req, res) => {
|
|
65
|
+
const { targetType, targetId } = req.body;
|
|
66
|
+
if (!targetType || !['orchestrator', 'squad'].includes(targetType)) {
|
|
67
|
+
res.status(400).json({ error: 'targetType must be "orchestrator" or "squad"' });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const skill = getSkill(req.params.name);
|
|
72
|
+
if (!skill) {
|
|
73
|
+
res.status(404).json({ error: `Skill '${req.params.name}' not found` });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await activateSkill(req.params.name, targetType, targetId);
|
|
78
|
+
res.json({ activated: true, skillName: req.params.name, targetType, targetId });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Deactivate a skill
|
|
82
|
+
skillsRouter.post('/skills/:name/deactivate', async (req, res) => {
|
|
83
|
+
const { targetType, targetId } = req.body;
|
|
84
|
+
if (!targetType || !['orchestrator', 'squad'].includes(targetType)) {
|
|
85
|
+
res.status(400).json({ error: 'targetType must be "orchestrator" or "squad"' });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await deactivateSkill(req.params.name, targetType, targetId);
|
|
90
|
+
res.json({ deactivated: true, skillName: req.params.name });
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Remove a skill
|
|
94
|
+
skillsRouter.delete('/skills/:name', (req, res) => {
|
|
95
|
+
removeSkill(req.params.name);
|
|
96
|
+
res.status(204).end();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Get activations for a target
|
|
100
|
+
skillsRouter.get('/skills-activations', async (req, res) => {
|
|
101
|
+
const targetType = (req.query.targetType as string) ?? 'orchestrator';
|
|
102
|
+
const targetId = req.query.targetId as string | undefined;
|
|
103
|
+
const activations = await getActiveSkills(targetType as 'orchestrator' | 'squad', targetId);
|
|
104
|
+
res.json({ activations });
|
|
105
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { getSquadInstances } from '../../squad/execution/instance.js';
|
|
3
|
+
import { runInstance } from '../../squad/execution/runner.js';
|
|
4
|
+
import { getSquadByName, getSquadMembers, listSquads } from '../../squad/manager.js';
|
|
5
|
+
|
|
6
|
+
export function squadsRouter(): Router {
|
|
7
|
+
const router = Router();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* GET /api/squads
|
|
11
|
+
* List all active squads.
|
|
12
|
+
*/
|
|
13
|
+
router.get('/squads', async (_req, res) => {
|
|
14
|
+
try {
|
|
15
|
+
const squads = await listSquads();
|
|
16
|
+
const results = await Promise.all(
|
|
17
|
+
squads.map(async (s) => {
|
|
18
|
+
const members = await getSquadMembers(s.id);
|
|
19
|
+
const instances = getSquadInstances(s.id);
|
|
20
|
+
return {
|
|
21
|
+
id: s.id,
|
|
22
|
+
name: s.name,
|
|
23
|
+
projectPath: s.projectPath,
|
|
24
|
+
repoUrl: s.repoUrl,
|
|
25
|
+
universe: s.universe,
|
|
26
|
+
autonomyTier: s.autonomyTier,
|
|
27
|
+
status: s.status,
|
|
28
|
+
memberCount: members.length,
|
|
29
|
+
activeInstances: instances.filter(
|
|
30
|
+
(i) => i.status !== 'complete' && i.status !== 'failed',
|
|
31
|
+
).length,
|
|
32
|
+
createdAt: s.createdAt.toISOString(),
|
|
33
|
+
};
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
res.json({ squads: results });
|
|
37
|
+
} catch (err) {
|
|
38
|
+
res.status(500).json({ error: 'Failed to list squads' });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* GET /api/squads/:name
|
|
44
|
+
* Get detailed squad info.
|
|
45
|
+
*/
|
|
46
|
+
router.get('/squads/:name', async (req, res) => {
|
|
47
|
+
try {
|
|
48
|
+
const squad = await getSquadByName(req.params.name);
|
|
49
|
+
if (!squad) {
|
|
50
|
+
res.status(404).json({ error: `Squad '${req.params.name}' not found` });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const members = await getSquadMembers(squad.id);
|
|
55
|
+
const instances = getSquadInstances(squad.id);
|
|
56
|
+
|
|
57
|
+
// Build a map of role -> current task (from active instances)
|
|
58
|
+
const currentTasks = new Map<string, string>();
|
|
59
|
+
for (const inst of instances) {
|
|
60
|
+
if (inst.status !== 'complete' && inst.status !== 'failed') {
|
|
61
|
+
for (const task of inst.tasks) {
|
|
62
|
+
if (task.status === 'in_progress') {
|
|
63
|
+
currentTasks.set(task.assignedTo, task.description);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
res.json({
|
|
70
|
+
squad: {
|
|
71
|
+
id: squad.id,
|
|
72
|
+
name: squad.name,
|
|
73
|
+
projectPath: squad.projectPath,
|
|
74
|
+
repoUrl: squad.repoUrl,
|
|
75
|
+
universe: squad.universe,
|
|
76
|
+
autonomyTier: squad.autonomyTier,
|
|
77
|
+
autonomyConfig: squad.autonomyConfig,
|
|
78
|
+
status: squad.status,
|
|
79
|
+
createdAt: squad.createdAt.toISOString(),
|
|
80
|
+
},
|
|
81
|
+
members: members.map((m) => ({
|
|
82
|
+
id: m.id,
|
|
83
|
+
displayName: m.displayName,
|
|
84
|
+
role: m.roleName,
|
|
85
|
+
persona: m.persona,
|
|
86
|
+
veto: m.isVetoMember,
|
|
87
|
+
tools: m.toolsAllowed,
|
|
88
|
+
status: m.status,
|
|
89
|
+
currentTask: currentTasks.get(m.roleName) ?? null,
|
|
90
|
+
})),
|
|
91
|
+
instances: instances.map((i) => ({
|
|
92
|
+
id: i.id,
|
|
93
|
+
status: i.status,
|
|
94
|
+
issueRef: i.issueRef,
|
|
95
|
+
branch: i.branch,
|
|
96
|
+
taskCount: i.tasks.length,
|
|
97
|
+
tasksComplete: i.tasks.filter((t) => t.status === 'done').length,
|
|
98
|
+
})),
|
|
99
|
+
});
|
|
100
|
+
} catch (err) {
|
|
101
|
+
res.status(500).json({ error: 'Failed to get squad' });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* POST /api/squads/:name/run
|
|
107
|
+
* Spawn a new instance for a squad.
|
|
108
|
+
* Body: { objective: string, issueRef?: string }
|
|
109
|
+
*/
|
|
110
|
+
router.post('/squads/:name/run', async (req, res) => {
|
|
111
|
+
try {
|
|
112
|
+
const squad = await getSquadByName(req.params.name);
|
|
113
|
+
if (!squad) {
|
|
114
|
+
res.status(404).json({ error: `Squad '${req.params.name}' not found` });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const { objective, issueRef } = req.body as {
|
|
119
|
+
objective?: string;
|
|
120
|
+
issueRef?: string;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (!objective) {
|
|
124
|
+
res.status(400).json({ error: 'objective is required' });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Run asynchronously — return immediately with instance ID
|
|
129
|
+
const instancePromise = runInstance({ squad, objective, issueRef });
|
|
130
|
+
|
|
131
|
+
// We don't await — return accepted status
|
|
132
|
+
// The client can poll /api/squads/:name for progress
|
|
133
|
+
instancePromise.catch(() => {}); // prevent unhandled rejection
|
|
134
|
+
|
|
135
|
+
res.status(202).json({
|
|
136
|
+
message: `Instance starting for squad '${squad.name}'`,
|
|
137
|
+
objective,
|
|
138
|
+
});
|
|
139
|
+
} catch (err) {
|
|
140
|
+
res.status(500).json({ error: 'Failed to start instance' });
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return router;
|
|
145
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { getAllPricing, queryUsage, refreshPricing } from '../../models/index.js';
|
|
3
|
+
|
|
4
|
+
export function usageRouter(): Router {
|
|
5
|
+
const router = Router();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* GET /api/usage
|
|
9
|
+
* Query token usage with optional filters.
|
|
10
|
+
* Query params: squadId, agentRole, model, since, until
|
|
11
|
+
*/
|
|
12
|
+
router.get('/usage', async (req, res) => {
|
|
13
|
+
try {
|
|
14
|
+
const filters = {
|
|
15
|
+
squadId: req.query.squadId as string | undefined,
|
|
16
|
+
agentRole: req.query.agentRole as string | undefined,
|
|
17
|
+
model: req.query.model as string | undefined,
|
|
18
|
+
since: req.query.since as string | undefined,
|
|
19
|
+
until: req.query.until as string | undefined,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const result = await queryUsage(filters);
|
|
23
|
+
res.json(result);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
res.status(500).json({ error: 'Failed to query usage' });
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* GET /api/usage/pricing
|
|
31
|
+
* Get current model pricing.
|
|
32
|
+
*/
|
|
33
|
+
router.get('/usage/pricing', async (_req, res) => {
|
|
34
|
+
try {
|
|
35
|
+
const pricing = await getAllPricing();
|
|
36
|
+
res.json({ pricing });
|
|
37
|
+
} catch (err) {
|
|
38
|
+
res.status(500).json({ error: 'Failed to get pricing' });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* POST /api/usage/pricing/refresh
|
|
44
|
+
* Trigger a pricing refresh.
|
|
45
|
+
*/
|
|
46
|
+
router.post('/usage/pricing/refresh', async (_req, res) => {
|
|
47
|
+
try {
|
|
48
|
+
await refreshPricing();
|
|
49
|
+
const pricing = await getAllPricing();
|
|
50
|
+
res.json({ message: 'Pricing refreshed', pricing });
|
|
51
|
+
} catch (err) {
|
|
52
|
+
res.status(500).json({ error: 'Failed to refresh pricing' });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return router;
|
|
57
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { listWikiPages, readWikiPage, searchWiki, writeWikiPage } from '../../wiki/index.js';
|
|
3
|
+
|
|
4
|
+
export const wikiRouter = Router();
|
|
5
|
+
|
|
6
|
+
// List pages in a scope
|
|
7
|
+
wikiRouter.get('/:scope', (req, res) => {
|
|
8
|
+
const { scope } = req.params;
|
|
9
|
+
const pages = listWikiPages(scope);
|
|
10
|
+
res.json({ scope, pages });
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
// Read a specific page
|
|
14
|
+
wikiRouter.get('/:scope/:page', (req, res) => {
|
|
15
|
+
const { scope, page } = req.params;
|
|
16
|
+
const result = readWikiPage(scope, page);
|
|
17
|
+
if (!result) {
|
|
18
|
+
res.status(404).json({ error: `Page '${page}' not found in '${scope}' wiki` });
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
res.json(result);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Write (create/overwrite) a page
|
|
25
|
+
wikiRouter.put('/:scope/:page', (req, res) => {
|
|
26
|
+
const { scope, page } = req.params;
|
|
27
|
+
const { content } = req.body;
|
|
28
|
+
if (!content || typeof content !== 'string') {
|
|
29
|
+
res.status(400).json({ error: 'Body must include "content" (string)' });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const result = writeWikiPage(scope, page, content);
|
|
33
|
+
res.json(result);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Search across scopes
|
|
37
|
+
wikiRouter.get('/', (req, res) => {
|
|
38
|
+
const keyword = req.query.q as string;
|
|
39
|
+
const scopesParam = req.query.scopes as string | undefined;
|
|
40
|
+
|
|
41
|
+
if (!keyword) {
|
|
42
|
+
res.status(400).json({ error: 'Query parameter "q" is required' });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const scopes = scopesParam ? scopesParam.split(',') : ['io', 'shared'];
|
|
47
|
+
const results = searchWiki(keyword, scopes);
|
|
48
|
+
res.json({ keyword, results });
|
|
49
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import express from 'express';
|
|
6
|
+
import { type WebSocket, WebSocketServer } from 'ws';
|
|
7
|
+
import type { IOConfig } from '../config.js';
|
|
8
|
+
import { sendMessage } from '../copilot/orchestrator.js';
|
|
9
|
+
import { createChildLogger } from '../logging/logger.js';
|
|
10
|
+
import { authMiddleware, verifyWsToken } from './middleware/auth.js';
|
|
11
|
+
import { initNotifications, subscribeClient, unsubscribeClient } from './notifications.js';
|
|
12
|
+
import { activityRouter } from './routes/activity.js';
|
|
13
|
+
import { attachmentsRouter } from './routes/attachments.js';
|
|
14
|
+
import { configRouter } from './routes/config.js';
|
|
15
|
+
import { conversationsRouter } from './routes/conversations.js';
|
|
16
|
+
import { healthRouter } from './routes/health.js';
|
|
17
|
+
import { inboxRouter } from './routes/inbox.js';
|
|
18
|
+
import { schedulesRouter } from './routes/schedules.js';
|
|
19
|
+
import { skillsRouter } from './routes/skills.js';
|
|
20
|
+
import { squadsRouter } from './routes/squads.js';
|
|
21
|
+
import { usageRouter } from './routes/usage.js';
|
|
22
|
+
import { wikiRouter } from './routes/wiki.js';
|
|
23
|
+
|
|
24
|
+
export interface ApiServer {
|
|
25
|
+
start(): Promise<void>;
|
|
26
|
+
stop(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Connected WebSocket clients keyed by connection ID
|
|
30
|
+
const wsClients = new Map<string, WebSocket>();
|
|
31
|
+
|
|
32
|
+
export function createApiServer(config: IOConfig): ApiServer {
|
|
33
|
+
const logger = createChildLogger('api');
|
|
34
|
+
const app = express();
|
|
35
|
+
app.use(express.json());
|
|
36
|
+
|
|
37
|
+
// Auth middleware — verifies Supabase JWT if configured
|
|
38
|
+
app.use('/api', authMiddleware(config));
|
|
39
|
+
|
|
40
|
+
// Routes
|
|
41
|
+
app.use('/api', healthRouter());
|
|
42
|
+
app.use('/api', usageRouter());
|
|
43
|
+
app.use('/api', squadsRouter());
|
|
44
|
+
app.use('/api', activityRouter());
|
|
45
|
+
app.use('/api', attachmentsRouter(config.dataDir));
|
|
46
|
+
app.use('/api', inboxRouter());
|
|
47
|
+
app.use('/api', schedulesRouter());
|
|
48
|
+
app.use('/api', conversationsRouter());
|
|
49
|
+
app.use('/api', configRouter());
|
|
50
|
+
app.use('/api/wiki', wikiRouter);
|
|
51
|
+
app.use('/api', skillsRouter);
|
|
52
|
+
|
|
53
|
+
// POST /api/messages — send a message to the orchestrator
|
|
54
|
+
app.post('/api/messages', async (req, res) => {
|
|
55
|
+
const { content, source, connectionId } = req.body as {
|
|
56
|
+
content?: string;
|
|
57
|
+
source?: 'tui' | 'telegram' | 'web';
|
|
58
|
+
connectionId?: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
if (!content) {
|
|
62
|
+
res.status(400).json({ error: 'content is required' });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const ws = connectionId ? wsClients.get(connectionId) : undefined;
|
|
67
|
+
|
|
68
|
+
const onDelta = (accumulated: string, done: boolean) => {
|
|
69
|
+
if (ws && ws.readyState === ws.OPEN) {
|
|
70
|
+
ws.send(
|
|
71
|
+
JSON.stringify({
|
|
72
|
+
type: done ? 'message' : 'delta',
|
|
73
|
+
content: accumulated,
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const response = await sendMessage(content, source ?? 'web', onDelta);
|
|
81
|
+
res.json({ status: 'ok', content: response });
|
|
82
|
+
} catch (err) {
|
|
83
|
+
logger.error({ err }, 'Error processing message');
|
|
84
|
+
res.status(500).json({ error: 'Failed to process message' });
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Serve web frontend static files (production build)
|
|
89
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
90
|
+
const webDistPath = resolve(__dirname, '../../../web/dist');
|
|
91
|
+
if (existsSync(webDistPath)) {
|
|
92
|
+
app.use(express.static(webDistPath));
|
|
93
|
+
// SPA fallback: serve index.html for any non-API route
|
|
94
|
+
app.get('*', (_req, res) => {
|
|
95
|
+
res.sendFile(join(webDistPath, 'index.html'));
|
|
96
|
+
});
|
|
97
|
+
logger.info({ path: webDistPath }, 'Serving web frontend');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const server = createServer(app);
|
|
101
|
+
|
|
102
|
+
// WebSocket server for streaming
|
|
103
|
+
const wss = new WebSocketServer({ server, path: '/ws' });
|
|
104
|
+
|
|
105
|
+
wss.on('connection', (ws, req) => {
|
|
106
|
+
// Verify token from query string if auth is configured
|
|
107
|
+
const url = new URL(req.url ?? '', `http://${req.headers.host}`);
|
|
108
|
+
const token = url.searchParams.get('token');
|
|
109
|
+
if (!verifyWsToken(config, token)) {
|
|
110
|
+
ws.close(4001, 'Unauthorized');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const connectionId = crypto.randomUUID();
|
|
114
|
+
wsClients.set(connectionId, ws);
|
|
115
|
+
subscribeClient(connectionId, ws);
|
|
116
|
+
logger.info({ connectionId }, 'WebSocket client connected');
|
|
117
|
+
|
|
118
|
+
// Send the connection ID to the client
|
|
119
|
+
ws.send(JSON.stringify({ type: 'connected', connectionId }));
|
|
120
|
+
|
|
121
|
+
ws.on('message', (data) => {
|
|
122
|
+
try {
|
|
123
|
+
const parsed = JSON.parse(data.toString()) as {
|
|
124
|
+
type?: string;
|
|
125
|
+
content?: string;
|
|
126
|
+
source?: string;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
if (parsed.type === 'message' && parsed.content) {
|
|
130
|
+
const source = (parsed.source as 'tui' | 'telegram' | 'web') ?? 'tui';
|
|
131
|
+
|
|
132
|
+
const onDelta = (accumulated: string, done: boolean) => {
|
|
133
|
+
if (ws.readyState === ws.OPEN) {
|
|
134
|
+
ws.send(
|
|
135
|
+
JSON.stringify({
|
|
136
|
+
type: done ? 'message' : 'delta',
|
|
137
|
+
content: accumulated,
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
sendMessage(parsed.content, source, onDelta).catch((err) => {
|
|
144
|
+
logger.error({ err }, 'Error processing WebSocket message');
|
|
145
|
+
if (ws.readyState === ws.OPEN) {
|
|
146
|
+
ws.send(JSON.stringify({ type: 'error', content: 'Failed to process message' }));
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
} catch (err) {
|
|
151
|
+
logger.error({ err }, 'Failed to parse WebSocket message');
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
ws.on('close', () => {
|
|
156
|
+
wsClients.delete(connectionId);
|
|
157
|
+
unsubscribeClient(connectionId);
|
|
158
|
+
logger.info({ connectionId }, 'WebSocket client disconnected');
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
async start() {
|
|
164
|
+
return new Promise<void>((resolve) => {
|
|
165
|
+
server.listen(config.apiPort, () => {
|
|
166
|
+
logger.info({ port: config.apiPort }, 'API server listening');
|
|
167
|
+
resolve();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
async stop() {
|
|
173
|
+
return new Promise<void>((resolve, reject) => {
|
|
174
|
+
for (const ws of wsClients.values()) {
|
|
175
|
+
ws.close();
|
|
176
|
+
}
|
|
177
|
+
wsClients.clear();
|
|
178
|
+
wss.close();
|
|
179
|
+
server.close((err) => {
|
|
180
|
+
if (err) reject(err);
|
|
181
|
+
else resolve();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
package/src/config.ts
ADDED