speexor 0.1.1 → 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.
- package/API-REFERENCE.md +96 -1
- package/ARCHITECTURE.md +83 -32
- package/BENCHMARKS.md +73 -0
- package/CHANGELOG.md +59 -4
- package/CODE-OF-CONDUCT.md +83 -83
- package/CONTRIBUTING.md +92 -97
- package/FAQ.md +132 -105
- package/GLOSSARY.md +34 -0
- package/LICENSE.md +21 -21
- package/PUBLISH.md +82 -77
- package/README.md +220 -6
- package/REFACTOR-LOG.md +40 -40
- package/ROADMAP.md +31 -42
- package/SECURITY-DEFAULTS.md +118 -0
- package/SECURITY.md +80 -79
- package/SUMMARY.md +31 -8
- package/TESTING.md +140 -140
- package/dist/{agent-5D3BVWNK.js → agent-C64T66XT.js} +4 -4
- package/dist/agent-C64T66XT.js.map +1 -0
- package/dist/{chunk-B7WLHC4W.js → chunk-5OD5UWB5.js} +322 -121
- package/dist/chunk-5OD5UWB5.js.map +1 -0
- package/dist/chunk-GOGI3JQD.js +1637 -0
- package/dist/chunk-GOGI3JQD.js.map +1 -0
- package/dist/{chunk-2F66BZYJ.js → chunk-VEZQT5SX.js} +80 -8
- package/dist/chunk-VEZQT5SX.js.map +1 -0
- package/dist/cli/index.js +2058 -18
- package/dist/cli/index.js.map +1 -1
- package/dist/core/index.d.ts +682 -3
- package/dist/core/index.js +1 -1
- package/dist/index.d.ts +102 -14
- package/dist/index.js +55 -29
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/index.js +1 -1
- package/dist/types-BOMap-tI.d.ts +389 -0
- package/docs/PRD03.md +119 -0
- package/docs/PRD06.md +125 -0
- package/docs/SETUP.md +94 -94
- package/docs/TROUBLESHOOTING.md +113 -113
- package/docs/adr/0001-record-architecture-decisions.md +44 -0
- package/docs/adr/0002-plugin-architecture.md +53 -0
- package/docs/adr/0003-recursive-task-decomposition.md +57 -0
- package/docs/adr/0004-local-first-security.md +58 -0
- package/docs/adr/0005-data-directory-layout.md +69 -0
- package/examples/basic.yaml +61 -61
- package/package.json +103 -102
- package/schema/config.schema.json +119 -119
- package/speexor.config.yaml.example +30 -30
- package/dist/agent-5D3BVWNK.js.map +0 -1
- package/dist/chunk-2F66BZYJ.js.map +0 -1
- package/dist/chunk-B7WLHC4W.js.map +0 -1
- package/dist/chunk-SXALZEOJ.js +0 -345
- package/dist/chunk-SXALZEOJ.js.map +0 -1
- package/dist/types-0q_okI2g.d.ts +0 -205
package/dist/chunk-SXALZEOJ.js
DELETED
|
@@ -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 — 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-SXALZEOJ.js.map
|
|
345
|
-
//# sourceMappingURL=chunk-SXALZEOJ.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-SXALZEOJ.js","sourcesContent":["import type { SpeexorState, AgentSession, WorktreeSession, RuntimeSession, ProjectConfig } from '../core/types.js'\n\nexport class DashboardState {\n private state: SpeexorState = {\n sessions: [],\n worktrees: [],\n runtimes: [],\n projects: [],\n }\n\n private listeners: Array<() => void> = []\n\n getState(): SpeexorState {\n return { ...this.state }\n }\n\n setProjects(projects: ProjectConfig[]): void {\n this.state.projects = projects\n this.notify()\n }\n\n addSession(session: AgentSession): void {\n this.state.sessions.push(session)\n this.notify()\n }\n\n updateSession(sessionId: string, updates: Partial<AgentSession>): void {\n const idx = this.state.sessions.findIndex((s) => s.id === sessionId)\n if (idx !== -1) {\n const entry = this.state.sessions[idx]\n if (entry) {\n if (updates.id !== undefined) entry.id = updates.id\n if (updates.taskId !== undefined) entry.taskId = updates.taskId\n if (updates.provider !== undefined) entry.provider = updates.provider\n if (updates.status !== undefined) entry.status = updates.status\n if (updates.startedAt !== undefined) entry.startedAt = updates.startedAt\n if (updates.runtimeSessionId !== undefined) entry.runtimeSessionId = updates.runtimeSessionId\n this.notify()\n }\n }\n }\n\n removeSession(sessionId: string): void {\n this.state.sessions = this.state.sessions.filter((s) => s.id !== sessionId)\n this.notify()\n }\n\n addWorktree(worktree: WorktreeSession): void {\n this.state.worktrees.push(worktree)\n this.notify()\n }\n\n removeWorktree(sessionId: string): void {\n this.state.worktrees = this.state.worktrees.filter((w) => w.id !== sessionId)\n this.notify()\n }\n\n addRuntime(runtime: RuntimeSession): void {\n this.state.runtimes.push(runtime)\n this.notify()\n }\n\n removeRuntime(sessionId: string): void {\n this.state.runtimes = this.state.runtimes.filter((r) => r.id !== sessionId)\n this.notify()\n }\n\n onUpdate(listener: () => void): () => void {\n this.listeners.push(listener)\n return () => {\n this.listeners = this.listeners.filter((l) => l !== listener)\n }\n }\n\n private notify(): void {\n for (const listener of this.listeners) {\n try {\n listener()\n } catch {}\n }\n }\n\n toJSON(): SpeexorState {\n return this.getState()\n }\n}\n","import { createServer, type IncomingMessage, type ServerResponse } from 'node:http'\nimport { SpeexorLifecycle } from '../core/lifecycle.js'\nimport type { AgentSession } from '../core/types.js'\nimport { DashboardState } from './state.js'\nimport Debug from 'debug'\n\nconst debug = Debug('speexor:dashboard')\n\ninterface Route {\n method: string\n path: string\n handler: (req: IncomingMessage, res: ServerResponse, params: Record<string, string>) => void | Promise<void>\n}\n\nexport class DashboardServer {\n private server: ReturnType<typeof createServer>\n private lifecycle: SpeexorLifecycle\n private state: DashboardState\n private port: number\n private routes: Route[] = []\n\n constructor(lifecycle: SpeexorLifecycle, port: number = 3000) {\n this.lifecycle = lifecycle\n this.state = new DashboardState()\n this.port = port\n\n // Initialize state from lifecycle\n this.state.setProjects(lifecycle.getConfig().projects)\n\n // Listen to lifecycle events\n lifecycle.eventBus.on('session:created', (data: unknown) => {\n const { session } = data as { session: AgentSession; task: unknown }\n this.state.addSession(session)\n })\n\n lifecycle.eventBus.on('session:completed', (data: unknown) => {\n const { sessionId } = data as { sessionId: string }\n this.state.removeSession(sessionId)\n })\n\n this.setupRoutes()\n\n this.server = createServer((req, res) => this.handleRequest(req, res))\n }\n\n private setupRoutes(): void {\n this.routes = [\n { method: 'GET', path: '/api/status', handler: this.handleStatus.bind(this) },\n { method: 'GET', path: '/api/projects', handler: this.handleProjects.bind(this) },\n { method: 'GET', path: '/api/sessions', handler: this.handleSessions.bind(this) },\n { method: 'GET', path: '/api/health', handler: this.handleHealth.bind(this) },\n ]\n }\n\n private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n // CORS headers\n res.setHeader('Access-Control-Allow-Origin', '*')\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type')\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204)\n res.end()\n return\n }\n\n // Try to match route\n const url = new URL(req.url ?? '/', `http://${req.headers.host ?? 'localhost'}`)\n const pathname = url.pathname\n\n for (const route of this.routes) {\n if (req.method === route.method && this.matchPath(route.path, pathname)) {\n try {\n await route.handler(req, res, {})\n } catch (error) {\n this.sendJSON(res, 500, { error: String(error) })\n }\n return\n }\n }\n\n // Serve dashboard HTML for root\n if (pathname === '/' || pathname === '/dashboard') {\n this.serveDashboardHTML(res)\n return\n }\n\n this.sendJSON(res, 404, { error: 'Not found' })\n }\n\n private matchPath(pattern: string, path: string): boolean {\n return pattern === path\n }\n\n private sendJSON(res: ServerResponse, status: number, data: unknown): void {\n res.writeHead(status, { 'Content-Type': 'application/json' })\n res.end(JSON.stringify(data, null, 2))\n }\n\n private handleStatus(_req: IncomingMessage, res: ServerResponse): void {\n this.sendJSON(res, 200, {\n status: this.lifecycle.getStatus(),\n projectCount: this.lifecycle.getConfig().projects.length,\n activeSessions: this.lifecycle.listSessions().length,\n uptime: process.uptime(),\n })\n }\n\n private handleProjects(_req: IncomingMessage, res: ServerResponse): void {\n this.sendJSON(res, 200, {\n projects: this.lifecycle.getConfig().projects,\n })\n }\n\n private handleSessions(_req: IncomingMessage, res: ServerResponse): void {\n this.sendJSON(res, 200, {\n sessions: this.lifecycle.listSessions(),\n worktrees: [], // placeholder\n runtimes: [], // placeholder\n })\n }\n\n private handleHealth(_req: IncomingMessage, res: ServerResponse): void {\n this.sendJSON(res, 200, {\n status: 'healthy',\n timestamp: new Date().toISOString(),\n memory: process.memoryUsage(),\n })\n }\n\n private serveDashboardHTML(res: ServerResponse): void {\n const html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Speexor Dashboard</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0d1117; color: #c9d1d9; }\n .container { max-width: 1200px; margin: 0 auto; padding: 2rem; }\n header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; }\n h1 { font-size: 1.5rem; color: #58a6ff; }\n h1 span { color: #8b949e; font-weight: normal; }\n .status-badge { padding: 0.25rem 0.75rem; border-radius: 999px; font-size: 0.8rem; background: #238636; color: #fff; }\n .stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }\n .stat-card { background: #161b22; border: 1px solid #30363d; border-radius: 6px; padding: 1.25rem; }\n .stat-card h3 { font-size: 0.75rem; text-transform: uppercase; color: #8b949e; margin-bottom: 0.5rem; }\n .stat-card .value { font-size: 2rem; font-weight: 600; }\n .stat-card .value.green { color: #3fb950; }\n .stat-card .value.blue { color: #58a6ff; }\n .stat-card .value.yellow { color: #d29922; }\n .section { margin-bottom: 2rem; }\n .section h2 { font-size: 1.1rem; margin-bottom: 1rem; border-bottom: 1px solid #30363d; padding-bottom: 0.5rem; }\n table { width: 100%; border-collapse: collapse; }\n th, td { text-align: left; padding: 0.75rem; border-bottom: 1px solid #21262d; }\n th { color: #8b949e; font-size: 0.8rem; text-transform: uppercase; }\n td { font-size: 0.9rem; }\n .agent-tag { padding: 0.125rem 0.5rem; border-radius: 999px; font-size: 0.75rem; background: #1f6feb; color: #fff; }\n .status-running { color: #3fb950; }\n .status-stuck { color: #d29922; }\n .status-error { color: #f85149; }\n .status-done { color: #8b949e; }\n .footer { margin-top: 3rem; text-align: center; color: #484f58; font-size: 0.8rem; }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <header>\n <h1>Speexor <span>Dashboard</span></h1>\n <span class=\"status-badge\">● Running</span>\n </header>\n \n <div class=\"stats\">\n <div class=\"stat-card\">\n <h3>Projects</h3>\n <div class=\"value blue\" id=\"projectCount\">-</div>\n </div>\n <div class=\"stat-card\">\n <h3>Active Agents</h3>\n <div class=\"value green\" id=\"agentCount\">-</div>\n </div>\n <div class=\"stat-card\">\n <h3>System Status</h3>\n <div class=\"value yellow\" id=\"systemStatus\">Healthy</div>\n </div>\n <div class=\"stat-card\">\n <h3>Uptime</h3>\n <div class=\"value\" id=\"uptime\">-</div>\n </div>\n </div>\n \n <div class=\"section\">\n <h2>Projects</h2>\n <table>\n <thead>\n <tr>\n <th>Name</th>\n <th>Repository</th>\n <th>Primary Agent</th>\n <th>Fallback</th>\n </tr>\n </thead>\n <tbody id=\"projectsBody\">\n <tr><td colspan=\"4\">Loading...</td></tr>\n </tbody>\n </table>\n </div>\n \n <div class=\"section\">\n <h2>Active Sessions</h2>\n <table>\n <thead>\n <tr>\n <th>ID</th>\n <th>Task</th>\n <th>Provider</th>\n <th>Status</th>\n <th>Started</th>\n </tr>\n </thead>\n <tbody id=\"sessionsBody\">\n <tr><td colspan=\"5\">Loading...</td></tr>\n </tbody>\n </table>\n </div>\n \n <div class=\"footer\">\n Speexor Agent Orchestrator — part of SpeexJS\n </div>\n </div>\n \n <script>\n async function fetchJSON(url) {\n const res = await fetch(url);\n return res.json();\n }\n \n async function refresh() {\n try {\n const [status, projects, sessions] = await Promise.all([\n fetchJSON('/api/status'),\n fetchJSON('/api/projects'),\n fetchJSON('/api/sessions'),\n ]);\n \n document.getElementById('projectCount').textContent = status.projectCount;\n document.getElementById('agentCount').textContent = status.activeSessions;\n document.getElementById('systemStatus').textContent = status.status;\n document.getElementById('uptime').textContent = Math.floor(status.uptime) + 's';\n \n const projectsBody = document.getElementById('projectsBody');\n if (projects.projects?.length) {\n projectsBody.innerHTML = projects.projects.map(p => \\`\n <tr>\n <td><strong>\\${p.name}</strong></td>\n <td>\\${p.repository}</td>\n <td><span class=\"agent-tag\">\\${p.provider?.primary || '-'}</span></td>\n <td>\\${p.provider?.fallback?.join(', ') || '-'}</td>\n </tr>\n \\`).join('');\n }\n \n const sessionsBody = document.getElementById('sessionsBody');\n if (sessions.sessions?.length) {\n sessionsBody.innerHTML = sessions.sessions.map(s => \\`\n <tr>\n <td>\\${s.id}</td>\n <td>\\${s.taskId}</td>\n <td><span class=\"agent-tag\">\\${s.provider}</span></td>\n <td class=\"status-\\${s.status}\">\\${s.status}</td>\n <td>\\${new Date(s.startedAt).toLocaleTimeString()}</td>\n </tr>\n \\`).join('');\n }\n } catch (e) {\n console.error('Failed to refresh dashboard:', e);\n }\n }\n \n refresh();\n setInterval(refresh, 5000);\n </script>\n</body>\n</html>`\n\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(html)\n }\n\n start(): void {\n this.server.listen(this.port, () => {\n debug(`Dashboard server listening on port ${this.port}`)\n })\n }\n\n stop(): void {\n this.server.close()\n debug('Dashboard server stopped')\n }\n}\n\nexport function createDashboardServer(lifecycle: SpeexorLifecycle, port: number = 3000): DashboardServer {\n return new DashboardServer(lifecycle, port)\n}\n"]}
|
package/dist/types-0q_okI2g.d.ts
DELETED
|
@@ -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 };
|