rax-flow 0.1.1 → 0.1.2

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.
@@ -0,0 +1,317 @@
1
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import * as readline from "node:readline/promises";
5
+ import { stdin as input, stdout as output } from "node:process";
6
+
7
+ import { CoreOrchestrator, WorkflowGraph } from "rax-flow-core";
8
+ import { buildOfficialAgentsRuntime } from "rax-flow-agents";
9
+ import { ClaudeAdapter, GenericRestAdapter, HostBridgeAdapter, OpenAIAdapter } from "rax-flow-providers";
10
+ import { WebSocketRelay } from "./ws-relay.js";
11
+ import { c } from "./styles.js";
12
+
13
+ interface RunOptions {
14
+ cwd: string;
15
+ prompt: string;
16
+ workflowPath?: string;
17
+ asJson?: boolean;
18
+ stream?: boolean;
19
+ maxParallel?: number;
20
+ noCachePersist?: boolean;
21
+ providerOverride?: string;
22
+ bridgeCommandOverride?: string;
23
+ ui?: boolean;
24
+ yolo?: boolean;
25
+ }
26
+
27
+
28
+ interface RaxConfig {
29
+ defaultProvider?: string;
30
+ strongProvider?: string;
31
+ providers?: {
32
+ host?: { model?: string; mode?: "auto" | "bridge-only" | "mock"; bridgeCommandEnv?: string; timeoutMs?: number };
33
+ openai?: { model?: string; apiKeyEnv?: string; baseUrl?: string };
34
+ claude?: { model?: string; apiKeyEnv?: string; baseUrl?: string };
35
+ rest?: { endpoint?: string; tokenEnv?: string; model?: string };
36
+ };
37
+ }
38
+
39
+ async function loadConfig(cwd: string): Promise<RaxConfig> {
40
+ const file = path.join(cwd, ".raxrc");
41
+ const raw = await readFile(file, "utf8");
42
+ return JSON.parse(raw) as RaxConfig;
43
+ }
44
+
45
+ async function loadWorkflow(cwd: string, workflowPath?: string): Promise<WorkflowGraph | undefined> {
46
+ const file = workflowPath
47
+ ? path.resolve(cwd, workflowPath)
48
+ : path.join(cwd, ".rax-flow", "workflows", "fullstack-feature.json");
49
+
50
+ try {
51
+ const raw = await readFile(file, "utf8");
52
+ return JSON.parse(raw) as WorkflowGraph;
53
+ } catch {
54
+ return undefined;
55
+ }
56
+ }
57
+
58
+ function createProviders(config: RaxConfig, bridgeCommandOverride?: string) {
59
+ const providers: Record<string, OpenAIAdapter | ClaudeAdapter | GenericRestAdapter | HostBridgeAdapter> = {};
60
+
61
+ const hostCfg = config.providers?.host;
62
+ const bridgeCommand = bridgeCommandOverride ?? process.env[hostCfg?.bridgeCommandEnv ?? "RAX_HOST_BRIDGE_COMMAND"];
63
+ providers.host = new HostBridgeAdapter({
64
+ model: hostCfg?.model ?? "host-managed",
65
+ mode: hostCfg?.mode ?? "auto",
66
+ command: bridgeCommand,
67
+ timeoutMs: hostCfg?.timeoutMs ?? 20000
68
+ });
69
+
70
+ const openaiKey = process.env[config.providers?.openai?.apiKeyEnv ?? "OPENAI_API_KEY"];
71
+ if (openaiKey) {
72
+ providers.openai = new OpenAIAdapter(openaiKey, config.providers?.openai?.baseUrl);
73
+ }
74
+
75
+ const claudeKey = process.env[config.providers?.claude?.apiKeyEnv ?? "ANTHROPIC_API_KEY"];
76
+ if (claudeKey) {
77
+ providers.claude = new ClaudeAdapter(claudeKey, config.providers?.claude?.baseUrl);
78
+ }
79
+
80
+ const restEndpoint = config.providers?.rest?.endpoint;
81
+ if (restEndpoint) {
82
+ const restToken = process.env[config.providers?.rest?.tokenEnv ?? "RAX_REST_TOKEN"];
83
+ providers.rest = new GenericRestAdapter(restEndpoint, restToken);
84
+ }
85
+
86
+ return providers;
87
+ }
88
+
89
+ function cachePath(cwd: string): string {
90
+ return path.join(cwd, ".rax-flow", "cache", "semantic-cache.json");
91
+ }
92
+
93
+ async function saveHistory(cwd: string, taskId: string, data: any) {
94
+ const dir = path.join(cwd, ".rax-flow", "history");
95
+ await mkdir(dir, { recursive: true });
96
+ const file = path.join(dir, `${taskId}.json`);
97
+ await writeFile(file, JSON.stringify(data, null, 2));
98
+ }
99
+
100
+ async function askApproval(nodeId: string, agent: string, data: any): Promise<{ approved: boolean; feedback?: string }> {
101
+ const rl = readline.createInterface({ input, output });
102
+ console.log(c.magenta("\n--- [HUMAN INTERVENTION REQUIRED] ---"));
103
+ console.log(c.cyan(`Step: ${nodeId} | Agent: ${agent}`));
104
+ console.log(c.white("Candidate Output Data:"));
105
+ console.log(JSON.stringify(data, null, 2));
106
+
107
+ const answer = await rl.question(c.yellow("\nApprove? [Y to accept, or type your feedback to reject/fix]: "));
108
+ rl.close();
109
+
110
+ if (answer.toLowerCase() === "y" || answer === "") {
111
+ return { approved: true };
112
+ } else {
113
+ return { approved: false, feedback: answer };
114
+ }
115
+ }
116
+
117
+
118
+ export async function runWorkflow(options: RunOptions): Promise<number> {
119
+ let config: RaxConfig;
120
+ try {
121
+ config = await loadConfig(options.cwd);
122
+ } catch {
123
+ console.error(c.red("Missing .raxrc. Run `rax-flow install` first."));
124
+ return 1;
125
+ }
126
+ const providers = createProviders(config, options.bridgeCommandOverride);
127
+ const selectedDefault = options.providerOverride ?? config.defaultProvider ?? "host";
128
+
129
+ // Pre-flight check: Ensure Zero-Error compliance for Bridge integration
130
+ if (selectedDefault === "host") {
131
+ const health = await providers.host.healthCheck();
132
+ if (!health) {
133
+ console.error(c.red(`[Error] Host Bridge connectivity failed.`));
134
+ console.error(c.yellow(`Check your .raxrc or set RAX_HOST_BRIDGE_COMMAND env var.`));
135
+ console.log("Tip: run `rax-flow doctor` for detailed diagnostics.");
136
+ return 1;
137
+ }
138
+ }
139
+
140
+ const selectedStrong = config.strongProvider ?? "host";
141
+
142
+
143
+ const agents = buildOfficialAgentsRuntime({
144
+ providers,
145
+ defaultProvider: selectedDefault,
146
+ strongProvider: selectedStrong,
147
+ providerModels: {
148
+ host: config.providers?.host?.model ?? "host-managed",
149
+ openai: config.providers?.openai?.model ?? "gpt-4.1-mini",
150
+ claude: config.providers?.claude?.model ?? "claude-3-5-sonnet-latest",
151
+ rest: config.providers?.rest?.model ?? "generic-llm"
152
+ }
153
+ });
154
+
155
+ const workflow = await loadWorkflow(options.cwd, options.workflowPath);
156
+ // Memory System: Quantum Semantic Graph Memory (QSGM)
157
+ const memoryDir = path.join(options.cwd, ".rax-flow", "memory");
158
+ const { LocalVectorStore, GraphMemory, MemoryManager } = await import("rax-flow-core");
159
+
160
+ const vectorStore = new LocalVectorStore(path.join(memoryDir, "vector-index.json"));
161
+ await vectorStore.load();
162
+
163
+ const graphMemory = new GraphMemory(path.join(memoryDir, "graph-memory.json"));
164
+ await graphMemory.load();
165
+
166
+ const memoryManager = new MemoryManager(vectorStore, graphMemory);
167
+
168
+ const orchestrator = new CoreOrchestrator(providers, agents, { memory: memoryManager }, { maxParallel: options.maxParallel ?? 4, cacheTtlMs: 1000 * 60 * 60 * 24 });
169
+
170
+ // Governance System: Enterprise security & compliance
171
+ const { GovernancePlugin, PIIPolicy } = await import("rax-flow-core");
172
+ const governancePlugin = new GovernancePlugin(
173
+ [new PIIPolicy()],
174
+ path.join(options.cwd, ".rax-flow", "audit"),
175
+ orchestrator.events
176
+ );
177
+ orchestrator.registerPlugin(governancePlugin);
178
+
179
+
180
+ // Register Memory Plugin if an embedding provider is available (OpenAI in this case)
181
+ if (providers.openai) {
182
+ const { LongTermMemoryPlugin } = await import("rax-flow-core");
183
+ orchestrator.registerPlugin(new LongTermMemoryPlugin(memoryManager, providers.openai as any));
184
+ }
185
+
186
+
187
+
188
+ let relay: WebSocketRelay | undefined;
189
+
190
+ if (options.ui) {
191
+ relay = new WebSocketRelay(3002);
192
+ relay.start(orchestrator.events);
193
+ }
194
+
195
+ const persistentCacheFile = cachePath(options.cwd);
196
+ if (!options.noCachePersist) {
197
+ await orchestrator.loadCache(persistentCacheFile);
198
+ }
199
+
200
+ const sessionEvents: any[] = [];
201
+ orchestrator.onEvent(e => sessionEvents.push(e));
202
+
203
+ // Connect WebSocket relay resolutions to orchestrator
204
+ orchestrator.onEvent((event: any) => {
205
+ if (event.type === "INTERNAL_RESOLVE_APPROVAL") {
206
+ orchestrator.resolveApproval(event.taskId, event.nodeId, event.approved, event.feedback);
207
+ }
208
+ });
209
+
210
+ if (options.stream) {
211
+ orchestrator.onEvent((event) => {
212
+ if (event.type === "graph_ready") {
213
+ console.log(c.magenta(c.bold(`\n--- [DAG FLOW] ---`)));
214
+ const edgeCount = event.workflow.nodes.reduce((acc: number, n: any) => acc + (n.dependsOn?.length || 0), 0);
215
+ console.log(c.gray(`Tasks: ${event.workflow.nodes.length} | Edges: ${edgeCount}`));
216
+ }
217
+
218
+ if (event.type === "node_start") {
219
+ const retryPrefix = event.retry > 0 ? c.yellow(` (Retry ${event.retry})`) : "";
220
+ console.log(`${c.blue("◆")} ${c.bold(event.nodeId)}${retryPrefix} ${c.gray(`[${event.agent}]`)}`);
221
+ }
222
+
223
+ if (event.type === "node_awaiting_approval") {
224
+ if (options.yolo) {
225
+ orchestrator.resolveApproval(event.taskId, event.nodeId, true);
226
+ } else {
227
+ (async () => {
228
+ const { approved, feedback } = await askApproval(event.nodeId, event.agent, event.data);
229
+ orchestrator.resolveApproval(event.taskId, event.nodeId, approved, feedback);
230
+ })();
231
+ }
232
+ }
233
+
234
+ if (event.type === "node_end") {
235
+ const confColor = event.confidence >= 0.8 ? c.green : (event.confidence >= 0.6 ? c.yellow : c.red);
236
+ const icon = event.success ? c.green("✔") : c.red("✘");
237
+ console.log(` ${icon} ${confColor(`conf: ${(event.confidence * 100).toFixed(0)}%`)} ${c.gray(`cost: $${(event.costUsd || 0).toFixed(5)}`)}`);
238
+ }
239
+
240
+ if (event.type === "node_error") {
241
+ console.log(` ${c.red("!")} ${c.red(event.message)}`);
242
+ }
243
+
244
+ if (event.type === "audit_record" && event.mutation) {
245
+ console.log(c.magenta(`\n🧬 [Genetic Mutation Generated]`));
246
+ console.log(c.magenta(` Reason: ${event.mutation.reason ?? "Optimization"}`));
247
+ }
248
+ });
249
+ }
250
+
251
+ const taskId = `task_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
252
+ const result = await orchestrator.run(
253
+ {
254
+ taskId,
255
+ userPrompt: options.prompt,
256
+ context: {
257
+ cli: true,
258
+ host: os.hostname()
259
+ }
260
+ },
261
+ workflow
262
+ );
263
+
264
+ if (relay) {
265
+ await new Promise((resolve) => setTimeout(resolve, 2000));
266
+ relay.stop();
267
+ }
268
+
269
+ if (!options.noCachePersist) {
270
+ await orchestrator.saveCache(persistentCacheFile);
271
+ }
272
+
273
+ // Save to history
274
+ await saveHistory(options.cwd, taskId, {
275
+ taskId,
276
+ timestamp: Date.now(),
277
+ prompt: options.prompt,
278
+ result: result.result,
279
+ metrics: result.metrics,
280
+ workflow: result.workflow,
281
+ events: sessionEvents
282
+ });
283
+
284
+ if (options.asJson) {
285
+ console.log(JSON.stringify(result, null, 2));
286
+ return 0;
287
+ }
288
+
289
+ // Final Summary Table
290
+ console.log(c.bold(c.cyan(`\n\n--- [RUN SUMMARY] ---`)));
291
+ const tableData = [
292
+ ["Task ID", taskId],
293
+ ["Main Agent", result.result.agent],
294
+ ["Status", result.result.success ? c.green("SUCCESS") : c.red("FAILED")],
295
+ ["Confidence", `${(result.result.confidence * 100).toFixed(1)}%`],
296
+ ["Retries", result.metrics.retries.toString()],
297
+ ["Escalations", result.metrics.escalations.toString()],
298
+ ["Latency", `${(result.metrics.totalLatencyMs / 1000).toFixed(2)}s`],
299
+ ["Total Cost", c.yellow(`$${(result.metrics.totalCostUsd || 0).toFixed(5)}`)]
300
+ ];
301
+
302
+ for (const [key, value] of tableData) {
303
+ console.log(`${c.bold(key.padEnd(15))} : ${value}`);
304
+ }
305
+
306
+ if (result.result.logs.length > 0) {
307
+ console.log(c.bold(`\nTrace Logs:`));
308
+ for (const line of result.result.logs.slice(-5)) {
309
+ console.log(c.gray(` • ${line}`));
310
+ }
311
+ if (result.result.logs.length > 5) {
312
+ console.log(c.gray(` ... and ${result.result.logs.length - 5} more lines.`));
313
+ }
314
+ }
315
+
316
+ return 0;
317
+ }
@@ -0,0 +1,12 @@
1
+ export const c = {
2
+ blue: (s: string) => `\u001b[34m${s}\u001b[0m`,
3
+ green: (s: string) => `\u001b[32m${s}\u001b[0m`,
4
+ yellow: (s: string) => `\u001b[33m${s}\u001b[0m`,
5
+ red: (s: string) => `\u001b[31m${s}\u001b[0m`,
6
+ magenta: (s: string) => `\u001b[35m${s}\u001b[0m`,
7
+ cyan: (s: string) => `\u001b[36m${s}\u001b[0m`,
8
+ white: (s: string) => `\u001b[37m${s}\u001b[0m`,
9
+ gray: (s: string) => `\u001b[90m${s}\u001b[0m`,
10
+ bold: (s: string) => `\u001b[1m${s}\u001b[22m`
11
+ };
12
+
@@ -0,0 +1,113 @@
1
+ import path from "node:path";
2
+ import { HostTarget } from "./host-init-templates.js";
3
+
4
+ export interface HostManifestModel {
5
+ version: number;
6
+ target: HostTarget;
7
+ title: string;
8
+ autoInjectMode: "native" | "scripted" | "manual";
9
+ bridgeCommand: string;
10
+ quickCommands: string[];
11
+ sessionEntryExamples: string[];
12
+ task: string | null;
13
+ files: {
14
+ bootstrapPrompt: string;
15
+ instructions: string;
16
+ bridgeAdapter: string;
17
+ bridgeRuntimeConfig: string;
18
+ bridgeSmokeTest: string;
19
+ vendorManifest: string;
20
+ };
21
+ }
22
+
23
+ function toRelative(baseDir: string, file: string): string {
24
+ const rel = path.relative(baseDir, file);
25
+ return rel.length > 0 ? rel : path.basename(file);
26
+ }
27
+
28
+ function buildClaudeManifest(baseDir: string, manifest: HostManifestModel) {
29
+ return {
30
+ schema: "rax.vendor.claude-code.v1",
31
+ session: {
32
+ initMessageFile: toRelative(baseDir, manifest.files.bootstrapPrompt),
33
+ mode: manifest.autoInjectMode,
34
+ bridgeCommand: manifest.bridgeCommand
35
+ },
36
+ orchestrator: {
37
+ tool: "rax-flow",
38
+ quickCommands: manifest.quickCommands
39
+ }
40
+ };
41
+ }
42
+
43
+ function buildCodexManifest(baseDir: string, manifest: HostManifestModel) {
44
+ return {
45
+ schema: "rax.vendor.codex.v1",
46
+ chat: {
47
+ bootstrapFile: toRelative(baseDir, manifest.files.bootstrapPrompt),
48
+ strategy: manifest.autoInjectMode
49
+ },
50
+ runtime: {
51
+ bridgeCommand: manifest.bridgeCommand,
52
+ taskContext: manifest.task
53
+ }
54
+ };
55
+ }
56
+
57
+ function buildOpenCodeManifest(baseDir: string, manifest: HostManifestModel) {
58
+ return {
59
+ schema: "rax.vendor.opencode.v1",
60
+ integration: {
61
+ startupPromptFile: toRelative(baseDir, manifest.files.bootstrapPrompt),
62
+ launchMode: manifest.autoInjectMode
63
+ },
64
+ orchestration: {
65
+ quickCommands: manifest.quickCommands,
66
+ bridgeCommand: manifest.bridgeCommand
67
+ }
68
+ };
69
+ }
70
+
71
+ function buildKiloManifest(baseDir: string, manifest: HostManifestModel) {
72
+ return {
73
+ schema: "rax.vendor.kilo.v1",
74
+ initializer: {
75
+ promptFile: toRelative(baseDir, manifest.files.bootstrapPrompt),
76
+ injectMode: manifest.autoInjectMode
77
+ },
78
+ runtime: {
79
+ bridgeCommand: manifest.bridgeCommand,
80
+ commandHints: manifest.sessionEntryExamples
81
+ }
82
+ };
83
+ }
84
+
85
+ function buildGenericManifest(baseDir: string, manifest: HostManifestModel) {
86
+ return {
87
+ schema: "rax.vendor.generic.v1",
88
+ bootstrap: {
89
+ file: toRelative(baseDir, manifest.files.bootstrapPrompt)
90
+ },
91
+ bridge: {
92
+ command: manifest.bridgeCommand,
93
+ mode: manifest.autoInjectMode
94
+ },
95
+ commands: manifest.quickCommands
96
+ };
97
+ }
98
+
99
+ export function generateVendorManifest(target: HostTarget, baseDir: string, manifest: HostManifestModel): Record<string, unknown> {
100
+ if (target === "claude-code") return buildClaudeManifest(baseDir, manifest);
101
+ if (target === "codex") return buildCodexManifest(baseDir, manifest);
102
+ if (target === "opencode") return buildOpenCodeManifest(baseDir, manifest);
103
+ if (target === "kilo") return buildKiloManifest(baseDir, manifest);
104
+ return buildGenericManifest(baseDir, manifest);
105
+ }
106
+
107
+ export function vendorManifestFilename(target: HostTarget): string {
108
+ if (target === "claude-code") return "vendor-claude-code.json";
109
+ if (target === "codex") return "vendor-codex.json";
110
+ if (target === "opencode") return "vendor-opencode.json";
111
+ if (target === "kilo") return "vendor-kilo.json";
112
+ return "vendor-generic.json";
113
+ }
@@ -0,0 +1,156 @@
1
+ import { WebSocketServer, WebSocket } from "ws";
2
+ import { RuntimeEventBus } from "rax-flow-core";
3
+ import { readdir, readFile, mkdir, writeFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+
6
+ export class WebSocketRelay {
7
+ private wss: WebSocketServer | null = null;
8
+ private clients = new Set<WebSocket>();
9
+ private bus: RuntimeEventBus | null = null;
10
+
11
+ constructor(private port: number = 3002) { }
12
+
13
+ public start(eventBus: RuntimeEventBus): void {
14
+ this.bus = eventBus;
15
+ try {
16
+ this.wss = new WebSocketServer({ port: this.port });
17
+ console.log(`\n[UI] 📡 WebSocket relay started on ws://localhost:${this.port}`);
18
+
19
+ this.wss.on("connection", (ws) => {
20
+ this.clients.add(ws);
21
+ ws.send(JSON.stringify({ type: "HELLO", timestamp: Date.now() }));
22
+
23
+ ws.on("close", () => {
24
+ this.clients.delete(ws);
25
+ });
26
+
27
+ ws.on("message", async (data) => {
28
+ try {
29
+ const message = JSON.parse(data.toString());
30
+ const historyDir = path.join(process.cwd(), ".rax-flow", "history");
31
+
32
+ if (message.type === "GET_HISTORY") {
33
+ try {
34
+ const files = await readdir(historyDir);
35
+ const runs = await Promise.all(
36
+ files.filter(f => f.endsWith(".json")).map(async f => {
37
+ const content = await readFile(path.join(historyDir, f), "utf8");
38
+ const parsed = JSON.parse(content);
39
+ return {
40
+ taskId: parsed.taskId,
41
+ timestamp: parsed.timestamp,
42
+ prompt: parsed.prompt,
43
+ success: parsed.result.success,
44
+ costUsd: parsed.metrics.totalCostUsd
45
+ };
46
+ })
47
+ );
48
+ ws.send(JSON.stringify({ type: "HISTORY_LIST", runs: runs.sort((a, b) => b.timestamp - a.timestamp) }));
49
+ } catch {
50
+ ws.send(JSON.stringify({ type: "HISTORY_LIST", runs: [] }));
51
+ }
52
+ }
53
+
54
+ else if (message.type === "LOAD_RUN") {
55
+ try {
56
+ const file = path.join(historyDir, `${message.taskId}.json`);
57
+ const content = await readFile(file, "utf8");
58
+ ws.send(JSON.stringify({ type: "RUN_DATA", data: JSON.parse(content) }));
59
+ } catch (err) {
60
+ ws.send(JSON.stringify({ type: "ERROR", message: "Run not found" }));
61
+ }
62
+ }
63
+
64
+ else if (message.type === "LIST_WORKFLOWS") {
65
+ const workflowsDir = path.join(process.cwd(), ".rax-flow", "workflows");
66
+ try {
67
+ const files = await readdir(workflowsDir);
68
+ const list = files.filter(f => f.endsWith(".json")).map(f => ({
69
+ id: f.replace(".json", ""),
70
+ filename: f
71
+ }));
72
+ ws.send(JSON.stringify({ type: "WORKFLOW_LIST", workflows: list }));
73
+ } catch {
74
+ ws.send(JSON.stringify({ type: "WORKFLOW_LIST", workflows: [] }));
75
+ }
76
+ }
77
+
78
+ else if (message.type === "RESOLVE_APPROVAL") {
79
+ this.bus?.emit({
80
+ type: "INTERNAL_RESOLVE_APPROVAL",
81
+ taskId: message.taskId,
82
+ nodeId: message.nodeId,
83
+ approved: message.approved,
84
+ feedback: message.feedback
85
+ } as any);
86
+ }
87
+
88
+ else if (message.type === "SAVE_WORKFLOW") {
89
+ try {
90
+ const workflowsDir = path.join(process.cwd(), ".rax-flow", "workflows");
91
+ await mkdir(workflowsDir, { recursive: true });
92
+ const file = path.join(workflowsDir, `${message.id}.json`);
93
+ const data = {
94
+ id: message.id,
95
+ nodes: message.nodes,
96
+ edges: message.edges
97
+ };
98
+ await writeFile(file, JSON.stringify(data, null, 2));
99
+ ws.send(JSON.stringify({ type: "INFO", message: `Workflow ${message.id} saved.` }));
100
+ this.broadcast({ type: "REFRESH_WORKFLOWS" });
101
+ } catch (err) {
102
+ ws.send(JSON.stringify({ type: "ERROR", message: "Failed to save workflow" }));
103
+ }
104
+ }
105
+
106
+ else if (message.type === "DELETE_WORKFLOW") {
107
+ try {
108
+ const url = path.join(process.cwd(), ".rax-flow", "workflows", `${message.id}.json`);
109
+ const { unlink } = await import("node:fs/promises");
110
+ await unlink(url);
111
+ this.broadcast({ type: "REFRESH_WORKFLOWS" });
112
+ } catch (err) {
113
+ ws.send(JSON.stringify({ type: "ERROR", message: "Failed to delete workflow" }));
114
+ }
115
+ }
116
+ } catch (e) {
117
+ console.error("[WS] Message parse error:", e);
118
+ }
119
+ });
120
+
121
+ ws.on("error", () => {
122
+ this.clients.delete(ws);
123
+ });
124
+ });
125
+
126
+ this.wss.on("error", (err) => {
127
+ console.error(`[UI] WebSocket server error: ${err.message}`);
128
+ });
129
+
130
+ eventBus.onEvent((event) => {
131
+ this.broadcast(event);
132
+ });
133
+ } catch (err) {
134
+ console.error(`[UI] Failed to start WebSocket server: ${err}`);
135
+ }
136
+ }
137
+
138
+ private broadcast(event: unknown): void {
139
+ if (!this.wss) return;
140
+ const payload = JSON.stringify(event);
141
+ for (const client of this.clients) {
142
+ if (client.readyState === WebSocket.OPEN) {
143
+ client.send(payload);
144
+ }
145
+ }
146
+ }
147
+
148
+ public stop(): void {
149
+ if (this.wss) {
150
+ this.wss.close();
151
+ this.wss = null;
152
+ this.clients.clear();
153
+ console.log("[UI] 📡 WebSocket relay stopped.");
154
+ }
155
+ }
156
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist",
6
+ "composite": true,
7
+ "declaration": true,
8
+ "declarationMap": true
9
+ },
10
+ "include": ["src/**/*.ts"],
11
+ "references": [{ "path": "../core" }, { "path": "../agents" }, { "path": "../providers" }]
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rax-flow",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -14,9 +14,9 @@
14
14
  "typecheck": "tsc -p tsconfig.json --noEmit"
15
15
  },
16
16
  "dependencies": {
17
- "rax-flow-agents": "0.1.1",
18
- "rax-flow-core": "0.1.1",
19
- "rax-flow-providers": "0.1.1",
17
+ "rax-flow-agents": "0.1.2",
18
+ "rax-flow-core": "0.1.2",
19
+ "rax-flow-providers": "0.1.2",
20
20
  "ws": "^8.19.0"
21
21
  },
22
22
  "publishConfig": {
@@ -25,4 +25,4 @@
25
25
  "devDependencies": {
26
26
  "@types/ws": "^8.18.1"
27
27
  }
28
- }
28
+ }
Binary file