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
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createDashboardServer } from '../chunk-SXALZEOJ.js';
|
|
3
|
+
import { generateDefaultConfig, loadConfig, SpeexorLifecycle } from '../chunk-2F66BZYJ.js';
|
|
4
|
+
import { loadAllPlugins } from '../chunk-B7WLHC4W.js';
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
7
|
+
import { dirname, join } from 'path';
|
|
8
|
+
import { stringify } from 'yaml';
|
|
9
|
+
import Debug from 'debug';
|
|
10
|
+
import ora from 'ora';
|
|
11
|
+
import chalk4 from 'chalk';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
|
|
14
|
+
var debug = Debug("speexor:start");
|
|
15
|
+
async function startCommand(repo, options = {}) {
|
|
16
|
+
const cwd = process.cwd();
|
|
17
|
+
const port = parseInt(options.port ?? "3000", 10);
|
|
18
|
+
if (repo) {
|
|
19
|
+
const spinner = ora("Initializing Speexor project...").start();
|
|
20
|
+
const config = generateDefaultConfig(repo, options.name);
|
|
21
|
+
const configPath = join(cwd, "speexor.config.yaml");
|
|
22
|
+
if (existsSync(configPath)) {
|
|
23
|
+
spinner.warn("speexor.config.yaml already exists \u2014 using existing config");
|
|
24
|
+
} else {
|
|
25
|
+
const yaml = stringify(config, { indent: 2 });
|
|
26
|
+
writeFileSync(configPath, yaml, "utf-8");
|
|
27
|
+
spinner.succeed(`Created speexor.config.yaml at ${configPath}`);
|
|
28
|
+
}
|
|
29
|
+
const worktreesDir = join(cwd, ".speexor", "worktrees");
|
|
30
|
+
if (!existsSync(worktreesDir)) {
|
|
31
|
+
mkdirSync(worktreesDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
const logsDir = join(cwd, ".speexor", "logs");
|
|
34
|
+
if (!existsSync(logsDir)) {
|
|
35
|
+
mkdirSync(logsDir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
spinner.succeed("Project initialized");
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const config = loadConfig(cwd);
|
|
41
|
+
debug(`Loaded config with ${config.projects.length} project(s)`);
|
|
42
|
+
const lifecycle = new SpeexorLifecycle(config);
|
|
43
|
+
await lifecycle.initialize();
|
|
44
|
+
const spinner = ora("Loading plugins...").start();
|
|
45
|
+
const plugins = loadAllPlugins();
|
|
46
|
+
for (const plugin of plugins) {
|
|
47
|
+
lifecycle.registerPlugin(plugin);
|
|
48
|
+
}
|
|
49
|
+
spinner.succeed(`Loaded ${plugins.length} plugin(s)`);
|
|
50
|
+
if (options.dashboard !== false) {
|
|
51
|
+
const server = createDashboardServer(lifecycle, port);
|
|
52
|
+
server.start();
|
|
53
|
+
console.log(`
|
|
54
|
+
\u2728 Speexor Dashboard: http://localhost:${port}`);
|
|
55
|
+
console.log(` \u{1F4CB} ${config.projects.length} project(s) configured`);
|
|
56
|
+
console.log(` \u{1F916} Agents: ${plugins.filter((p) => p.type === "agent").length} adapter(s) loaded`);
|
|
57
|
+
console.log("\n Press Ctrl+C to stop\n");
|
|
58
|
+
const shutdown = async () => {
|
|
59
|
+
console.log("\nShutting down...");
|
|
60
|
+
await lifecycle.destroy();
|
|
61
|
+
server.stop();
|
|
62
|
+
process.exit(0);
|
|
63
|
+
};
|
|
64
|
+
process.on("SIGINT", shutdown);
|
|
65
|
+
process.on("SIGTERM", shutdown);
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error instanceof Error) {
|
|
69
|
+
console.error(`
|
|
70
|
+
\u2716 Error: ${error.message}
|
|
71
|
+
`);
|
|
72
|
+
if (!repo) {
|
|
73
|
+
console.log(" Tip: Run `speexor start <repo-url>` first to initialize a project.\n");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function listCommand() {
|
|
80
|
+
try {
|
|
81
|
+
const config = loadConfig();
|
|
82
|
+
const lifecycle = new SpeexorLifecycle(config);
|
|
83
|
+
await lifecycle.initialize();
|
|
84
|
+
const plugins = loadAllPlugins();
|
|
85
|
+
for (const plugin of plugins) {
|
|
86
|
+
lifecycle.registerPlugin(plugin);
|
|
87
|
+
}
|
|
88
|
+
console.log("\n \u{1F4CB} Speexor \u2014 Active Projects & Agents\n");
|
|
89
|
+
for (const project of config.projects) {
|
|
90
|
+
const statusColor = project.provider.primary === "opencode" ? chalk4.blue : chalk4.magenta;
|
|
91
|
+
console.log(` ${chalk4.bold(project.name)}`);
|
|
92
|
+
console.log(` Repository: ${project.repository}`);
|
|
93
|
+
console.log(` Primary Agent: ${statusColor(project.provider.primary)}`);
|
|
94
|
+
if (project.provider.fallback?.length) {
|
|
95
|
+
console.log(` Fallback: ${project.provider.fallback.join(", ")}`);
|
|
96
|
+
}
|
|
97
|
+
console.log();
|
|
98
|
+
}
|
|
99
|
+
const sessions = lifecycle.listSessions();
|
|
100
|
+
if (sessions.length === 0) {
|
|
101
|
+
console.log(` ${chalk4.dim("No active agent sessions. Use `speexor agent spawn` to start one.")}
|
|
102
|
+
`);
|
|
103
|
+
} else {
|
|
104
|
+
for (const session of sessions) {
|
|
105
|
+
console.log(` ${chalk4.cyan(session.id)} \u2014 ${session.status}`);
|
|
106
|
+
}
|
|
107
|
+
console.log();
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (error instanceof Error) {
|
|
111
|
+
console.error(` ${chalk4.red("\u2716")} ${error.message}
|
|
112
|
+
`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function stopCommand(sessionId) {
|
|
117
|
+
try {
|
|
118
|
+
const config = loadConfig();
|
|
119
|
+
const lifecycle = new SpeexorLifecycle(config);
|
|
120
|
+
await lifecycle.initialize();
|
|
121
|
+
const plugins = loadAllPlugins();
|
|
122
|
+
for (const plugin of plugins) {
|
|
123
|
+
lifecycle.registerPlugin(plugin);
|
|
124
|
+
}
|
|
125
|
+
await lifecycle.stopSession(sessionId);
|
|
126
|
+
console.log(`
|
|
127
|
+
${chalk4.green("\u2713")} Session ${chalk4.bold(sessionId)} stopped
|
|
128
|
+
`);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (error instanceof Error) {
|
|
131
|
+
console.error(`
|
|
132
|
+
${chalk4.red("\u2716")} ${error.message}
|
|
133
|
+
`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function logsCommand(sessionId, options) {
|
|
138
|
+
const numLines = parseInt(options.lines ?? "50", 10);
|
|
139
|
+
const logPath = join(process.cwd(), ".speexor", "logs", `${sessionId}.log`);
|
|
140
|
+
if (!existsSync(logPath)) {
|
|
141
|
+
console.error(`
|
|
142
|
+
${chalk4.red("\u2716")} No logs found for session: ${sessionId}
|
|
143
|
+
`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const content = readFileSync(logPath, "utf-8");
|
|
148
|
+
const lines = content.split("\n").filter(Boolean);
|
|
149
|
+
const tail = lines.slice(-numLines);
|
|
150
|
+
console.log(`
|
|
151
|
+
\u{1F4CB} Logs for session ${chalk4.bold(sessionId)} (last ${numLines} lines):
|
|
152
|
+
`);
|
|
153
|
+
for (const line of tail) {
|
|
154
|
+
console.log(` ${line}`);
|
|
155
|
+
}
|
|
156
|
+
console.log();
|
|
157
|
+
if (options.follow) {
|
|
158
|
+
console.log(chalk4.dim(" (Follow mode \u2014 press Ctrl+C to stop)\n"));
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (error instanceof Error) {
|
|
162
|
+
console.error(`
|
|
163
|
+
${chalk4.red("\u2716")} ${error.message}
|
|
164
|
+
`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
var CONFIG_HELP = `
|
|
169
|
+
${chalk4.bold("speexor.config.yaml Schema Reference")}
|
|
170
|
+
|
|
171
|
+
${chalk4.cyan("version")}
|
|
172
|
+
${chalk4.dim('string, must be "1"')}
|
|
173
|
+
|
|
174
|
+
${chalk4.cyan("projects")}
|
|
175
|
+
${chalk4.dim("array of project configurations")}
|
|
176
|
+
|
|
177
|
+
${chalk4.cyan("projects[].name")}
|
|
178
|
+
${chalk4.dim("string \u2014 Project display name")}
|
|
179
|
+
|
|
180
|
+
${chalk4.cyan("projects[].repository")}
|
|
181
|
+
${chalk4.dim("string \u2014 Git repository URL or local path")}
|
|
182
|
+
|
|
183
|
+
${chalk4.cyan("projects[].path")}
|
|
184
|
+
${chalk4.dim("string (optional) \u2014 Local path override")}
|
|
185
|
+
|
|
186
|
+
${chalk4.cyan("projects[].branch")}
|
|
187
|
+
${chalk4.dim("string (optional) \u2014 Default base branch (default: main)")}
|
|
188
|
+
|
|
189
|
+
${chalk4.cyan("projects[].provider")}
|
|
190
|
+
${chalk4.dim("Provider routing configuration")}
|
|
191
|
+
|
|
192
|
+
${chalk4.cyan("projects[].provider.primary")}
|
|
193
|
+
${chalk4.dim("string \u2014 Primary agent: opencode, claude-code, aider, codex")}
|
|
194
|
+
|
|
195
|
+
${chalk4.cyan("projects[].provider.fallback")}
|
|
196
|
+
${chalk4.dim("string[] (optional) \u2014 Fallback agents in priority order")}
|
|
197
|
+
|
|
198
|
+
${chalk4.cyan("projects[].provider.concurrentLimit")}
|
|
199
|
+
${chalk4.dim("number (optional) \u2014 Max parallel agents for this project (default: 5)")}
|
|
200
|
+
|
|
201
|
+
${chalk4.cyan("projects[].provider.costLimit")}
|
|
202
|
+
${chalk4.dim("number (optional) \u2014 Cost limit in cents per session")}
|
|
203
|
+
|
|
204
|
+
${chalk4.cyan("projects[].reactions")}
|
|
205
|
+
${chalk4.dim("Reaction rules for CI/PR events")}
|
|
206
|
+
|
|
207
|
+
${chalk4.cyan("projects[].reactions.ci-failed")}
|
|
208
|
+
${chalk4.cyan("projects[].reactions.changes-requested")}
|
|
209
|
+
${chalk4.cyan("projects[].reactions.approved-and-green")}
|
|
210
|
+
${chalk4.dim("Each reaction has:")}
|
|
211
|
+
${chalk4.dim(" auto: boolean \u2014 Auto-trigger reaction")}
|
|
212
|
+
${chalk4.dim(' action: "fix" | "notify" | "escalate" | "skip"')}
|
|
213
|
+
${chalk4.dim(" retries: number \u2014 Max retry attempts (0-10)")}
|
|
214
|
+
${chalk4.dim(" escalateAfter: number \u2014 Minutes before escalation")}
|
|
215
|
+
|
|
216
|
+
${chalk4.bold("Example:")}
|
|
217
|
+
|
|
218
|
+
${chalk4.green(`
|
|
219
|
+
version: "1"
|
|
220
|
+
projects:
|
|
221
|
+
- name: brainclash
|
|
222
|
+
repository: https://github.com/user/brainclash
|
|
223
|
+
provider:
|
|
224
|
+
primary: opencode
|
|
225
|
+
fallback: [deepseek, claude-code]
|
|
226
|
+
concurrentLimit: 3
|
|
227
|
+
reactions:
|
|
228
|
+
ci-failed:
|
|
229
|
+
auto: true
|
|
230
|
+
action: fix
|
|
231
|
+
retries: 3
|
|
232
|
+
escalateAfter: 30
|
|
233
|
+
changes-requested:
|
|
234
|
+
auto: true
|
|
235
|
+
action: fix
|
|
236
|
+
retries: 2
|
|
237
|
+
escalateAfter: 60
|
|
238
|
+
approved-and-green:
|
|
239
|
+
auto: false
|
|
240
|
+
action: notify
|
|
241
|
+
retries: 0
|
|
242
|
+
escalateAfter: 0
|
|
243
|
+
`)}
|
|
244
|
+
|
|
245
|
+
${chalk4.bold("Commands:")}
|
|
246
|
+
${chalk4.cyan("speexor start [repo-url|path]")}
|
|
247
|
+
${chalk4.dim("Initialize project + start dashboard")}
|
|
248
|
+
${chalk4.cyan("speexor agent spawn --task <id>")}
|
|
249
|
+
${chalk4.dim("Spawn agent for a task")}
|
|
250
|
+
${chalk4.cyan("speexor list")}
|
|
251
|
+
${chalk4.dim("List all projects and active agents")}
|
|
252
|
+
${chalk4.cyan("speexor stop <session-id>")}
|
|
253
|
+
${chalk4.dim("Stop an agent session")}
|
|
254
|
+
${chalk4.cyan("speexor logs <session-id>")}
|
|
255
|
+
${chalk4.dim("View agent session logs")}
|
|
256
|
+
`;
|
|
257
|
+
function configHelpCommand() {
|
|
258
|
+
console.log(CONFIG_HELP);
|
|
259
|
+
}
|
|
260
|
+
var __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
261
|
+
function getVersion() {
|
|
262
|
+
try {
|
|
263
|
+
const pkgPath = join(__dirname$1, "..", "..", "package.json");
|
|
264
|
+
if (existsSync(pkgPath)) {
|
|
265
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
266
|
+
return pkg.version ?? "0.1.0";
|
|
267
|
+
}
|
|
268
|
+
} catch {
|
|
269
|
+
}
|
|
270
|
+
return "0.1.0";
|
|
271
|
+
}
|
|
272
|
+
var program = new Command();
|
|
273
|
+
program.name("speexor").description("Agent Orchestrator for multi-AI coding agent orchestration (Speexor)").version(getVersion());
|
|
274
|
+
program.command("start [repo]").description("Initialize a project and start the dashboard").option("-p, --port <port>", "Dashboard port", "3000").option("-n, --name <name>", "Project name").option("--no-dashboard", "Skip starting dashboard").action(startCommand);
|
|
275
|
+
program.command("agent").description("Manage agents").addCommand(
|
|
276
|
+
new Command("spawn").description("Spawn a new agent for a task").requiredOption("-t, --task <id>", "Task ID").option("-a, --agent <type>", "Agent type (opencode, claude-code, aider, codex)", "opencode").action(async (opts) => {
|
|
277
|
+
const { agentSpawnCommand } = await import('../agent-5D3BVWNK.js');
|
|
278
|
+
await agentSpawnCommand(opts);
|
|
279
|
+
})
|
|
280
|
+
);
|
|
281
|
+
program.command("list").description("List all projects and active agent statuses").action(listCommand);
|
|
282
|
+
program.command("stop <session-id>").description("Stop an agent session safely").action(stopCommand);
|
|
283
|
+
program.command("logs <session-id>").description("Tail logs for an agent session").option("-f, --follow", "Follow log output").option("-n, --lines <count>", "Number of lines", "50").action(logsCommand);
|
|
284
|
+
program.command("config-help").description("Show full config schema reference").action(configHelpCommand);
|
|
285
|
+
program.parse(process.argv);
|
|
286
|
+
//# sourceMappingURL=index.js.map
|
|
287
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/start.ts","../../src/cli/list.ts","../../src/cli/stop.ts","../../src/cli/logs.ts","../../src/cli/config-help.ts","../../src/cli/index.ts"],"names":["chalk","join","existsSync","__dirname","readFileSync"],"mappings":"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","file":"index.js","sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport { stringify } from 'yaml'\nimport { loadConfig, generateDefaultConfig, validateConfig } from '../core/config.js'\nimport { SpeexorLifecycle } from '../core/lifecycle.js'\nimport { loadAllPlugins } from '../plugins/index.js'\nimport { createDashboardServer } from '../dashboard/server.js'\nimport Debug from 'debug'\nimport ora from 'ora'\n\nconst debug = Debug('speexor:start')\n\ninterface StartOptions {\n port?: string\n name?: string\n dashboard?: boolean\n}\n\nexport async function startCommand(repo?: string, options: StartOptions = {}) {\n const cwd = process.cwd()\n const port = parseInt(options.port ?? '3000', 10)\n\n // If repo provided, init new project\n if (repo) {\n const spinner = ora('Initializing Speexor project...').start()\n\n const config = generateDefaultConfig(repo, options.name)\n const configPath = join(cwd, 'speexor.config.yaml')\n\n if (existsSync(configPath)) {\n spinner.warn('speexor.config.yaml already exists — using existing config')\n } else {\n const yaml = stringify(config, { indent: 2 })\n writeFileSync(configPath, yaml, 'utf-8')\n spinner.succeed(`Created speexor.config.yaml at ${configPath}`)\n }\n\n // Create worktrees directory\n const worktreesDir = join(cwd, '.speexor', 'worktrees')\n if (!existsSync(worktreesDir)) {\n mkdirSync(worktreesDir, { recursive: true })\n }\n\n const logsDir = join(cwd, '.speexor', 'logs')\n if (!existsSync(logsDir)) {\n mkdirSync(logsDir, { recursive: true })\n }\n\n spinner.succeed('Project initialized')\n }\n\n // Load config\n try {\n const config = loadConfig(cwd)\n debug(`Loaded config with ${config.projects.length} project(s)`)\n\n // Initialize lifecycle\n const lifecycle = new SpeexorLifecycle(config)\n await lifecycle.initialize()\n\n // Load plugins dynamically\n const spinner = ora('Loading plugins...').start()\n const plugins = loadAllPlugins()\n for (const plugin of plugins) {\n lifecycle.registerPlugin(plugin)\n }\n spinner.succeed(`Loaded ${plugins.length} plugin(s)`)\n\n // Start dashboard if enabled\n if (options.dashboard !== false) {\n const server = createDashboardServer(lifecycle, port)\n server.start()\n console.log(`\\n ✨ Speexor Dashboard: http://localhost:${port}`)\n console.log(` 📋 ${config.projects.length} project(s) configured`)\n console.log(` 🤖 Agents: ${plugins.filter((p) => p.type === 'agent').length} adapter(s) loaded`)\n console.log('\\n Press Ctrl+C to stop\\n')\n\n // Graceful shutdown\n const shutdown = async () => {\n console.log('\\nShutting down...')\n await lifecycle.destroy()\n server.stop()\n process.exit(0)\n }\n\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n }\n } catch (error) {\n if (error instanceof Error) {\n console.error(`\\n ✖ Error: ${error.message}\\n`)\n if (!repo) {\n console.log(' Tip: Run `speexor start <repo-url>` first to initialize a project.\\n')\n }\n }\n process.exit(1)\n }\n}\n","import { loadConfig } from '../core/config.js'\nimport { SpeexorLifecycle } from '../core/lifecycle.js'\nimport { loadAllPlugins } from '../plugins/index.js'\nimport chalk from 'chalk'\n\nexport async function listCommand() {\n try {\n const config = loadConfig()\n const lifecycle = new SpeexorLifecycle(config)\n await lifecycle.initialize()\n\n const plugins = loadAllPlugins()\n for (const plugin of plugins) {\n lifecycle.registerPlugin(plugin)\n }\n\n console.log('\\n 📋 Speexor — Active Projects & Agents\\n')\n\n for (const project of config.projects) {\n const statusColor = project.provider.primary === 'opencode' ? chalk.blue : chalk.magenta\n console.log(` ${chalk.bold(project.name)}`)\n console.log(` Repository: ${project.repository}`)\n console.log(` Primary Agent: ${statusColor(project.provider.primary)}`)\n if (project.provider.fallback?.length) {\n console.log(` Fallback: ${project.provider.fallback.join(', ')}`)\n }\n console.log()\n }\n\n const sessions = lifecycle.listSessions()\n if (sessions.length === 0) {\n console.log(` ${chalk.dim('No active agent sessions. Use `speexor agent spawn` to start one.')}\\n`)\n } else {\n for (const session of sessions) {\n console.log(` ${chalk.cyan(session.id)} — ${session.status}`)\n }\n console.log()\n }\n } catch (error) {\n if (error instanceof Error) {\n console.error(` ${chalk.red('✖')} ${error.message}\\n`)\n }\n }\n}\n","import { loadConfig } from '../core/config.js'\nimport { SpeexorLifecycle } from '../core/lifecycle.js'\nimport { loadAllPlugins } from '../plugins/index.js'\nimport chalk from 'chalk'\n\nexport async function stopCommand(sessionId: string) {\n try {\n const config = loadConfig()\n const lifecycle = new SpeexorLifecycle(config)\n await lifecycle.initialize()\n\n const plugins = loadAllPlugins()\n for (const plugin of plugins) {\n lifecycle.registerPlugin(plugin)\n }\n\n await lifecycle.stopSession(sessionId)\n console.log(`\\n ${chalk.green('✓')} Session ${chalk.bold(sessionId)} stopped\\n`)\n } catch (error) {\n if (error instanceof Error) {\n console.error(`\\n ${chalk.red('✖')} ${error.message}\\n`)\n }\n }\n}\n","import { loadConfig } from '../core/config.js'\nimport { SpeexorLifecycle } from '../core/lifecycle.js'\nimport { loadAllPlugins } from '../plugins/index.js'\nimport { readFileSync, existsSync } from 'node:fs'\nimport { join } from 'node:path'\nimport chalk from 'chalk'\n\ninterface LogsOptions {\n follow?: boolean\n lines?: string\n}\n\nexport async function logsCommand(sessionId: string, options: LogsOptions) {\n const numLines = parseInt(options.lines ?? '50', 10)\n const logPath = join(process.cwd(), '.speexor', 'logs', `${sessionId}.log`)\n\n if (!existsSync(logPath)) {\n console.error(`\\n ${chalk.red('✖')} No logs found for session: ${sessionId}\\n`)\n return\n }\n\n try {\n const content = readFileSync(logPath, 'utf-8')\n const lines = content.split('\\n').filter(Boolean)\n const tail = lines.slice(-numLines)\n\n console.log(`\\n 📋 Logs for session ${chalk.bold(sessionId)} (last ${numLines} lines):\\n`)\n for (const line of tail) {\n console.log(` ${line}`)\n }\n console.log()\n\n if (options.follow) {\n console.log(chalk.dim(' (Follow mode — press Ctrl+C to stop)\\n'))\n // In follow mode, we'd tail the file. For now, just show note.\n }\n } catch (error) {\n if (error instanceof Error) {\n console.error(`\\n ${chalk.red('✖')} ${error.message}\\n`)\n }\n }\n}\n","import chalk from 'chalk'\n\nconst CONFIG_HELP = `\n${chalk.bold('speexor.config.yaml Schema Reference')}\n\n${chalk.cyan('version')}\n ${chalk.dim('string, must be \"1\"')}\n\n${chalk.cyan('projects')}\n ${chalk.dim('array of project configurations')}\n\n ${chalk.cyan('projects[].name')}\n ${chalk.dim('string — Project display name')}\n \n ${chalk.cyan('projects[].repository')}\n ${chalk.dim('string — Git repository URL or local path')}\n \n ${chalk.cyan('projects[].path')}\n ${chalk.dim('string (optional) — Local path override')}\n \n ${chalk.cyan('projects[].branch')}\n ${chalk.dim('string (optional) — Default base branch (default: main)')}\n\n ${chalk.cyan('projects[].provider')}\n ${chalk.dim('Provider routing configuration')}\n \n ${chalk.cyan('projects[].provider.primary')}\n ${chalk.dim('string — Primary agent: opencode, claude-code, aider, codex')}\n \n ${chalk.cyan('projects[].provider.fallback')}\n ${chalk.dim('string[] (optional) — Fallback agents in priority order')}\n \n ${chalk.cyan('projects[].provider.concurrentLimit')}\n ${chalk.dim('number (optional) — Max parallel agents for this project (default: 5)')}\n \n ${chalk.cyan('projects[].provider.costLimit')}\n ${chalk.dim('number (optional) — Cost limit in cents per session')}\n\n ${chalk.cyan('projects[].reactions')}\n ${chalk.dim('Reaction rules for CI/PR events')}\n \n ${chalk.cyan('projects[].reactions.ci-failed')}\n ${chalk.cyan('projects[].reactions.changes-requested')}\n ${chalk.cyan('projects[].reactions.approved-and-green')}\n ${chalk.dim('Each reaction has:')}\n ${chalk.dim(' auto: boolean — Auto-trigger reaction')}\n ${chalk.dim(' action: \"fix\" | \"notify\" | \"escalate\" | \"skip\"')}\n ${chalk.dim(' retries: number — Max retry attempts (0-10)')}\n ${chalk.dim(' escalateAfter: number — Minutes before escalation')}\n\n${chalk.bold('Example:')}\n\n${chalk.green(`\nversion: \"1\"\nprojects:\n - name: brainclash\n repository: https://github.com/user/brainclash\n provider:\n primary: opencode\n fallback: [deepseek, claude-code]\n concurrentLimit: 3\n reactions:\n ci-failed:\n auto: true\n action: fix\n retries: 3\n escalateAfter: 30\n changes-requested:\n auto: true\n action: fix\n retries: 2\n escalateAfter: 60\n approved-and-green:\n auto: false\n action: notify\n retries: 0\n escalateAfter: 0\n`)}\n\n${chalk.bold('Commands:')}\n ${chalk.cyan('speexor start [repo-url|path]')}\n ${chalk.dim('Initialize project + start dashboard')}\n ${chalk.cyan('speexor agent spawn --task <id>')}\n ${chalk.dim('Spawn agent for a task')}\n ${chalk.cyan('speexor list')}\n ${chalk.dim('List all projects and active agents')}\n ${chalk.cyan('speexor stop <session-id>')}\n ${chalk.dim('Stop an agent session')}\n ${chalk.cyan('speexor logs <session-id>')}\n ${chalk.dim('View agent session logs')}\n`\n\nexport function configHelpCommand() {\n console.log(CONFIG_HELP)\n}\n","#!/usr/bin/env node\nimport { Command } from 'commander'\nimport { startCommand } from './start.js'\nimport { listCommand } from './list.js'\nimport { stopCommand } from './stop.js'\nimport { logsCommand } from './logs.js'\nimport { configHelpCommand } from './config-help.js'\nimport { readFileSync, existsSync } from 'node:fs'\nimport { join, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\nfunction getVersion(): string {\n try {\n const pkgPath = join(__dirname, '..', '..', 'package.json')\n if (existsSync(pkgPath)) {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))\n return pkg.version ?? '0.1.0'\n }\n } catch {}\n return '0.1.0'\n}\n\nconst program = new Command()\n\nprogram.name('speexor').description('Agent Orchestrator for multi-AI coding agent orchestration (Speexor)').version(getVersion())\n\nprogram\n .command('start [repo]')\n .description('Initialize a project and start the dashboard')\n .option('-p, --port <port>', 'Dashboard port', '3000')\n .option('-n, --name <name>', 'Project name')\n .option('--no-dashboard', 'Skip starting dashboard')\n .action(startCommand)\n\nprogram\n .command('agent')\n .description('Manage agents')\n .addCommand(\n new Command('spawn')\n .description('Spawn a new agent for a task')\n .requiredOption('-t, --task <id>', 'Task ID')\n .option('-a, --agent <type>', 'Agent type (opencode, claude-code, aider, codex)', 'opencode')\n .action(async (opts) => {\n const { agentSpawnCommand } = await import('./agent.js')\n await agentSpawnCommand(opts)\n }),\n )\n\nprogram.command('list').description('List all projects and active agent statuses').action(listCommand)\n\nprogram.command('stop <session-id>').description('Stop an agent session safely').action(stopCommand)\n\nprogram\n .command('logs <session-id>')\n .description('Tail logs for an agent session')\n .option('-f, --follow', 'Follow log output')\n .option('-n, --lines <count>', 'Number of lines', '50')\n .action(logsCommand)\n\nprogram.command('config-help').description('Show full config schema reference').action(configHelpCommand)\n\nprogram.parse(process.argv)\n"]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { E as EventBus, l as ReactionConfig, s as SpeexorConfig, P as PluginModule, j as PluginSlot, e as AgentTask, A as AgentSession, r as SessionStatus } from '../types-0q_okI2g.js';
|
|
2
|
+
export { b as AgentPlugin, c as AgentProvider, d as AgentStatus, C as CIRun, N as NotifierPlugin, f as PRComment, g as PRInfo, h as PRStatus, i as PluginContext, a as ProjectConfig, k as ProviderRouting, m as ReactionRule, n as RuntimePlugin, R as RuntimeSession, o as RuntimeType, p as SCMPlugin, q as SessionEventType, S as SpeexorState, t as TerminalPlugin, T as TrackerEvent, u as TrackerEventType, v as TrackerFilter, w as TrackerIssue, x as TrackerPlugin, y as WorkspacePlugin, W as WorktreeSession } from '../types-0q_okI2g.js';
|
|
3
|
+
|
|
4
|
+
declare function createEventBus(): EventBus;
|
|
5
|
+
|
|
6
|
+
declare const DEFAULT_REACTION_RULES: ReactionConfig;
|
|
7
|
+
declare function loadConfig(cwd?: string): SpeexorConfig;
|
|
8
|
+
declare function validateConfig(raw: unknown): SpeexorConfig;
|
|
9
|
+
declare function generateDefaultConfig(repoUrl: string, projectName?: string): SpeexorConfig;
|
|
10
|
+
|
|
11
|
+
declare class SpeexorLifecycle {
|
|
12
|
+
private config;
|
|
13
|
+
eventBus: EventBus;
|
|
14
|
+
private plugins;
|
|
15
|
+
private sessions;
|
|
16
|
+
private status;
|
|
17
|
+
constructor(config: SpeexorConfig);
|
|
18
|
+
initialize(): Promise<void>;
|
|
19
|
+
registerPlugin(plugin: PluginModule): void;
|
|
20
|
+
getPlugins<T extends PluginModule>(slot: PluginSlot): T[];
|
|
21
|
+
getFirstPlugin<T extends PluginModule>(slot: PluginSlot): T | undefined;
|
|
22
|
+
spawnAgent(task: AgentTask): Promise<AgentSession>;
|
|
23
|
+
stopSession(sessionId: string): Promise<void>;
|
|
24
|
+
getSession(sessionId: string): AgentSession | undefined;
|
|
25
|
+
listSessions(): AgentSession[];
|
|
26
|
+
destroy(): Promise<void>;
|
|
27
|
+
getConfig(): SpeexorConfig;
|
|
28
|
+
getStatus(): SessionStatus;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { AgentSession, AgentTask, DEFAULT_REACTION_RULES, EventBus, PluginModule, PluginSlot, ReactionConfig, SessionStatus, SpeexorConfig, SpeexorLifecycle, createEventBus, generateDefaultConfig, loadConfig, validateConfig };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { S as SpeexorState, a as ProjectConfig, A as AgentSession, W as WorktreeSession, R as RuntimeSession, T as TrackerEvent } from './types-0q_okI2g.js';
|
|
2
|
+
export { b as AgentPlugin, c as AgentProvider, d as AgentStatus, e as AgentTask, C as CIRun, E as EventBus, N as NotifierPlugin, f as PRComment, g as PRInfo, h as PRStatus, i as PluginContext, P as PluginModule, j as PluginSlot, k as ProviderRouting, l as ReactionConfig, m as ReactionRule, n as RuntimePlugin, o as RuntimeType, p as SCMPlugin, q as SessionEventType, r as SessionStatus, s as SpeexorConfig, t as TerminalPlugin, u as TrackerEventType, v as TrackerFilter, w as TrackerIssue, x as TrackerPlugin, y as WorkspacePlugin } from './types-0q_okI2g.js';
|
|
3
|
+
import { SpeexorLifecycle } from './core/index.js';
|
|
4
|
+
export { DEFAULT_REACTION_RULES, createEventBus, generateDefaultConfig, loadConfig, validateConfig } from './core/index.js';
|
|
5
|
+
export { loadAllPlugins, loadPluginByType } from './plugins/index.js';
|
|
6
|
+
|
|
7
|
+
declare class DashboardState {
|
|
8
|
+
private state;
|
|
9
|
+
private listeners;
|
|
10
|
+
getState(): SpeexorState;
|
|
11
|
+
setProjects(projects: ProjectConfig[]): void;
|
|
12
|
+
addSession(session: AgentSession): void;
|
|
13
|
+
updateSession(sessionId: string, updates: Partial<AgentSession>): void;
|
|
14
|
+
removeSession(sessionId: string): void;
|
|
15
|
+
addWorktree(worktree: WorktreeSession): void;
|
|
16
|
+
removeWorktree(sessionId: string): void;
|
|
17
|
+
addRuntime(runtime: RuntimeSession): void;
|
|
18
|
+
removeRuntime(sessionId: string): void;
|
|
19
|
+
onUpdate(listener: () => void): () => void;
|
|
20
|
+
private notify;
|
|
21
|
+
toJSON(): SpeexorState;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
declare class DashboardServer {
|
|
25
|
+
private server;
|
|
26
|
+
private lifecycle;
|
|
27
|
+
private state;
|
|
28
|
+
private port;
|
|
29
|
+
private routes;
|
|
30
|
+
constructor(lifecycle: SpeexorLifecycle, port?: number);
|
|
31
|
+
private setupRoutes;
|
|
32
|
+
private handleRequest;
|
|
33
|
+
private matchPath;
|
|
34
|
+
private sendJSON;
|
|
35
|
+
private handleStatus;
|
|
36
|
+
private handleProjects;
|
|
37
|
+
private handleSessions;
|
|
38
|
+
private handleHealth;
|
|
39
|
+
private serveDashboardHTML;
|
|
40
|
+
start(): void;
|
|
41
|
+
stop(): void;
|
|
42
|
+
}
|
|
43
|
+
declare function createDashboardServer(lifecycle: SpeexorLifecycle, port?: number): DashboardServer;
|
|
44
|
+
|
|
45
|
+
declare class ReactionEngine {
|
|
46
|
+
private lifecycle;
|
|
47
|
+
private handlers;
|
|
48
|
+
private retryCounts;
|
|
49
|
+
constructor(lifecycle: SpeexorLifecycle);
|
|
50
|
+
configure(projects: ProjectConfig[]): void;
|
|
51
|
+
processEvent(event: TrackerEvent): Promise<void>;
|
|
52
|
+
private executeReaction;
|
|
53
|
+
private autoFix;
|
|
54
|
+
private escalate;
|
|
55
|
+
getRetryCount(key: string): number;
|
|
56
|
+
resetRetryCount(key: string): void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
declare class SessionStore {
|
|
60
|
+
private statePath;
|
|
61
|
+
private state;
|
|
62
|
+
constructor(baseDir?: string);
|
|
63
|
+
private load;
|
|
64
|
+
private save;
|
|
65
|
+
getState(): SpeexorState;
|
|
66
|
+
addSession(session: AgentSession): void;
|
|
67
|
+
updateSession(sessionId: string, updates: Partial<AgentSession>): void;
|
|
68
|
+
removeSession(sessionId: string): void;
|
|
69
|
+
addWorktree(worktree: WorktreeSession): void;
|
|
70
|
+
removeWorktree(sessionId: string): void;
|
|
71
|
+
setProjects(projects: ProjectConfig[]): void;
|
|
72
|
+
clear(): void;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { AgentSession, DashboardServer, DashboardState, ProjectConfig, ReactionEngine, RuntimeSession, SessionStore, SpeexorLifecycle, SpeexorState, TrackerEvent, WorktreeSession, createDashboardServer };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import './chunk-5NA2TFPG.js';
|
|
2
|
+
export { DashboardServer, DashboardState, createDashboardServer } from './chunk-SXALZEOJ.js';
|
|
3
|
+
export { DEFAULT_REACTION_RULES, SpeexorLifecycle, createEventBus, generateDefaultConfig, loadConfig, validateConfig } from './chunk-2F66BZYJ.js';
|
|
4
|
+
export { loadAllPlugins, loadPluginByType } from './chunk-B7WLHC4W.js';
|
|
5
|
+
import Debug from 'debug';
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
|
|
9
|
+
var debug = Debug("speexor:reaction");
|
|
10
|
+
var ReactionEngine = class {
|
|
11
|
+
lifecycle;
|
|
12
|
+
handlers = [];
|
|
13
|
+
retryCounts = /* @__PURE__ */ new Map();
|
|
14
|
+
constructor(lifecycle) {
|
|
15
|
+
this.lifecycle = lifecycle;
|
|
16
|
+
lifecycle.eventBus.on("reaction:triggered", (data) => {
|
|
17
|
+
const { eventType, taskId } = data;
|
|
18
|
+
debug(`Reaction triggered: ${eventType} for task ${taskId}`);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
configure(projects) {
|
|
22
|
+
this.handlers = [];
|
|
23
|
+
for (const project of projects) {
|
|
24
|
+
if (!project.reactions) continue;
|
|
25
|
+
for (const [eventType, rule] of Object.entries(project.reactions)) {
|
|
26
|
+
if (!rule) continue;
|
|
27
|
+
this.handlers.push({
|
|
28
|
+
eventType,
|
|
29
|
+
rule,
|
|
30
|
+
project,
|
|
31
|
+
handle: async (event) => {
|
|
32
|
+
await this.executeReaction(event, rule, project);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
debug(`Configured ${this.handlers.length} reaction handler(s)`);
|
|
38
|
+
}
|
|
39
|
+
async processEvent(event) {
|
|
40
|
+
debug(`Processing event: ${event.type} for issue ${event.issueId}`);
|
|
41
|
+
const matchingHandlers = this.handlers.filter((h) => h.eventType === event.type);
|
|
42
|
+
for (const handler of matchingHandlers) {
|
|
43
|
+
const key = `${event.issueId}:${handler.eventType}`;
|
|
44
|
+
const retries = this.retryCounts.get(key) ?? 0;
|
|
45
|
+
if (retries >= handler.rule.retries) {
|
|
46
|
+
debug(`Max retries reached for ${key} (${retries}/${handler.rule.retries})`);
|
|
47
|
+
if (handler.rule.action === "escalate") {
|
|
48
|
+
this.escalate(event, handler.project);
|
|
49
|
+
}
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
await handler.handle(event);
|
|
54
|
+
this.retryCounts.set(key, retries + 1);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
debug(`Reaction failed for ${key}: ${error}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async executeReaction(event, rule, project) {
|
|
61
|
+
if (!rule.auto) {
|
|
62
|
+
debug(`Rule for ${event.type} is not auto \u2014 notifying instead`);
|
|
63
|
+
this.lifecycle.eventBus.emit("reaction:triggered", {
|
|
64
|
+
eventType: event.type,
|
|
65
|
+
taskId: event.issueId,
|
|
66
|
+
action: "notify"
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
switch (rule.action) {
|
|
71
|
+
case "fix":
|
|
72
|
+
await this.autoFix(event, project);
|
|
73
|
+
break;
|
|
74
|
+
case "notify":
|
|
75
|
+
this.lifecycle.eventBus.emit("reaction:triggered", {
|
|
76
|
+
eventType: event.type,
|
|
77
|
+
taskId: event.issueId,
|
|
78
|
+
action: "notify"
|
|
79
|
+
});
|
|
80
|
+
break;
|
|
81
|
+
case "escalate":
|
|
82
|
+
this.escalate(event, project);
|
|
83
|
+
break;
|
|
84
|
+
case "skip":
|
|
85
|
+
debug(`Skipping reaction for ${event.type}`);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async autoFix(event, project) {
|
|
90
|
+
const task = {
|
|
91
|
+
id: `fix-${event.issueId}-${Date.now()}`,
|
|
92
|
+
title: `Auto-fix: ${event.type} for issue ${event.issueId}`,
|
|
93
|
+
description: `Automated fix triggered by event: ${event.type}
|
|
94
|
+
Issue: ${event.issueId}
|
|
95
|
+
Data: ${JSON.stringify(event.data)}`,
|
|
96
|
+
repository: project.repository,
|
|
97
|
+
branch: `speexor/fix-${event.issueId}`,
|
|
98
|
+
provider: project.provider.primary
|
|
99
|
+
};
|
|
100
|
+
debug(`Auto-fix spawning agent for task ${task.id}`);
|
|
101
|
+
try {
|
|
102
|
+
await this.lifecycle.spawnAgent(task);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
debug(`Auto-fix spawn failed: ${error}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
escalate(event, _project) {
|
|
108
|
+
debug(`Escalating: ${event.type} for issue ${event.issueId}`);
|
|
109
|
+
this.lifecycle.eventBus.emit("reaction:triggered", {
|
|
110
|
+
eventType: event.type,
|
|
111
|
+
taskId: event.issueId,
|
|
112
|
+
action: "escalate",
|
|
113
|
+
message: `Max retries exceeded for ${event.type} on issue ${event.issueId}`
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
getRetryCount(key) {
|
|
117
|
+
return this.retryCounts.get(key) ?? 0;
|
|
118
|
+
}
|
|
119
|
+
resetRetryCount(key) {
|
|
120
|
+
this.retryCounts.delete(key);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
var debug2 = Debug("speexor:store");
|
|
124
|
+
var SessionStore = class {
|
|
125
|
+
statePath;
|
|
126
|
+
state;
|
|
127
|
+
constructor(baseDir) {
|
|
128
|
+
const dir = baseDir ?? join(process.cwd(), ".speexor");
|
|
129
|
+
if (!existsSync(dir)) {
|
|
130
|
+
mkdirSync(dir, { recursive: true });
|
|
131
|
+
}
|
|
132
|
+
this.statePath = join(dir, "state.json");
|
|
133
|
+
this.state = this.load();
|
|
134
|
+
}
|
|
135
|
+
load() {
|
|
136
|
+
try {
|
|
137
|
+
if (existsSync(this.statePath)) {
|
|
138
|
+
const raw = readFileSync(this.statePath, "utf-8");
|
|
139
|
+
const data = JSON.parse(raw);
|
|
140
|
+
return {
|
|
141
|
+
sessions: data.sessions ?? [],
|
|
142
|
+
worktrees: data.worktrees ?? [],
|
|
143
|
+
runtimes: data.runtimes ?? [],
|
|
144
|
+
projects: data.projects ?? []
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
} catch (error) {
|
|
148
|
+
debug2(`Failed to load state: ${error}`);
|
|
149
|
+
}
|
|
150
|
+
return { sessions: [], worktrees: [], runtimes: [], projects: [] };
|
|
151
|
+
}
|
|
152
|
+
save() {
|
|
153
|
+
try {
|
|
154
|
+
writeFileSync(this.statePath, JSON.stringify(this.state, null, 2), "utf-8");
|
|
155
|
+
} catch (error) {
|
|
156
|
+
debug2(`Failed to save state: ${error}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
getState() {
|
|
160
|
+
return { ...this.state };
|
|
161
|
+
}
|
|
162
|
+
addSession(session) {
|
|
163
|
+
this.state.sessions.push(session);
|
|
164
|
+
this.save();
|
|
165
|
+
}
|
|
166
|
+
updateSession(sessionId, updates) {
|
|
167
|
+
const idx = this.state.sessions.findIndex((s) => s.id === sessionId);
|
|
168
|
+
if (idx !== -1) {
|
|
169
|
+
const existing = this.state.sessions[idx];
|
|
170
|
+
if (existing) {
|
|
171
|
+
if (updates.id !== void 0) existing.id = updates.id;
|
|
172
|
+
if (updates.taskId !== void 0) existing.taskId = updates.taskId;
|
|
173
|
+
if (updates.provider !== void 0) existing.provider = updates.provider;
|
|
174
|
+
if (updates.status !== void 0) existing.status = updates.status;
|
|
175
|
+
if (updates.startedAt !== void 0) existing.startedAt = updates.startedAt;
|
|
176
|
+
if (updates.runtimeSessionId !== void 0) existing.runtimeSessionId = updates.runtimeSessionId;
|
|
177
|
+
this.save();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
removeSession(sessionId) {
|
|
182
|
+
this.state.sessions = this.state.sessions.filter((s) => s.id !== sessionId);
|
|
183
|
+
this.save();
|
|
184
|
+
}
|
|
185
|
+
addWorktree(worktree) {
|
|
186
|
+
this.state.worktrees.push(worktree);
|
|
187
|
+
this.save();
|
|
188
|
+
}
|
|
189
|
+
removeWorktree(sessionId) {
|
|
190
|
+
this.state.worktrees = this.state.worktrees.filter((w) => w.id !== sessionId);
|
|
191
|
+
this.save();
|
|
192
|
+
}
|
|
193
|
+
setProjects(projects) {
|
|
194
|
+
this.state.projects = projects;
|
|
195
|
+
this.save();
|
|
196
|
+
}
|
|
197
|
+
clear() {
|
|
198
|
+
this.state = { sessions: [], worktrees: [], runtimes: [], projects: [] };
|
|
199
|
+
this.save();
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export { ReactionEngine, SessionStore };
|
|
204
|
+
//# sourceMappingURL=index.js.map
|
|
205
|
+
//# sourceMappingURL=index.js.map
|