speexor 0.2.0 → 0.2.1

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.
@@ -1,345 +0,0 @@
1
- import { createServer } from 'http';
2
- import Debug from 'debug';
3
-
4
- // src/dashboard/state.ts
5
- var DashboardState = class {
6
- state = {
7
- sessions: [],
8
- worktrees: [],
9
- runtimes: [],
10
- projects: []
11
- };
12
- listeners = [];
13
- getState() {
14
- return { ...this.state };
15
- }
16
- setProjects(projects) {
17
- this.state.projects = projects;
18
- this.notify();
19
- }
20
- addSession(session) {
21
- this.state.sessions.push(session);
22
- this.notify();
23
- }
24
- updateSession(sessionId, updates) {
25
- const idx = this.state.sessions.findIndex((s) => s.id === sessionId);
26
- if (idx !== -1) {
27
- const entry = this.state.sessions[idx];
28
- if (entry) {
29
- if (updates.id !== void 0) entry.id = updates.id;
30
- if (updates.taskId !== void 0) entry.taskId = updates.taskId;
31
- if (updates.provider !== void 0) entry.provider = updates.provider;
32
- if (updates.status !== void 0) entry.status = updates.status;
33
- if (updates.startedAt !== void 0) entry.startedAt = updates.startedAt;
34
- if (updates.runtimeSessionId !== void 0) entry.runtimeSessionId = updates.runtimeSessionId;
35
- this.notify();
36
- }
37
- }
38
- }
39
- removeSession(sessionId) {
40
- this.state.sessions = this.state.sessions.filter((s) => s.id !== sessionId);
41
- this.notify();
42
- }
43
- addWorktree(worktree) {
44
- this.state.worktrees.push(worktree);
45
- this.notify();
46
- }
47
- removeWorktree(sessionId) {
48
- this.state.worktrees = this.state.worktrees.filter((w) => w.id !== sessionId);
49
- this.notify();
50
- }
51
- addRuntime(runtime) {
52
- this.state.runtimes.push(runtime);
53
- this.notify();
54
- }
55
- removeRuntime(sessionId) {
56
- this.state.runtimes = this.state.runtimes.filter((r) => r.id !== sessionId);
57
- this.notify();
58
- }
59
- onUpdate(listener) {
60
- this.listeners.push(listener);
61
- return () => {
62
- this.listeners = this.listeners.filter((l) => l !== listener);
63
- };
64
- }
65
- notify() {
66
- for (const listener of this.listeners) {
67
- try {
68
- listener();
69
- } catch {
70
- }
71
- }
72
- }
73
- toJSON() {
74
- return this.getState();
75
- }
76
- };
77
- var debug = Debug("speexor:dashboard");
78
- var DashboardServer = class {
79
- server;
80
- lifecycle;
81
- state;
82
- port;
83
- routes = [];
84
- constructor(lifecycle, port = 3e3) {
85
- this.lifecycle = lifecycle;
86
- this.state = new DashboardState();
87
- this.port = port;
88
- this.state.setProjects(lifecycle.getConfig().projects);
89
- lifecycle.eventBus.on("session:created", (data) => {
90
- const { session } = data;
91
- this.state.addSession(session);
92
- });
93
- lifecycle.eventBus.on("session:completed", (data) => {
94
- const { sessionId } = data;
95
- this.state.removeSession(sessionId);
96
- });
97
- this.setupRoutes();
98
- this.server = createServer((req, res) => this.handleRequest(req, res));
99
- }
100
- setupRoutes() {
101
- this.routes = [
102
- { method: "GET", path: "/api/status", handler: this.handleStatus.bind(this) },
103
- { method: "GET", path: "/api/projects", handler: this.handleProjects.bind(this) },
104
- { method: "GET", path: "/api/sessions", handler: this.handleSessions.bind(this) },
105
- { method: "GET", path: "/api/health", handler: this.handleHealth.bind(this) }
106
- ];
107
- }
108
- async handleRequest(req, res) {
109
- res.setHeader("Access-Control-Allow-Origin", "*");
110
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
111
- res.setHeader("Access-Control-Allow-Headers", "Content-Type");
112
- if (req.method === "OPTIONS") {
113
- res.writeHead(204);
114
- res.end();
115
- return;
116
- }
117
- const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
118
- const pathname = url.pathname;
119
- for (const route of this.routes) {
120
- if (req.method === route.method && this.matchPath(route.path, pathname)) {
121
- try {
122
- await route.handler(req, res, {});
123
- } catch (error) {
124
- this.sendJSON(res, 500, { error: String(error) });
125
- }
126
- return;
127
- }
128
- }
129
- if (pathname === "/" || pathname === "/dashboard") {
130
- this.serveDashboardHTML(res);
131
- return;
132
- }
133
- this.sendJSON(res, 404, { error: "Not found" });
134
- }
135
- matchPath(pattern, path) {
136
- return pattern === path;
137
- }
138
- sendJSON(res, status, data) {
139
- res.writeHead(status, { "Content-Type": "application/json" });
140
- res.end(JSON.stringify(data, null, 2));
141
- }
142
- handleStatus(_req, res) {
143
- this.sendJSON(res, 200, {
144
- status: this.lifecycle.getStatus(),
145
- projectCount: this.lifecycle.getConfig().projects.length,
146
- activeSessions: this.lifecycle.listSessions().length,
147
- uptime: process.uptime()
148
- });
149
- }
150
- handleProjects(_req, res) {
151
- this.sendJSON(res, 200, {
152
- projects: this.lifecycle.getConfig().projects
153
- });
154
- }
155
- handleSessions(_req, res) {
156
- this.sendJSON(res, 200, {
157
- sessions: this.lifecycle.listSessions(),
158
- worktrees: [],
159
- // placeholder
160
- runtimes: []
161
- // placeholder
162
- });
163
- }
164
- handleHealth(_req, res) {
165
- this.sendJSON(res, 200, {
166
- status: "healthy",
167
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
168
- memory: process.memoryUsage()
169
- });
170
- }
171
- serveDashboardHTML(res) {
172
- const html = `<!DOCTYPE html>
173
- <html lang="en">
174
- <head>
175
- <meta charset="UTF-8" />
176
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
177
- <title>Speexor Dashboard</title>
178
- <style>
179
- * { margin: 0; padding: 0; box-sizing: border-box; }
180
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0d1117; color: #c9d1d9; }
181
- .container { max-width: 1200px; margin: 0 auto; padding: 2rem; }
182
- header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; }
183
- h1 { font-size: 1.5rem; color: #58a6ff; }
184
- h1 span { color: #8b949e; font-weight: normal; }
185
- .status-badge { padding: 0.25rem 0.75rem; border-radius: 999px; font-size: 0.8rem; background: #238636; color: #fff; }
186
- .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
187
- .stat-card { background: #161b22; border: 1px solid #30363d; border-radius: 6px; padding: 1.25rem; }
188
- .stat-card h3 { font-size: 0.75rem; text-transform: uppercase; color: #8b949e; margin-bottom: 0.5rem; }
189
- .stat-card .value { font-size: 2rem; font-weight: 600; }
190
- .stat-card .value.green { color: #3fb950; }
191
- .stat-card .value.blue { color: #58a6ff; }
192
- .stat-card .value.yellow { color: #d29922; }
193
- .section { margin-bottom: 2rem; }
194
- .section h2 { font-size: 1.1rem; margin-bottom: 1rem; border-bottom: 1px solid #30363d; padding-bottom: 0.5rem; }
195
- table { width: 100%; border-collapse: collapse; }
196
- th, td { text-align: left; padding: 0.75rem; border-bottom: 1px solid #21262d; }
197
- th { color: #8b949e; font-size: 0.8rem; text-transform: uppercase; }
198
- td { font-size: 0.9rem; }
199
- .agent-tag { padding: 0.125rem 0.5rem; border-radius: 999px; font-size: 0.75rem; background: #1f6feb; color: #fff; }
200
- .status-running { color: #3fb950; }
201
- .status-stuck { color: #d29922; }
202
- .status-error { color: #f85149; }
203
- .status-done { color: #8b949e; }
204
- .footer { margin-top: 3rem; text-align: center; color: #484f58; font-size: 0.8rem; }
205
- </style>
206
- </head>
207
- <body>
208
- <div class="container">
209
- <header>
210
- <h1>Speexor <span>Dashboard</span></h1>
211
- <span class="status-badge">\u25CF Running</span>
212
- </header>
213
-
214
- <div class="stats">
215
- <div class="stat-card">
216
- <h3>Projects</h3>
217
- <div class="value blue" id="projectCount">-</div>
218
- </div>
219
- <div class="stat-card">
220
- <h3>Active Agents</h3>
221
- <div class="value green" id="agentCount">-</div>
222
- </div>
223
- <div class="stat-card">
224
- <h3>System Status</h3>
225
- <div class="value yellow" id="systemStatus">Healthy</div>
226
- </div>
227
- <div class="stat-card">
228
- <h3>Uptime</h3>
229
- <div class="value" id="uptime">-</div>
230
- </div>
231
- </div>
232
-
233
- <div class="section">
234
- <h2>Projects</h2>
235
- <table>
236
- <thead>
237
- <tr>
238
- <th>Name</th>
239
- <th>Repository</th>
240
- <th>Primary Agent</th>
241
- <th>Fallback</th>
242
- </tr>
243
- </thead>
244
- <tbody id="projectsBody">
245
- <tr><td colspan="4">Loading...</td></tr>
246
- </tbody>
247
- </table>
248
- </div>
249
-
250
- <div class="section">
251
- <h2>Active Sessions</h2>
252
- <table>
253
- <thead>
254
- <tr>
255
- <th>ID</th>
256
- <th>Task</th>
257
- <th>Provider</th>
258
- <th>Status</th>
259
- <th>Started</th>
260
- </tr>
261
- </thead>
262
- <tbody id="sessionsBody">
263
- <tr><td colspan="5">Loading...</td></tr>
264
- </tbody>
265
- </table>
266
- </div>
267
-
268
- <div class="footer">
269
- Speexor Agent Orchestrator &mdash; part of SpeexJS
270
- </div>
271
- </div>
272
-
273
- <script>
274
- async function fetchJSON(url) {
275
- const res = await fetch(url);
276
- return res.json();
277
- }
278
-
279
- async function refresh() {
280
- try {
281
- const [status, projects, sessions] = await Promise.all([
282
- fetchJSON('/api/status'),
283
- fetchJSON('/api/projects'),
284
- fetchJSON('/api/sessions'),
285
- ]);
286
-
287
- document.getElementById('projectCount').textContent = status.projectCount;
288
- document.getElementById('agentCount').textContent = status.activeSessions;
289
- document.getElementById('systemStatus').textContent = status.status;
290
- document.getElementById('uptime').textContent = Math.floor(status.uptime) + 's';
291
-
292
- const projectsBody = document.getElementById('projectsBody');
293
- if (projects.projects?.length) {
294
- projectsBody.innerHTML = projects.projects.map(p => \`
295
- <tr>
296
- <td><strong>\${p.name}</strong></td>
297
- <td>\${p.repository}</td>
298
- <td><span class="agent-tag">\${p.provider?.primary || '-'}</span></td>
299
- <td>\${p.provider?.fallback?.join(', ') || '-'}</td>
300
- </tr>
301
- \`).join('');
302
- }
303
-
304
- const sessionsBody = document.getElementById('sessionsBody');
305
- if (sessions.sessions?.length) {
306
- sessionsBody.innerHTML = sessions.sessions.map(s => \`
307
- <tr>
308
- <td>\${s.id}</td>
309
- <td>\${s.taskId}</td>
310
- <td><span class="agent-tag">\${s.provider}</span></td>
311
- <td class="status-\${s.status}">\${s.status}</td>
312
- <td>\${new Date(s.startedAt).toLocaleTimeString()}</td>
313
- </tr>
314
- \`).join('');
315
- }
316
- } catch (e) {
317
- console.error('Failed to refresh dashboard:', e);
318
- }
319
- }
320
-
321
- refresh();
322
- setInterval(refresh, 5000);
323
- </script>
324
- </body>
325
- </html>`;
326
- res.writeHead(200, { "Content-Type": "text/html" });
327
- res.end(html);
328
- }
329
- start() {
330
- this.server.listen(this.port, () => {
331
- debug(`Dashboard server listening on port ${this.port}`);
332
- });
333
- }
334
- stop() {
335
- this.server.close();
336
- debug("Dashboard server stopped");
337
- }
338
- };
339
- function createDashboardServer(lifecycle, port = 3e3) {
340
- return new DashboardServer(lifecycle, port);
341
- }
342
-
343
- export { DashboardServer, DashboardState, createDashboardServer };
344
- //# sourceMappingURL=chunk-AOFWQZWY.js.map
345
- //# sourceMappingURL=chunk-AOFWQZWY.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/dashboard/state.ts","../src/dashboard/server.ts"],"names":[],"mappings":"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","file":"chunk-AOFWQZWY.js","sourcesContent":["import type { SpeexorState, AgentSession, WorktreeSession, RuntimeSession, ProjectConfig } from '../core/types.js'\r\n\r\nexport class DashboardState {\r\n private state: SpeexorState = {\r\n sessions: [],\r\n worktrees: [],\r\n runtimes: [],\r\n projects: [],\r\n }\r\n\r\n private listeners: Array<() => void> = []\r\n\r\n getState(): SpeexorState {\r\n return { ...this.state }\r\n }\r\n\r\n setProjects(projects: ProjectConfig[]): void {\r\n this.state.projects = projects\r\n this.notify()\r\n }\r\n\r\n addSession(session: AgentSession): void {\r\n this.state.sessions.push(session)\r\n this.notify()\r\n }\r\n\r\n updateSession(sessionId: string, updates: Partial<AgentSession>): void {\r\n const idx = this.state.sessions.findIndex((s) => s.id === sessionId)\r\n if (idx !== -1) {\r\n const entry = this.state.sessions[idx]\r\n if (entry) {\r\n if (updates.id !== undefined) entry.id = updates.id\r\n if (updates.taskId !== undefined) entry.taskId = updates.taskId\r\n if (updates.provider !== undefined) entry.provider = updates.provider\r\n if (updates.status !== undefined) entry.status = updates.status\r\n if (updates.startedAt !== undefined) entry.startedAt = updates.startedAt\r\n if (updates.runtimeSessionId !== undefined) entry.runtimeSessionId = updates.runtimeSessionId\r\n this.notify()\r\n }\r\n }\r\n }\r\n\r\n removeSession(sessionId: string): void {\r\n this.state.sessions = this.state.sessions.filter((s) => s.id !== sessionId)\r\n this.notify()\r\n }\r\n\r\n addWorktree(worktree: WorktreeSession): void {\r\n this.state.worktrees.push(worktree)\r\n this.notify()\r\n }\r\n\r\n removeWorktree(sessionId: string): void {\r\n this.state.worktrees = this.state.worktrees.filter((w) => w.id !== sessionId)\r\n this.notify()\r\n }\r\n\r\n addRuntime(runtime: RuntimeSession): void {\r\n this.state.runtimes.push(runtime)\r\n this.notify()\r\n }\r\n\r\n removeRuntime(sessionId: string): void {\r\n this.state.runtimes = this.state.runtimes.filter((r) => r.id !== sessionId)\r\n this.notify()\r\n }\r\n\r\n onUpdate(listener: () => void): () => void {\r\n this.listeners.push(listener)\r\n return () => {\r\n this.listeners = this.listeners.filter((l) => l !== listener)\r\n }\r\n }\r\n\r\n private notify(): void {\r\n for (const listener of this.listeners) {\r\n try {\r\n listener()\r\n } catch {}\r\n }\r\n }\r\n\r\n toJSON(): SpeexorState {\r\n return this.getState()\r\n }\r\n}\r\n","import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'\r\nimport { SpeexorLifecycle } from '../core/lifecycle.js'\r\nimport type { AgentSession } from '../core/types.js'\r\nimport { DashboardState } from './state.js'\r\nimport Debug from 'debug'\r\n\r\nconst debug = Debug('speexor:dashboard')\r\n\r\ninterface Route {\r\n method: string\r\n path: string\r\n handler: (req: IncomingMessage, res: ServerResponse, params: Record<string, string>) => void | Promise<void>\r\n}\r\n\r\nexport class DashboardServer {\r\n private server: ReturnType<typeof createServer>\r\n private lifecycle: SpeexorLifecycle\r\n private state: DashboardState\r\n private port: number\r\n private routes: Route[] = []\r\n\r\n constructor(lifecycle: SpeexorLifecycle, port: number = 3000) {\r\n this.lifecycle = lifecycle\r\n this.state = new DashboardState()\r\n this.port = port\r\n\r\n // Initialize state from lifecycle\r\n this.state.setProjects(lifecycle.getConfig().projects)\r\n\r\n // Listen to lifecycle events\r\n lifecycle.eventBus.on('session:created', (data: unknown) => {\r\n const { session } = data as { session: AgentSession; task: unknown }\r\n this.state.addSession(session)\r\n })\r\n\r\n lifecycle.eventBus.on('session:completed', (data: unknown) => {\r\n const { sessionId } = data as { sessionId: string }\r\n this.state.removeSession(sessionId)\r\n })\r\n\r\n this.setupRoutes()\r\n\r\n this.server = createServer((req, res) => this.handleRequest(req, res))\r\n }\r\n\r\n private setupRoutes(): void {\r\n this.routes = [\r\n { method: 'GET', path: '/api/status', handler: this.handleStatus.bind(this) },\r\n { method: 'GET', path: '/api/projects', handler: this.handleProjects.bind(this) },\r\n { method: 'GET', path: '/api/sessions', handler: this.handleSessions.bind(this) },\r\n { method: 'GET', path: '/api/health', handler: this.handleHealth.bind(this) },\r\n ]\r\n }\r\n\r\n private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\r\n // CORS headers\r\n res.setHeader('Access-Control-Allow-Origin', '*')\r\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')\r\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type')\r\n\r\n if (req.method === 'OPTIONS') {\r\n res.writeHead(204)\r\n res.end()\r\n return\r\n }\r\n\r\n // Try to match route\r\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`)\r\n const pathname = url.pathname\r\n\r\n for (const route of this.routes) {\r\n if (req.method === route.method && this.matchPath(route.path, pathname)) {\r\n try {\r\n await route.handler(req, res, {})\r\n } catch (error) {\r\n this.sendJSON(res, 500, { error: String(error) })\r\n }\r\n return\r\n }\r\n }\r\n\r\n // Serve dashboard HTML for root\r\n if (pathname === '/' || pathname === '/dashboard') {\r\n this.serveDashboardHTML(res)\r\n return\r\n }\r\n\r\n this.sendJSON(res, 404, { error: 'Not found' })\r\n }\r\n\r\n private matchPath(pattern: string, path: string): boolean {\r\n return pattern === path\r\n }\r\n\r\n private sendJSON(res: ServerResponse, status: number, data: unknown): void {\r\n res.writeHead(status, { 'Content-Type': 'application/json' })\r\n res.end(JSON.stringify(data, null, 2))\r\n }\r\n\r\n private handleStatus(_req: IncomingMessage, res: ServerResponse): void {\r\n this.sendJSON(res, 200, {\r\n status: this.lifecycle.getStatus(),\r\n projectCount: this.lifecycle.getConfig().projects.length,\r\n activeSessions: this.lifecycle.listSessions().length,\r\n uptime: process.uptime(),\r\n })\r\n }\r\n\r\n private handleProjects(_req: IncomingMessage, res: ServerResponse): void {\r\n this.sendJSON(res, 200, {\r\n projects: this.lifecycle.getConfig().projects,\r\n })\r\n }\r\n\r\n private handleSessions(_req: IncomingMessage, res: ServerResponse): void {\r\n this.sendJSON(res, 200, {\r\n sessions: this.lifecycle.listSessions(),\r\n worktrees: [], // placeholder\r\n runtimes: [], // placeholder\r\n })\r\n }\r\n\r\n private handleHealth(_req: IncomingMessage, res: ServerResponse): void {\r\n this.sendJSON(res, 200, {\r\n status: 'healthy',\r\n timestamp: new Date().toISOString(),\r\n memory: process.memoryUsage(),\r\n })\r\n }\r\n\r\n private serveDashboardHTML(res: ServerResponse): void {\r\n const html = `<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n <meta charset=\"UTF-8\" />\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\r\n <title>Speexor Dashboard</title>\r\n <style>\r\n * { margin: 0; padding: 0; box-sizing: border-box; }\r\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0d1117; color: #c9d1d9; }\r\n .container { max-width: 1200px; margin: 0 auto; padding: 2rem; }\r\n header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; }\r\n h1 { font-size: 1.5rem; color: #58a6ff; }\r\n h1 span { color: #8b949e; font-weight: normal; }\r\n .status-badge { padding: 0.25rem 0.75rem; border-radius: 999px; font-size: 0.8rem; background: #238636; color: #fff; }\r\n .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }\r\n .stat-card { background: #161b22; border: 1px solid #30363d; border-radius: 6px; padding: 1.25rem; }\r\n .stat-card h3 { font-size: 0.75rem; text-transform: uppercase; color: #8b949e; margin-bottom: 0.5rem; }\r\n .stat-card .value { font-size: 2rem; font-weight: 600; }\r\n .stat-card .value.green { color: #3fb950; }\r\n .stat-card .value.blue { color: #58a6ff; }\r\n .stat-card .value.yellow { color: #d29922; }\r\n .section { margin-bottom: 2rem; }\r\n .section h2 { font-size: 1.1rem; margin-bottom: 1rem; border-bottom: 1px solid #30363d; padding-bottom: 0.5rem; }\r\n table { width: 100%; border-collapse: collapse; }\r\n th, td { text-align: left; padding: 0.75rem; border-bottom: 1px solid #21262d; }\r\n th { color: #8b949e; font-size: 0.8rem; text-transform: uppercase; }\r\n td { font-size: 0.9rem; }\r\n .agent-tag { padding: 0.125rem 0.5rem; border-radius: 999px; font-size: 0.75rem; background: #1f6feb; color: #fff; }\r\n .status-running { color: #3fb950; }\r\n .status-stuck { color: #d29922; }\r\n .status-error { color: #f85149; }\r\n .status-done { color: #8b949e; }\r\n .footer { margin-top: 3rem; text-align: center; color: #484f58; font-size: 0.8rem; }\r\n </style>\r\n</head>\r\n<body>\r\n <div class=\"container\">\r\n <header>\r\n <h1>Speexor <span>Dashboard</span></h1>\r\n <span class=\"status-badge\">● Running</span>\r\n </header>\r\n \r\n <div class=\"stats\">\r\n <div class=\"stat-card\">\r\n <h3>Projects</h3>\r\n <div class=\"value blue\" id=\"projectCount\">-</div>\r\n </div>\r\n <div class=\"stat-card\">\r\n <h3>Active Agents</h3>\r\n <div class=\"value green\" id=\"agentCount\">-</div>\r\n </div>\r\n <div class=\"stat-card\">\r\n <h3>System Status</h3>\r\n <div class=\"value yellow\" id=\"systemStatus\">Healthy</div>\r\n </div>\r\n <div class=\"stat-card\">\r\n <h3>Uptime</h3>\r\n <div class=\"value\" id=\"uptime\">-</div>\r\n </div>\r\n </div>\r\n \r\n <div class=\"section\">\r\n <h2>Projects</h2>\r\n <table>\r\n <thead>\r\n <tr>\r\n <th>Name</th>\r\n <th>Repository</th>\r\n <th>Primary Agent</th>\r\n <th>Fallback</th>\r\n </tr>\r\n </thead>\r\n <tbody id=\"projectsBody\">\r\n <tr><td colspan=\"4\">Loading...</td></tr>\r\n </tbody>\r\n </table>\r\n </div>\r\n \r\n <div class=\"section\">\r\n <h2>Active Sessions</h2>\r\n <table>\r\n <thead>\r\n <tr>\r\n <th>ID</th>\r\n <th>Task</th>\r\n <th>Provider</th>\r\n <th>Status</th>\r\n <th>Started</th>\r\n </tr>\r\n </thead>\r\n <tbody id=\"sessionsBody\">\r\n <tr><td colspan=\"5\">Loading...</td></tr>\r\n </tbody>\r\n </table>\r\n </div>\r\n \r\n <div class=\"footer\">\r\n Speexor Agent Orchestrator &mdash; part of SpeexJS\r\n </div>\r\n </div>\r\n \r\n <script>\r\n async function fetchJSON(url) {\r\n const res = await fetch(url);\r\n return res.json();\r\n }\r\n \r\n async function refresh() {\r\n try {\r\n const [status, projects, sessions] = await Promise.all([\r\n fetchJSON('/api/status'),\r\n fetchJSON('/api/projects'),\r\n fetchJSON('/api/sessions'),\r\n ]);\r\n \r\n document.getElementById('projectCount').textContent = status.projectCount;\r\n document.getElementById('agentCount').textContent = status.activeSessions;\r\n document.getElementById('systemStatus').textContent = status.status;\r\n document.getElementById('uptime').textContent = Math.floor(status.uptime) + 's';\r\n \r\n const projectsBody = document.getElementById('projectsBody');\r\n if (projects.projects?.length) {\r\n projectsBody.innerHTML = projects.projects.map(p => \\`\r\n <tr>\r\n <td><strong>\\${p.name}</strong></td>\r\n <td>\\${p.repository}</td>\r\n <td><span class=\"agent-tag\">\\${p.provider?.primary || '-'}</span></td>\r\n <td>\\${p.provider?.fallback?.join(', ') || '-'}</td>\r\n </tr>\r\n \\`).join('');\r\n }\r\n \r\n const sessionsBody = document.getElementById('sessionsBody');\r\n if (sessions.sessions?.length) {\r\n sessionsBody.innerHTML = sessions.sessions.map(s => \\`\r\n <tr>\r\n <td>\\${s.id}</td>\r\n <td>\\${s.taskId}</td>\r\n <td><span class=\"agent-tag\">\\${s.provider}</span></td>\r\n <td class=\"status-\\${s.status}\">\\${s.status}</td>\r\n <td>\\${new Date(s.startedAt).toLocaleTimeString()}</td>\r\n </tr>\r\n \\`).join('');\r\n }\r\n } catch (e) {\r\n console.error('Failed to refresh dashboard:', e);\r\n }\r\n }\r\n \r\n refresh();\r\n setInterval(refresh, 5000);\r\n </script>\r\n</body>\r\n</html>`\r\n\r\n res.writeHead(200, { 'Content-Type': 'text/html' })\r\n res.end(html)\r\n }\r\n\r\n start(): void {\r\n this.server.listen(this.port, () => {\r\n debug(`Dashboard server listening on port ${this.port}`)\r\n })\r\n }\r\n\r\n stop(): void {\r\n this.server.close()\r\n debug('Dashboard server stopped')\r\n }\r\n}\r\n\r\nexport function createDashboardServer(lifecycle: SpeexorLifecycle, port: number = 3000): DashboardServer {\r\n return new DashboardServer(lifecycle, port)\r\n}\r\n"]}
@@ -1,205 +0,0 @@
1
- type AgentStatus = 'idle' | 'running' | 'stuck' | 'needs-review' | 'done' | 'error';
2
- type AgentProvider = 'opencode' | 'claude-code' | 'aider' | 'codex';
3
- type RuntimeType = 'tmux' | 'process';
4
- type SessionStatus = 'initializing' | 'active' | 'paused' | 'completed' | 'failed' | 'cancelled';
5
- interface PluginModule {
6
- name: string;
7
- version: string;
8
- type: PluginSlot;
9
- initialize(context: PluginContext): Promise<void>;
10
- destroy(): Promise<void>;
11
- }
12
- type PluginSlot = 'agent' | 'runtime' | 'workspace' | 'tracker' | 'scm' | 'notifier' | 'terminal';
13
- interface PluginContext {
14
- config: SpeexorConfig;
15
- eventBus: EventBus;
16
- logger: (msg: string) => void;
17
- }
18
- interface AgentPlugin extends PluginModule {
19
- type: 'agent';
20
- spawn(task: AgentTask, runtime: RuntimeSession): Promise<AgentSession>;
21
- sendInput(sessionId: string, input: string): Promise<void>;
22
- getStatus(sessionId: string): Promise<AgentStatus>;
23
- kill(sessionId: string): Promise<void>;
24
- }
25
- interface AgentTask {
26
- id: string;
27
- title: string;
28
- description: string;
29
- repository: string;
30
- branch: string;
31
- provider?: AgentProvider;
32
- model?: string;
33
- }
34
- interface AgentSession {
35
- id: string;
36
- taskId: string;
37
- provider: AgentProvider;
38
- status: AgentStatus;
39
- startedAt: Date;
40
- runtimeSessionId: string;
41
- }
42
- interface RuntimePlugin extends PluginModule {
43
- type: 'runtime';
44
- createSession(worktreePath: string): Promise<RuntimeSession>;
45
- destroySession(sessionId: string): Promise<void>;
46
- sendInput(sessionId: string, input: string): Promise<void>;
47
- getOutput(sessionId: string): Promise<string>;
48
- getLiveStream(sessionId: string): AsyncIterable<string>;
49
- getStatus(sessionId: string): Promise<'running' | 'stopped' | 'error'>;
50
- }
51
- interface RuntimeSession {
52
- id: string;
53
- type: RuntimeType;
54
- worktreePath: string;
55
- pid?: number;
56
- createdAt: Date;
57
- }
58
- interface WorkspacePlugin extends PluginModule {
59
- type: 'workspace';
60
- createWorktree(task: AgentTask): Promise<WorktreeSession>;
61
- removeWorktree(sessionId: string): Promise<void>;
62
- getWorktreePath(sessionId: string): string;
63
- listActive(): Promise<WorktreeSession[]>;
64
- cleanupStale(): Promise<string[]>;
65
- }
66
- interface WorktreeSession {
67
- id: string;
68
- taskId: string;
69
- repository: string;
70
- branch: string;
71
- path: string;
72
- createdAt: Date;
73
- }
74
- interface TrackerPlugin extends PluginModule {
75
- type: 'tracker';
76
- fetchIssues(filter?: TrackerFilter): Promise<TrackerIssue[]>;
77
- getIssue(id: string): Promise<TrackerIssue | null>;
78
- onEvent(handler: (event: TrackerEvent) => void): void;
79
- }
80
- interface TrackerFilter {
81
- state?: 'open' | 'closed' | 'all';
82
- labels?: string[];
83
- since?: Date;
84
- limit?: number;
85
- }
86
- interface TrackerIssue {
87
- id: string;
88
- title: string;
89
- description: string;
90
- state: 'open' | 'closed';
91
- labels: string[];
92
- url: string;
93
- createdAt: Date;
94
- updatedAt: Date;
95
- }
96
- type TrackerEventType = 'issue-opened' | 'issue-closed' | 'ci-failed' | 'ci-passed' | 'changes-requested' | 'approved';
97
- interface TrackerEvent {
98
- type: TrackerEventType;
99
- issueId: string;
100
- timestamp: Date;
101
- data: Record<string, unknown>;
102
- }
103
- interface SCMPlugin extends PluginModule {
104
- type: 'scm';
105
- createBranch(baseBranch: string, newBranch: string): Promise<void>;
106
- commitAndPush(branch: string, message: string): Promise<string>;
107
- createPullRequest(title: string, description: string, head: string, base: string): Promise<PRInfo>;
108
- getPRStatus(prId: string): Promise<PRStatus>;
109
- getPRComments(prId: string): Promise<PRComment[]>;
110
- getCIRuns(prId: string): Promise<CIRun[]>;
111
- mergePR(prId: string, method?: 'merge' | 'squash' | 'rebase'): Promise<void>;
112
- onEvent(handler: (event: TrackerEvent) => void): void;
113
- }
114
- interface PRInfo {
115
- id: string;
116
- url: string;
117
- title: string;
118
- state: 'open' | 'closed' | 'merged';
119
- headBranch: string;
120
- baseBranch: string;
121
- createdAt: Date;
122
- }
123
- interface PRStatus {
124
- id: string;
125
- state: 'open' | 'closed' | 'merged';
126
- mergeable: boolean;
127
- ciStatus: 'pending' | 'passing' | 'failing' | 'unknown';
128
- reviewStatus: 'approved' | 'changes-requested' | 'pending' | 'none';
129
- }
130
- interface PRComment {
131
- id: string;
132
- author: string;
133
- body: string;
134
- file?: string;
135
- line?: number;
136
- createdAt: Date;
137
- }
138
- interface CIRun {
139
- id: string;
140
- name: string;
141
- status: 'queued' | 'in_progress' | 'completed';
142
- conclusion: 'success' | 'failure' | 'cancelled' | 'skipped' | null;
143
- url: string;
144
- }
145
- interface NotifierPlugin extends PluginModule {
146
- type: 'notifier';
147
- notify(level: 'info' | 'warn' | 'error' | 'success', title: string, message: string): Promise<void>;
148
- }
149
- interface TerminalPlugin extends PluginModule {
150
- type: 'terminal';
151
- attach(sessionId: string): Promise<void>;
152
- detach(sessionId: string): Promise<void>;
153
- write(sessionId: string, data: string): Promise<void>;
154
- onData(sessionId: string, handler: (data: string) => void): void;
155
- }
156
- interface EventBus {
157
- emit(event: string, data: unknown): void;
158
- on(event: string, handler: (data: unknown) => void): void;
159
- off(event: string, handler: (data: unknown) => void): void;
160
- once(event: string, handler: (data: unknown) => void): void;
161
- }
162
- interface ReactionConfig {
163
- 'ci-failed': ReactionRule;
164
- 'changes-requested': ReactionRule;
165
- 'approved-and-green': ReactionRule;
166
- }
167
- interface ReactionRule {
168
- auto: boolean;
169
- action: 'fix' | 'notify' | 'escalate' | 'skip';
170
- retries: number;
171
- escalateAfter: number;
172
- }
173
- interface ProviderRouting {
174
- primary: AgentProvider;
175
- fallback?: AgentProvider[];
176
- concurrentLimit?: number;
177
- costLimit?: number;
178
- }
179
- interface SpeexorConfig {
180
- version: '1';
181
- projects: ProjectConfig[];
182
- }
183
- interface ProjectConfig {
184
- name: string;
185
- repository: string;
186
- path?: string;
187
- branch?: string;
188
- provider: ProviderRouting;
189
- reactions?: Partial<ReactionConfig>;
190
- plugins?: {
191
- tracker?: string;
192
- scm?: string;
193
- runtime?: string;
194
- notifier?: string;
195
- };
196
- }
197
- interface SpeexorState {
198
- sessions: AgentSession[];
199
- worktrees: WorktreeSession[];
200
- runtimes: RuntimeSession[];
201
- projects: ProjectConfig[];
202
- }
203
- type SessionEventType = 'session:created' | 'session:status-changed' | 'session:completed' | 'session:failed' | 'worktree:created' | 'worktree:removed' | 'reaction:triggered' | 'reaction:completed' | 'error';
204
-
205
- export type { AgentSession as A, CIRun as C, EventBus as E, NotifierPlugin as N, PluginModule as P, RuntimeSession as R, SpeexorState as S, TrackerEvent as T, WorktreeSession as W, ProjectConfig as a, AgentPlugin as b, AgentProvider as c, AgentStatus as d, AgentTask as e, PRComment as f, PRInfo as g, PRStatus as h, PluginContext as i, PluginSlot as j, ProviderRouting as k, ReactionConfig as l, ReactionRule as m, RuntimePlugin as n, RuntimeType as o, SCMPlugin as p, SessionEventType as q, SessionStatus as r, SpeexorConfig as s, TerminalPlugin as t, TrackerEventType as u, TrackerFilter as v, TrackerIssue as w, TrackerPlugin as x, WorkspacePlugin as y };