speexor 0.1.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/API-REFERENCE.md +201 -0
- package/ARCHITECTURE.md +548 -0
- package/CHANGELOG.md +52 -0
- package/CODE-OF-CONDUCT.md +83 -0
- package/CONTRIBUTING.md +98 -0
- package/FAQ.md +105 -0
- package/LICENSE.md +21 -0
- package/PUBLISH.md +77 -0
- package/README.md +179 -0
- package/REFACTOR-LOG.md +40 -0
- package/ROADMAP.md +78 -0
- package/SECURITY.md +79 -0
- package/SUMMARY.md +46 -0
- package/TESTING.md +140 -0
- package/dist/agent-5D3BVWNK.js +37 -0
- package/dist/agent-5D3BVWNK.js.map +1 -0
- package/dist/chunk-2F66BZYJ.js +212 -0
- package/dist/chunk-2F66BZYJ.js.map +1 -0
- package/dist/chunk-5NA2TFPG.js +3 -0
- package/dist/chunk-5NA2TFPG.js.map +1 -0
- package/dist/chunk-B7WLHC4W.js +666 -0
- package/dist/chunk-B7WLHC4W.js.map +1 -0
- package/dist/chunk-SXALZEOJ.js +345 -0
- package/dist/chunk-SXALZEOJ.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +287 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/index.d.ts +31 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +205 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/index.d.ts +6 -0
- package/dist/plugins/index.js +3 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/types-0q_okI2g.d.ts +205 -0
- package/docs/PRD01.md +264 -0
- package/docs/PRD02.md +299 -0
- package/docs/PRD03.md +0 -0
- package/docs/PRD04.md +349 -0
- package/docs/PRD05.md +312 -0
- package/docs/SETUP.md +94 -0
- package/docs/TROUBLESHOOTING.md +113 -0
- package/examples/basic.yaml +61 -0
- package/package.json +102 -0
- package/schema/config.schema.json +119 -0
- package/speexor.config.yaml.example +30 -0
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
# Speexor Architecture
|
|
2
|
+
|
|
3
|
+
**Version:** 0.1.0 — **Package:** `@speexjs/speexor`
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Overview
|
|
8
|
+
|
|
9
|
+
Speexor (Agent Orchestrator) is a plugin-based, agent-agnostic orchestrator for multi-AI coding agents. It manages the full lifecycle of autonomous coding agents across repositories — spawning agents in isolated worktrees, routing between provider backends, reacting to CI/PR events, and providing a real-time dashboard.
|
|
10
|
+
|
|
11
|
+
**Philosophy:**
|
|
12
|
+
|
|
13
|
+
- **Plugin-first architecture** — every capability is a pluggable module behind a typed interface.
|
|
14
|
+
- **Agent-agnostic** — the orchestrator does not care which coding agent runs. OpenCode, Claude Code, Aider, and Codex are all first-class citizens.
|
|
15
|
+
- **Isolation by design** — each agent works in its own `git worktree` with its own runtime session (tmux or subprocess).
|
|
16
|
+
- **Event-driven** — lifecycle events, tracker events, and reaction triggers flow through a shared EventBus.
|
|
17
|
+
- **Provider routing** — per-project configuration of primary and fallback agents with concurrency and cost limits.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 2. High-Level Architecture
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
┌──────────────────────────────────────────────────────────────────────┐
|
|
25
|
+
│ CLI (Commander) │
|
|
26
|
+
│ start | agent spawn | list | stop | logs | config-help │
|
|
27
|
+
└──────────────────────────┬───────────────────────────────────────────┘
|
|
28
|
+
│
|
|
29
|
+
▼
|
|
30
|
+
┌──────────────────────────────────────────────────────────────────────┐
|
|
31
|
+
│ SpeexorLifecycle │
|
|
32
|
+
│ │
|
|
33
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────────────┐ │
|
|
34
|
+
│ │ Plugin │ │ Plugin │ │ Plugin │ │ Session Manager │ │
|
|
35
|
+
│ │ Registry │ │ Loader │ │ Context │ │ (in-memory Map) │ │
|
|
36
|
+
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────────────────────┘ │
|
|
37
|
+
│ │ │ │ │
|
|
38
|
+
│ ▼ ▼ ▼ │
|
|
39
|
+
│ ┌──────────────────────────────────────────────────────────────┐ │
|
|
40
|
+
│ │ EventBus │ │
|
|
41
|
+
│ │ (EventEmitter3 wrapper) │ │
|
|
42
|
+
│ └──────────────────────────────────────────────────────────────┘ │
|
|
43
|
+
└──────────────────────────────────────────────────────────────────────┘
|
|
44
|
+
│ │ │ │
|
|
45
|
+
▼ ▼ ▼ ▼
|
|
46
|
+
┌─────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────────┐
|
|
47
|
+
│ Agent │ │ Runtime │ │Workspace │ │ Tracker │ SCM │
|
|
48
|
+
│ Adapters │ │ Adapters │ │ Adapters │ │ Adapters│Adapt. │
|
|
49
|
+
│ (4 impls) │ │ (2 impls)│ │ (1 impl) │ │ (1 impl)│(1impl)│
|
|
50
|
+
└─────────────┘ └──────────┘ └──────────┘ └──────────────────┘
|
|
51
|
+
│
|
|
52
|
+
▼
|
|
53
|
+
┌──────────────────────────────────────────────────────────────────────┐
|
|
54
|
+
│ Dashboard Server (Node.js HTTP) │
|
|
55
|
+
│ /api/status | /api/projects | /api/sessions | /api/health | / │
|
|
56
|
+
│ (4 JSON endpoints + single-page HTML dashboard, auto-refresh 5s) │
|
|
57
|
+
└──────────────────────────────────────────────────────────────────────┘
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The lifecycle is the central orchestrator. It holds a plugin registry (keyed by slot), an in-memory session map, and the EventBus. Plugins are loaded via `loadAllPlugins()`, registered by slot, and collaborate through the shared `PluginContext` (config + EventBus + logger).
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 3. Plugin Architecture
|
|
65
|
+
|
|
66
|
+
### 3.1 Base Interface
|
|
67
|
+
|
|
68
|
+
Every plugin implements the `PluginModule` interface:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
interface PluginModule {
|
|
72
|
+
name: string
|
|
73
|
+
version: string
|
|
74
|
+
type: PluginSlot
|
|
75
|
+
initialize(context: PluginContext): Promise<void>
|
|
76
|
+
destroy(): Promise<void>
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface PluginContext {
|
|
80
|
+
config: SpeexorConfig
|
|
81
|
+
eventBus: EventBus
|
|
82
|
+
logger: (msg: string) => void
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 3.2 Seven Plugin Slots
|
|
87
|
+
|
|
88
|
+
| Slot | Interface | Purpose |
|
|
89
|
+
|------|-----------|---------|
|
|
90
|
+
| `agent` | `AgentPlugin` | Spawn, communicate with, and kill coding agents |
|
|
91
|
+
| `runtime` | `RuntimePlugin` | Create/destroy terminal sessions (tmux or process) |
|
|
92
|
+
| `workspace` | `WorkspacePlugin` | Manage isolated git worktrees |
|
|
93
|
+
| `tracker` | `TrackerPlugin` | Fetch issues and subscribe to tracker events |
|
|
94
|
+
| `scm` | `SCMPlugin` | Branch, commit, PR, and CI operations |
|
|
95
|
+
| `notifier` | `NotifierPlugin` | Desktop notifications (macOS/Win/Linux) |
|
|
96
|
+
| `terminal` | `TerminalPlugin` | Interactive terminal attach/detach (interface only) |
|
|
97
|
+
|
|
98
|
+
### 3.3 Slot Interface Definitions
|
|
99
|
+
|
|
100
|
+
**AgentPlugin**
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
interface AgentPlugin extends PluginModule {
|
|
104
|
+
type: 'agent'
|
|
105
|
+
spawn(task: AgentTask, runtime: RuntimeSession): Promise<AgentSession>
|
|
106
|
+
sendInput(sessionId: string, input: string): Promise<void>
|
|
107
|
+
getStatus(sessionId: string): Promise<AgentStatus>
|
|
108
|
+
kill(sessionId: string): Promise<void>
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**RuntimePlugin**
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
interface RuntimePlugin extends PluginModule {
|
|
116
|
+
type: 'runtime'
|
|
117
|
+
createSession(worktreePath: string): Promise<RuntimeSession>
|
|
118
|
+
destroySession(sessionId: string): Promise<void>
|
|
119
|
+
sendInput(sessionId: string, input: string): Promise<void>
|
|
120
|
+
getOutput(sessionId: string): Promise<string>
|
|
121
|
+
getLiveStream(sessionId: string): AsyncIterable<string>
|
|
122
|
+
getStatus(sessionId: string): Promise<'running' | 'stopped' | 'error'>
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**WorkspacePlugin**
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
interface WorkspacePlugin extends PluginModule {
|
|
130
|
+
type: 'workspace'
|
|
131
|
+
createWorktree(task: AgentTask): Promise<WorktreeSession>
|
|
132
|
+
removeWorktree(sessionId: string): Promise<void>
|
|
133
|
+
getWorktreePath(sessionId: string): string
|
|
134
|
+
listActive(): Promise<WorktreeSession[]>
|
|
135
|
+
cleanupStale(): Promise<string[]>
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**TrackerPlugin**
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
interface TrackerPlugin extends PluginModule {
|
|
143
|
+
type: 'tracker'
|
|
144
|
+
fetchIssues(filter?: TrackerFilter): Promise<TrackerIssue[]>
|
|
145
|
+
getIssue(id: string): Promise<TrackerIssue | null>
|
|
146
|
+
onEvent(handler: (event: TrackerEvent) => void): void
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**SCMPlugin**
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
interface SCMPlugin extends PluginModule {
|
|
154
|
+
type: 'scm'
|
|
155
|
+
createBranch(baseBranch: string, newBranch: string): Promise<void>
|
|
156
|
+
commitAndPush(branch: string, message: string): Promise<string>
|
|
157
|
+
createPullRequest(title: string, desc: string, head: string, base: string): Promise<PRInfo>
|
|
158
|
+
getPRStatus(prId: string): Promise<PRStatus>
|
|
159
|
+
getPRComments(prId: string): Promise<PRComment[]>
|
|
160
|
+
getCIRuns(prId: string): Promise<CIRun[]>
|
|
161
|
+
mergePR(prId: string, method?: 'merge' | 'squash' | 'rebase'): Promise<void>
|
|
162
|
+
onEvent(handler: (event: TrackerEvent) => void): void
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**NotifierPlugin**
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
interface NotifierPlugin extends PluginModule {
|
|
170
|
+
type: 'notifier'
|
|
171
|
+
notify(level: 'info' | 'warn' | 'error' | 'success', title: string, message: string): Promise<void>
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**TerminalPlugin**
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
interface TerminalPlugin extends PluginModule {
|
|
179
|
+
type: 'terminal'
|
|
180
|
+
attach(sessionId: string): Promise<void>
|
|
181
|
+
detach(sessionId: string): Promise<void>
|
|
182
|
+
write(sessionId: string, data: string): Promise<void>
|
|
183
|
+
onData(sessionId: string, handler: (data: string) => void): void
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 4. Plugin Implementations
|
|
190
|
+
|
|
191
|
+
The built-in registry (`src/plugins/index.ts`) includes 10 implementations loaded at startup:
|
|
192
|
+
|
|
193
|
+
| Plugin | Class | Slot | Dependencies |
|
|
194
|
+
|--------|-------|------|-------------|
|
|
195
|
+
| OpenCode Agent | `OpenCodeAgent` | agent | — |
|
|
196
|
+
| Claude Code Agent | `ClaudeCodeAgent` | agent | — |
|
|
197
|
+
| Aider Agent | `AiderAgent` | agent | — |
|
|
198
|
+
| Codex Agent | `CodexAgent` | agent | — |
|
|
199
|
+
| tmux Runtime | `TmuxRuntime` | runtime | `tmux -V` (Unix) |
|
|
200
|
+
| Process Runtime | `ProcessRuntime` | runtime | shell (Windows fallback) |
|
|
201
|
+
| Git Worktree | `GitWorktreeWorkspace` | workspace | `git` |
|
|
202
|
+
| GitHub Tracker | `GitHubTracker` | tracker | `gh` CLI |
|
|
203
|
+
| GitHub SCM | `GitHubSCM` | scm | `gh` CLI |
|
|
204
|
+
| Desktop Notifier | `DesktopNotifier` | notifier | osascript / PowerShell / notify-send |
|
|
205
|
+
|
|
206
|
+
**Runtime fallback logic:** The loader registers both `TmuxRuntime` and `ProcessRuntime`. `TmuxRuntime.initialize()` throws if `tmux` is not available; lifecycle initialization failure is caught per-plugin, allowing `ProcessRuntime` to serve as the fallback on Windows.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## 5. Data Flow
|
|
211
|
+
|
|
212
|
+
### 5.1 Startup Sequence
|
|
213
|
+
|
|
214
|
+
```
|
|
215
|
+
speexor start <repo-url>
|
|
216
|
+
│
|
|
217
|
+
├─ 1. generateDefaultConfig(repo) → speexor.config.yaml
|
|
218
|
+
│
|
|
219
|
+
├─ 2. loadConfig(cwd) → parse YAML → Zod validate → SpeexorConfig
|
|
220
|
+
│
|
|
221
|
+
├─ 3. new SpeexorLifecycle(config)
|
|
222
|
+
│ │
|
|
223
|
+
│ ├─ createEventBus() (EventEmitter3)
|
|
224
|
+
│ └─ status = 'initializing'
|
|
225
|
+
│
|
|
226
|
+
├─ 4. lifecycle.initialize()
|
|
227
|
+
│ └─ status = 'active'
|
|
228
|
+
│
|
|
229
|
+
├─ 5. loadAllPlugins()
|
|
230
|
+
│ ├─ new OpenCodeAgent()
|
|
231
|
+
│ ├─ new ClaudeCodeAgent()
|
|
232
|
+
│ ├─ new AiderAgent()
|
|
233
|
+
│ ├─ new CodexAgent()
|
|
234
|
+
│ ├─ new TmuxRuntime()
|
|
235
|
+
│ ├─ new ProcessRuntime()
|
|
236
|
+
│ ├─ new GitWorktreeWorkspace()
|
|
237
|
+
│ ├─ new GitHubTracker()
|
|
238
|
+
│ ├─ new GitHubSCM()
|
|
239
|
+
│ └─ new DesktopNotifier()
|
|
240
|
+
│
|
|
241
|
+
├─ 6. for each plugin → lifecycle.registerPlugin(plugin)
|
|
242
|
+
│ └─ plugin slot → Map<PluginSlot, PluginModule[]>
|
|
243
|
+
│
|
|
244
|
+
└─ 7. new DashboardServer(lifecycle, port).start()
|
|
245
|
+
└─ HTTP server listening on :3000
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### 5.2 Agent Spawn Flow
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
speexor agent spawn --task <id> [--agent opencode]
|
|
252
|
+
│
|
|
253
|
+
├─ 1. loadConfig() → SpeexorConfig
|
|
254
|
+
├─ 2. new SpeexorLifecycle(config).initialize()
|
|
255
|
+
├─ 3. loadAllPlugins() → register all
|
|
256
|
+
│
|
|
257
|
+
├─ 4. lifecycle.spawnAgent(task)
|
|
258
|
+
│ │
|
|
259
|
+
│ ├─ 4a. workspacePlugin.createWorktree(task)
|
|
260
|
+
│ │ ├─ git branch speexor/<task-id>
|
|
261
|
+
│ │ ├─ git worktree add .speexor/worktrees/<id> <branch>
|
|
262
|
+
│ │ └─ emit 'worktree:created'
|
|
263
|
+
│ │
|
|
264
|
+
│ ├─ 4b. runtimePlugin.createSession(worktreePath)
|
|
265
|
+
│ │ ├─ [tmux] tmux new-session -d -s speexor-<id> -c <path>
|
|
266
|
+
│ │ └─ [proc] spawn(shell, { cwd: <path> })
|
|
267
|
+
│ │
|
|
268
|
+
│ ├─ 4c. agentPlugin.spawn(task, runtimeSession)
|
|
269
|
+
│ │ ├─ creates AgentSession { id, taskId, provider, status, runtimeSessionId }
|
|
270
|
+
│ │ └─ emit 'session:created'
|
|
271
|
+
│ │
|
|
272
|
+
│ └─ return AgentSession
|
|
273
|
+
│
|
|
274
|
+
└─ 5. SessionStore persists session to .speexor/state.json
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### 5.3 Agent Teardown Flow
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
lifecycle.stopSession(sessionId)
|
|
281
|
+
│
|
|
282
|
+
├─ 1. agentPlugin.kill(sessionId)
|
|
283
|
+
├─ 2. runtimePlugin.destroySession(runtimeSessionId)
|
|
284
|
+
│ ├─ [tmux] tmux kill-session -t speexor-<id>
|
|
285
|
+
│ └─ [proc] SIGTERM → SIGKILL after 5s
|
|
286
|
+
├─ 3. workspacePlugin.removeWorktree(worktreeId)
|
|
287
|
+
│ ├─ git worktree remove <path>
|
|
288
|
+
│ └─ git branch -D speexor/<task-id>
|
|
289
|
+
├─ 4. sessions.delete(sessionId)
|
|
290
|
+
└─ 5. emit 'session:completed'
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## 6. Config System
|
|
296
|
+
|
|
297
|
+
### 6.1 Format
|
|
298
|
+
|
|
299
|
+
YAML files, validated at startup with Zod. The loader searches these candidates in order:
|
|
300
|
+
|
|
301
|
+
1. `speexor.config.yaml`
|
|
302
|
+
2. `speexor.config.yml`
|
|
303
|
+
3. `.speexor.yaml`
|
|
304
|
+
4. `.speexor.yml`
|
|
305
|
+
5. `speexor.yaml`
|
|
306
|
+
|
|
307
|
+
### 6.2 Schema
|
|
308
|
+
|
|
309
|
+
```yaml
|
|
310
|
+
version: "1" # literal, must be "1"
|
|
311
|
+
projects:
|
|
312
|
+
- name: my-project # string, required
|
|
313
|
+
repository: <url> # string, required
|
|
314
|
+
path: <local-path> # string, optional
|
|
315
|
+
branch: main # string, optional
|
|
316
|
+
provider:
|
|
317
|
+
primary: opencode # enum: opencode | claude-code | aider | codex
|
|
318
|
+
fallback: # array, optional
|
|
319
|
+
- claude-code
|
|
320
|
+
concurrentLimit: 3 # number (1-20), optional
|
|
321
|
+
costLimit: 1000 # number, optional (cents)
|
|
322
|
+
reactions: # optional
|
|
323
|
+
ci-failed: # reaction rule
|
|
324
|
+
auto: true
|
|
325
|
+
action: fix # enum: fix | notify | escalate | skip
|
|
326
|
+
retries: 3 # number (0-10)
|
|
327
|
+
escalateAfter: 30 # minutes (1-1440)
|
|
328
|
+
changes-requested:
|
|
329
|
+
auto: true
|
|
330
|
+
action: fix
|
|
331
|
+
retries: 2
|
|
332
|
+
escalateAfter: 60
|
|
333
|
+
approved-and-green:
|
|
334
|
+
auto: false
|
|
335
|
+
action: notify
|
|
336
|
+
retries: 0
|
|
337
|
+
escalateAfter: 0
|
|
338
|
+
plugins: # optional per-project plugin overrides
|
|
339
|
+
tracker: github-tracker
|
|
340
|
+
scm: github-scm
|
|
341
|
+
runtime: tmux-runtime
|
|
342
|
+
notifier: desktop-notifier
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### 6.3 Default Reaction Rules
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
const DEFAULT_REACTION_RULES = {
|
|
349
|
+
'ci-failed': { auto: true, action: 'fix', retries: 3, escalateAfter: 30 },
|
|
350
|
+
'changes-requested': { auto: true, action: 'fix', retries: 2, escalateAfter: 60 },
|
|
351
|
+
'approved-and-green':{ auto: false, action: 'notify', retries: 0, escalateAfter: 0 },
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
If a project omits the `reactions` block, all three defaults are applied. Partial overrides merge with defaults per event type.
|
|
356
|
+
|
|
357
|
+
---
|
|
358
|
+
|
|
359
|
+
## 7. Reaction Engine
|
|
360
|
+
|
|
361
|
+
The `ReactionEngine` listens to `TrackerEvent` instances and executes configurable automated responses.
|
|
362
|
+
|
|
363
|
+
### 7.1 Event Types
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
type TrackerEventType =
|
|
367
|
+
| 'issue-opened'
|
|
368
|
+
| 'issue-closed'
|
|
369
|
+
| 'ci-failed'
|
|
370
|
+
| 'ci-passed'
|
|
371
|
+
| 'changes-requested'
|
|
372
|
+
| 'approved'
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### 7.2 Reaction Pipeline
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
TrackerEvent
|
|
379
|
+
│
|
|
380
|
+
▼
|
|
381
|
+
ReactionEngine.processEvent(event)
|
|
382
|
+
│
|
|
383
|
+
├─ Find handlers matching event.type
|
|
384
|
+
│
|
|
385
|
+
├─ For each handler:
|
|
386
|
+
│ │
|
|
387
|
+
│ ├─ Check retry count (key = issueId:eventType)
|
|
388
|
+
│ │
|
|
389
|
+
│ ├─ if retries >= rule.retries → escalate or skip
|
|
390
|
+
│ │
|
|
391
|
+
│ ├─ if !rule.auto → emit 'reaction:triggered' (notify)
|
|
392
|
+
│ │
|
|
393
|
+
│ └─ executeReaction(event, rule, project)
|
|
394
|
+
│ │
|
|
395
|
+
│ ├─ action = 'fix'
|
|
396
|
+
│ │ └─ autoFix() → create AgentTask → lifecycle.spawnAgent()
|
|
397
|
+
│ │
|
|
398
|
+
│ ├─ action = 'notify'
|
|
399
|
+
│ │ └─ emit 'reaction:triggered' with action 'notify'
|
|
400
|
+
│ │
|
|
401
|
+
│ ├─ action = 'escalate'
|
|
402
|
+
│ │ └─ emit 'reaction:triggered' with action 'escalate'
|
|
403
|
+
│ │
|
|
404
|
+
│ └─ action = 'skip'
|
|
405
|
+
│ └─ no-op
|
|
406
|
+
│
|
|
407
|
+
└─ Increment retry count
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### 7.3 Retry Tracking
|
|
411
|
+
|
|
412
|
+
Retries are tracked per `issueId:eventType` key in a `Map<string, number>`. Once the configured retry limit is reached, further events of that type for the same issue are escalated or skipped.
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## 8. Dashboard
|
|
417
|
+
|
|
418
|
+
### 8.1 REST API
|
|
419
|
+
|
|
420
|
+
| Method | Endpoint | Description |
|
|
421
|
+
|--------|----------|-------------|
|
|
422
|
+
| `GET` | `/api/status` | Lifecycle status, project count, active sessions, uptime |
|
|
423
|
+
| `GET` | `/api/projects` | All configured projects with provider routing |
|
|
424
|
+
| `GET` | `/api/sessions` | Active agent sessions, worktrees, runtimes |
|
|
425
|
+
| `GET` | `/api/health` | Health check with memory usage and timestamp |
|
|
426
|
+
| `GET` | `/` or `/dashboard` | Single-page HTML dashboard |
|
|
427
|
+
|
|
428
|
+
All JSON endpoints return CORS headers (`Access-Control-Allow-Origin: *`).
|
|
429
|
+
|
|
430
|
+
### 8.2 Frontend
|
|
431
|
+
|
|
432
|
+
The dashboard is served as an inline HTML string from `DashboardServer.serveDashboardHTML()`. It features:
|
|
433
|
+
|
|
434
|
+
- **Dark theme** (GitHub-inspired `#0d1117` background)
|
|
435
|
+
- **Stat cards**: project count, active agents, system status, uptime
|
|
436
|
+
- **Projects table**: name, repository, primary agent, fallback agents
|
|
437
|
+
- **Sessions table**: session ID, task, provider, status, start time
|
|
438
|
+
- **Auto-refresh**: polls all 3 JSON endpoints every 5 seconds via `setInterval`
|
|
439
|
+
- **No build step** — single HTML file with embedded `<style>` and `<script>`
|
|
440
|
+
|
|
441
|
+
### 8.3 Architecture
|
|
442
|
+
|
|
443
|
+
```
|
|
444
|
+
DashboardServer
|
|
445
|
+
│
|
|
446
|
+
├─ Owns: http.Server, DashboardState, routes[]
|
|
447
|
+
│
|
|
448
|
+
├─ Subscribes to lifecycle.eventBus
|
|
449
|
+
│ ├─ 'session:created' → DashboardState.addSession()
|
|
450
|
+
│ └─ 'session:completed' → DashboardState.removeSession()
|
|
451
|
+
│
|
|
452
|
+
├─ DashboardState (in-memory)
|
|
453
|
+
│ ├─ SpeexorState { sessions, worktrees, runtimes, projects }
|
|
454
|
+
│ └─ listener pattern for reactive updates
|
|
455
|
+
│
|
|
456
|
+
└─ Routes:
|
|
457
|
+
├─ /api/status → lifecycle.getStatus(), lifecycle.listSessions()
|
|
458
|
+
├─ /api/projects → lifecycle.getConfig().projects
|
|
459
|
+
├─ /api/sessions → lifecycle.listSessions()
|
|
460
|
+
├─ /api/health → { status, timestamp, memory }
|
|
461
|
+
├─ / → serveDashboardHTML()
|
|
462
|
+
└─ 404 → { error: 'Not found' }
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## 9. Session Management
|
|
468
|
+
|
|
469
|
+
### 9.1 In-Memory (Runtime)
|
|
470
|
+
|
|
471
|
+
The `SpeexorLifecycle` maintains a `Map<string, AgentSession>` for active sessions. Sessions are created by `spawnAgent()` and removed by `stopSession()`.
|
|
472
|
+
|
|
473
|
+
### 9.2 Persistent Store
|
|
474
|
+
|
|
475
|
+
The `SessionStore` class manages a JSON file at `.speexor/state.json`:
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
interface SpeexorState {
|
|
479
|
+
sessions: AgentSession[]
|
|
480
|
+
worktrees: WorktreeSession[]
|
|
481
|
+
runtimes: RuntimeSession[]
|
|
482
|
+
projects: ProjectConfig[]
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
- Synchronous read/write (simplicity over performance)
|
|
487
|
+
- Auto-creates `.speexor/` directory on instantiation
|
|
488
|
+
- Methods: `addSession`, `updateSession`, `removeSession`, `addWorktree`, `removeWorktree`, `setProjects`, `clear`
|
|
489
|
+
|
|
490
|
+
### 9.3 Directory Layout
|
|
491
|
+
|
|
492
|
+
```
|
|
493
|
+
.speexor/
|
|
494
|
+
├── state.json # Persistent session state
|
|
495
|
+
├── worktrees/ # Git worktree targets
|
|
496
|
+
│ └── <task-id>/ # Isolated working directory per agent
|
|
497
|
+
├── logs/ # Runtime session logs
|
|
498
|
+
│ └── <session-id>.log # stdio capture (ProcessRuntime only)
|
|
499
|
+
└── config.yaml (optional, manual)
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
## 10. Extension Points
|
|
505
|
+
|
|
506
|
+
### 10.1 Adding a New Agent Provider
|
|
507
|
+
|
|
508
|
+
Implement `AgentPlugin`, register in `loadAllPlugins()`:
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
export class DeepSeekAgent implements AgentPlugin {
|
|
512
|
+
name = 'deepseek-agent'
|
|
513
|
+
version = '0.1.0'
|
|
514
|
+
type = 'agent' as const
|
|
515
|
+
// implement spawn(), sendInput(), getStatus(), kill()
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
Add the provider string to the `AgentProvider` union type and the Zod schema enum in `config.ts`.
|
|
520
|
+
|
|
521
|
+
### 10.2 Adding a New Plugin Slot
|
|
522
|
+
|
|
523
|
+
1. Define the interface in `src/core/types.ts` extending `PluginModule`
|
|
524
|
+
2. Add the slot name to `PluginSlot` union type
|
|
525
|
+
3. Implement the plugin class
|
|
526
|
+
4. Register it in `loadAllPlugins()` — it will be auto-loaded by the lifecycle
|
|
527
|
+
|
|
528
|
+
### 10.3 Custom Runtime
|
|
529
|
+
|
|
530
|
+
Implement `RuntimePlugin` for Docker, SSH, or Kubernetes sessions. The lifecycle uses `getFirstPlugin<RuntimePlugin>('runtime')`, so the first registered runtime wins.
|
|
531
|
+
|
|
532
|
+
### 10.4 Custom Tracker/SCM
|
|
533
|
+
|
|
534
|
+
Implement `TrackerPlugin` for Jira, Linear, or GitLab issues. Implement `SCMPlugin` for GitLab or Bitbucket PRs. Both follow the same `onEvent(handler)` subscription pattern for event-driven reactions.
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
## Key Design Decisions
|
|
539
|
+
|
|
540
|
+
| Decision | Rationale |
|
|
541
|
+
|----------|-----------|
|
|
542
|
+
| EventBus over direct calls | Loose coupling — plugins and dashboard observe without knowing each other |
|
|
543
|
+
| `getFirstPlugin()` slot resolution | Single active adapter per slot per lifecycle; multiple registrations possible for fallback |
|
|
544
|
+
| YAML + Zod config | Strict validation with human-editable format |
|
|
545
|
+
| Synchronous JSON state file | Simplicity — avoids database dependency for session tracking |
|
|
546
|
+
| Inline HTML dashboard | Zero build step, zero dependencies for the UI |
|
|
547
|
+
| `gh` CLI dependency | Avoids GitHub API token management; delegates auth to `gh` |
|
|
548
|
+
| Reverse-order destroy | Dependencies destroyed before dependents (terminal → notifier → scm → tracker → workspace → runtime → agent) |
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Speexor will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] — 2026-06-30
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Core orchestration lifecycle (SpeexorLifecycle) with plugin registry and session management
|
|
12
|
+
- 7-plugin slot architecture with TypeScript interfaces
|
|
13
|
+
- 4 AI agent adapters: OpenCode, Claude Code, Aider, Codex
|
|
14
|
+
- 2 runtime backends: tmux (Unix) and Process (Windows)
|
|
15
|
+
- Git worktree workspace isolation for parallel agent tasks
|
|
16
|
+
- GitHub tracker plugin for reading issues via gh CLI
|
|
17
|
+
- GitHub SCM plugin for PR/CI operations via gh CLI
|
|
18
|
+
- Desktop notification plugin (macOS, Windows, Linux)
|
|
19
|
+
- CLI with 6 commands: start, agent spawn, list, stop, logs, config-help
|
|
20
|
+
- YAML configuration system with Zod validation
|
|
21
|
+
- Built-in HTTP dashboard with REST API and HTML frontend
|
|
22
|
+
- Reaction engine for automated CI/PR feedback loops
|
|
23
|
+
- JSON-file session store for state persistence
|
|
24
|
+
- Event bus system (EventEmitter3) for inter-module communication
|
|
25
|
+
- In-memory reactive dashboard state management
|
|
26
|
+
|
|
27
|
+
### Architecture
|
|
28
|
+
- PluginModule base interface with initialize/destroy lifecycle
|
|
29
|
+
- Provider routing with primary/fallback agent support
|
|
30
|
+
- Configurable reaction rules (ci-failed, changes-requested, approved-and-green)
|
|
31
|
+
- Graceful shutdown with SIGINT/SIGTERM handling
|
|
32
|
+
|
|
33
|
+
## [0.2.0] — Planned
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
- Recursive task decomposition (DAG-based multi-agent orchestration)
|
|
37
|
+
- Real-time WebSocket terminal streaming
|
|
38
|
+
- Cost/usage tracking per provider
|
|
39
|
+
|
|
40
|
+
## [0.3.0] — Planned
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
- Extension/marketplace system
|
|
44
|
+
- Plugin SDK (@speexor/sdk)
|
|
45
|
+
- Air-gapped mode support
|
|
46
|
+
|
|
47
|
+
## [0.4.0] — Planned
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
- Multi-host distributed agent execution
|
|
51
|
+
- Advanced observability (OpenTelemetry traces)
|
|
52
|
+
- Webhook integration for third-party tools
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
+
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
10
|
+
identity and orientation.
|
|
11
|
+
|
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
13
|
+
diverse, inclusive, and healthy community.
|
|
14
|
+
|
|
15
|
+
## Our Standards
|
|
16
|
+
|
|
17
|
+
Examples of behavior that contributes to a positive environment:
|
|
18
|
+
|
|
19
|
+
* Demonstrating empathy and kindness toward other people
|
|
20
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
21
|
+
* Giving and gracefully accepting constructive feedback
|
|
22
|
+
* Accepting responsibility and apologizing to those affected by our mistakes
|
|
23
|
+
* Focusing on what is best not just for us as individuals, but for the overall community
|
|
24
|
+
|
|
25
|
+
Examples of unacceptable behavior:
|
|
26
|
+
|
|
27
|
+
* The use of sexualized language or imagery, and sexual attention or advances
|
|
28
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
29
|
+
* Public or private harassment
|
|
30
|
+
* Publishing others' private information without explicit permission
|
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
|
32
|
+
|
|
33
|
+
## Enforcement Responsibilities
|
|
34
|
+
|
|
35
|
+
Project maintainers are responsible for clarifying and enforcing our standards of
|
|
36
|
+
acceptable behavior and will take appropriate and fair corrective action in
|
|
37
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
38
|
+
or harmful.
|
|
39
|
+
|
|
40
|
+
## Scope
|
|
41
|
+
|
|
42
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
|
43
|
+
an individual is officially representing the community in public spaces.
|
|
44
|
+
|
|
45
|
+
## Enforcement
|
|
46
|
+
|
|
47
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
48
|
+
reported to the project maintainers at opensource@superdevids.com.
|
|
49
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
|
50
|
+
|
|
51
|
+
## Enforcement Guidelines
|
|
52
|
+
|
|
53
|
+
Project maintainers will follow these Community Impact Guidelines:
|
|
54
|
+
|
|
55
|
+
### 1. Correction
|
|
56
|
+
**Community Impact:** Use of inappropriate language or other behavior deemed
|
|
57
|
+
unprofessional or unwelcome.
|
|
58
|
+
**Consequence:** A private, written warning, providing clarity around the nature
|
|
59
|
+
of the violation and an explanation of why the behavior was inappropriate.
|
|
60
|
+
|
|
61
|
+
### 2. Warning
|
|
62
|
+
**Community Impact:** A violation through a single incident or series of actions.
|
|
63
|
+
**Consequence:** A warning with consequences for continued behavior. No
|
|
64
|
+
interaction with the people involved for a specified period of time.
|
|
65
|
+
|
|
66
|
+
### 3. Temporary Ban
|
|
67
|
+
**Community Impact:** A serious violation of community standards.
|
|
68
|
+
**Consequence:** A temporary ban from any sort of interaction or public
|
|
69
|
+
communication with the community for a specified period of time.
|
|
70
|
+
|
|
71
|
+
### 4. Permanent Ban
|
|
72
|
+
**Community Impact:** Demonstrating a pattern of violation of community standards.
|
|
73
|
+
**Consequence:** A permanent ban from any sort of public interaction within the
|
|
74
|
+
community.
|
|
75
|
+
|
|
76
|
+
## Attribution
|
|
77
|
+
|
|
78
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
79
|
+
version 2.1, available at
|
|
80
|
+
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
81
|
+
|
|
82
|
+
[homepage]: https://www.contributor-covenant.org
|
|
83
|
+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|