@ebowwa/stack 0.1.4 → 0.3.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.
Files changed (4) hide show
  1. package/README.md +26 -11
  2. package/dist/index.js +64318 -4384
  3. package/package.json +9 -9
  4. package/src/index.ts +122 -206
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ebowwa/stack",
3
- "version": "0.1.4",
4
- "description": "Full-stack daemon orchestrator combining unified-router (cross-channel) and node-agent (Ralph orchestration)",
3
+ "version": "0.3.0",
4
+ "description": "Cross-channel AI stack with node-agent integration (SSH + Telegram + Ralph/Git)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -20,12 +20,14 @@
20
20
  },
21
21
  "keywords": [
22
22
  "stack",
23
- "orchestrator",
24
- "daemon",
25
- "ralph",
23
+ "channel",
26
24
  "telegram",
27
25
  "ssh",
28
- "ai"
26
+ "ai",
27
+ "cross-channel",
28
+ "memory",
29
+ "ralph",
30
+ "node-agent"
29
31
  ],
30
32
  "author": "Ebowwa Labs <labs@ebowwa.com>",
31
33
  "license": "MIT",
@@ -41,9 +43,7 @@
41
43
  "@ebowwa/channel-telegram": "^1.14.2",
42
44
  "@ebowwa/channel-types": "^0.2.1",
43
45
  "@ebowwa/codespaces-types": "^1.6.1",
44
- "@ebowwa/daemons": "^0.5.0",
45
- "@ebowwa/node-agent": "^0.6.4",
46
- "@ebowwa/rolling-keys": "^0.1.1"
46
+ "@ebowwa/node-agent": "^0.6.5"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/bun": "latest"
package/src/index.ts CHANGED
@@ -1,17 +1,19 @@
1
1
  #!/usr/bin/env bun
2
2
  /**
3
- * @ebowwa/stack - Full-Stack Daemon Orchestrator
3
+ * @ebowwa/stack - Cross-Channel AI Stack with Node Agent
4
4
  *
5
- * Combines:
5
+ * Features:
6
6
  * - Unified Router: Cross-channel communication (SSH + Telegram)
7
- * - Node Agent: Ralph loop orchestration, worktrees, monitoring
7
+ * - Cross-channel memory with permission controls
8
+ * - AI-powered message handling
9
+ * - Node Agent: Ralph loops, Git, Monitoring (imported but disabled for now)
8
10
  *
9
11
  * Architecture:
10
12
  * ┌──────────────────────────────────────────────────────────────┐
11
13
  * │ STACK │
12
14
  * │ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
13
- * │ │ Unified Router │ │ Node Agent │ │ HTTP API │ │
14
- * │ │ (chat/messaging)│ │ (Ralph loops) │ (:8911) │ │
15
+ * │ │ SSH Channel │ │Telegram Channel│ │ HTTP API │ │
16
+ * │ │ (optional) │ │ (optional) │ (optional) │ │
15
17
  * │ └───────┬────────┘ └───────┬────────┘ └───────┬────────┘ │
16
18
  * │ │ │ │ │
17
19
  * │ └───────────────────┴───────────────────┘ │
@@ -19,7 +21,15 @@
19
21
  * │ ┌─────────▼─────────┐ │
20
22
  * │ │ Shared Memory │ │
21
23
  * │ │ (Cross-Context) │ │
22
- * │ └───────────────────┘
24
+ * │ └─────────┬─────────┘
25
+ * │ │ │
26
+ * │ ┌───────────────────┴───────────────────┐ │
27
+ * │ │ │ │
28
+ * │ ┌───────▼───────┐ ┌──────────▼───────┐ │
29
+ * │ │ AI Brain │ │ Node Agent │ │
30
+ * │ │ (GLM) │ │ (Ralph/Git/...) │ │
31
+ * │ └───────────────┘ │ [DISABLED] │ │
32
+ * │ └──────────────────┘ │
23
33
  * └──────────────────────────────────────────────────────────────┘
24
34
  */
25
35
 
@@ -28,6 +38,15 @@ import type { ChannelConnector, ChannelMessage, ChannelResponse, ChannelId } fro
28
38
  import { GLMClient } from "@ebowwa/ai";
29
39
  import { ToolExecutor, BUILTIN_TOOLS } from "@ebowwa/ai/tools";
30
40
 
41
+ // Node Agent imports (services available but disabled)
42
+ import {
43
+ RalphService,
44
+ GitService,
45
+ ConsoleLoggerService,
46
+ initializeStateService,
47
+ getState,
48
+ } from "@ebowwa/node-agent/lib";
49
+
31
50
  // ============================================================
32
51
  // Types
33
52
  // ============================================================
@@ -43,17 +62,11 @@ export interface StackConfig {
43
62
  botToken?: string;
44
63
  allowedChats?: number[];
45
64
  };
46
- /** Enable HTTP API for Ralph management */
65
+ /** Enable HTTP API */
47
66
  api?: {
48
67
  port?: number;
49
68
  host?: string;
50
69
  };
51
- /** Ralph loop configuration */
52
- ralph?: {
53
- worktreesDir: string;
54
- repoUrl: string;
55
- baseBranch?: string;
56
- };
57
70
  /** AI configuration */
58
71
  ai?: {
59
72
  model?: string;
@@ -65,6 +78,8 @@ export interface StackConfig {
65
78
  name: string;
66
79
  hostname?: string;
67
80
  };
81
+ /** Enable Node Agent features (Ralph, Git, etc.) */
82
+ enableNodeAgent?: boolean;
68
83
  }
69
84
 
70
85
  export interface StackState {
@@ -77,16 +92,9 @@ export interface StackState {
77
92
  enabled: boolean;
78
93
  port?: number;
79
94
  };
80
- ralphLoops: Map<string, RalphLoopInfo>;
81
- }
82
-
83
- interface RalphLoopInfo {
84
- id: string;
85
- worktree: string;
86
- prompt: string;
87
- status: "running" | "paused" | "completed" | "error";
88
- iterations: number;
89
- started: Date;
95
+ nodeAgent: {
96
+ enabled: boolean;
97
+ };
90
98
  }
91
99
 
92
100
  // ============================================================
@@ -103,25 +111,27 @@ export class Stack {
103
111
  private channels: Map<string, ChannelConnector> = new Map();
104
112
  private abortController: AbortController | null = null;
105
113
 
114
+ // Node Agent services (initialized but may not be used)
115
+ private ralphService: RalphService | null = null;
116
+ private gitService: GitService | null = null;
117
+ private consoleLogger: ConsoleLoggerService | null = null;
118
+
106
119
  constructor(config: StackConfig) {
107
- // Only set defaults for non-channel config
108
120
  this.config = {
109
121
  ...config,
110
122
  api: config.api ?? { port: 8911 },
111
- ralph: config.ralph ?? { worktreesDir: "/root/worktrees", repoUrl: "" },
112
123
  ai: config.ai ?? { model: "GLM-4.7", temperature: 0.7, maxTokens: 4096 },
113
124
  node: config.node ?? { name: "stack", hostname: "localhost" },
114
- // ssh and telegram remain undefined if not provided
115
125
  };
116
126
 
117
127
  this.state = {
118
128
  started: new Date(),
119
129
  channels: { ssh: false, telegram: false },
120
130
  api: { enabled: !!this.config.api, port: this.config.api?.port },
121
- ralphLoops: new Map(),
131
+ nodeAgent: { enabled: false },
122
132
  };
123
133
 
124
- // Build memory channels dynamically based on enabled channels
134
+ // Build memory channels dynamically
125
135
  const memoryChannels: Record<string, { memoryFile: string; maxMessages: number }> = {};
126
136
  const permissions: Record<string, { canRead: string[] }> = {};
127
137
  const enabledChannels: string[] = [];
@@ -134,35 +144,32 @@ export class Stack {
134
144
  memoryChannels.telegram = { memoryFile: "/root/.telegram-memory.json", maxMessages: 50 };
135
145
  enabledChannels.push("telegram");
136
146
  }
137
- if (this.config.api) {
138
- memoryChannels.api = { memoryFile: "/root/.api-memory.json", maxMessages: 100 };
139
- enabledChannels.push("api");
140
- }
141
147
 
142
- // Set up cross-channel permissions for enabled channels
143
148
  for (const channel of enabledChannels) {
144
149
  permissions[channel] = { canRead: enabledChannels.filter(c => c !== channel) };
145
150
  }
146
151
 
147
- // Initialize shared memory (empty if no channels)
148
152
  this.memory = createPermissionMemory({
149
153
  channels: memoryChannels,
150
154
  permissions,
151
155
  });
152
156
 
153
- // Initialize router
154
157
  this.router = createChannelRouter({
155
158
  announcement: {
156
159
  serverName: this.config.node.name,
157
160
  hostname: this.config.node.hostname,
158
161
  packageName: "@ebowwa/stack",
159
- version: "0.1.1",
162
+ version: "0.3.0",
160
163
  },
161
164
  });
162
165
 
163
- // Initialize AI
164
166
  this.client = new GLMClient();
165
167
  this.executor = new ToolExecutor(this.client, [...BUILTIN_TOOLS]);
168
+
169
+ // Initialize Node Agent services (but don't enable features yet)
170
+ this.ralphService = new RalphService();
171
+ this.gitService = new GitService();
172
+ this.consoleLogger = new ConsoleLoggerService();
166
173
  }
167
174
 
168
175
  // ============================================================
@@ -202,18 +209,42 @@ export class Stack {
202
209
  console.log("[Stack] Telegram registered");
203
210
  }
204
211
 
212
+ // ============================================================
213
+ // Node Agent Initialization
214
+ // ============================================================
215
+
216
+ private async initializeNodeAgent(): Promise<void> {
217
+ console.log("[Stack] Initializing Node Agent services...");
218
+
219
+ // Initialize state service
220
+ try {
221
+ await initializeStateService();
222
+ console.log("[Stack] State service initialized");
223
+ } catch (error) {
224
+ console.warn("[Stack] State service initialization failed (non-critical):", error);
225
+ }
226
+
227
+ // Ralph and Git services are instantiated but features are DISABLED
228
+ // TODO: Enable when ready
229
+ // this.ralphService?.startMonitoring();
230
+ // this.gitService?.initialize();
231
+
232
+ this.state.nodeAgent.enabled = true;
233
+ console.log("[Stack] Node Agent services ready (Ralph/Git DISABLED)");
234
+ }
235
+
205
236
  // ============================================================
206
237
  // Message Handler
207
238
  // ============================================================
208
239
 
209
240
  private async handleMessage(routed: { message: ChannelMessage; channelId: ChannelId }): Promise<ChannelResponse> {
210
241
  const { message, channelId } = routed;
211
- const channel = channelId.platform as "ssh" | "telegram" | "api";
242
+ const channel = channelId.platform as "ssh" | "telegram";
212
243
  const text = message.text;
213
244
 
214
245
  console.log(`[${channel}] ${text.slice(0, 50)}...`);
215
246
 
216
- // Start typing indicator for Telegram
247
+ // Typing indicator for Telegram
217
248
  const telegramChannel = this.channels.get("telegram") as { startTypingIndicator?: (chatId: string) => void; stopTypingIndicator?: (chatId: string) => void } | undefined;
218
249
  const chatId = channelId.metadata?.chatId as string | undefined;
219
250
  if (channel === "telegram" && telegramChannel?.startTypingIndicator && chatId) {
@@ -226,7 +257,7 @@ export class Stack {
226
257
  }
227
258
  };
228
259
 
229
- // Check for memory commands
260
+ // Memory commands
230
261
  const cmdResult = parseMemoryCommand(this.memory, channel, text);
231
262
  if (cmdResult.handled) {
232
263
  stopTyping();
@@ -236,20 +267,28 @@ export class Stack {
236
267
  };
237
268
  }
238
269
 
239
- // Check for Ralph commands
240
- const ralphResult = await this.handleRalphCommand(channel, text);
241
- if (ralphResult) {
270
+ // Status command
271
+ if (text.trim().toLowerCase() === "/status") {
242
272
  stopTyping();
243
273
  return {
244
- content: { text: ralphResult },
274
+ content: { text: this.getStatus() },
245
275
  replyTo: { messageId: message.messageId, channelId: message.channelId },
246
276
  };
247
277
  }
248
278
 
249
- // Add to shared memory
279
+ // Ralph commands (DISABLED for now)
280
+ // if (text.startsWith("/ralph")) {
281
+ // stopTyping();
282
+ // return {
283
+ // content: { text: await this.handleRalphCommand(text) },
284
+ // replyTo: { messageId: message.messageId, channelId: message.channelId },
285
+ // };
286
+ // }
287
+
288
+ // Add to memory
250
289
  this.memory.addMessage(channel, { role: "user", content: text });
251
290
 
252
- // Build messages with cross-channel context
291
+ // Build LLM messages
253
292
  const llmMessages = this.memory.buildLLMMessages(
254
293
  channel,
255
294
  this.buildSystemPrompt(),
@@ -284,100 +323,34 @@ export class Stack {
284
323
  }
285
324
 
286
325
  private buildSystemPrompt(): string {
287
- return `You are **${this.config.node.name}** — a 24/7 AI stack running on this node.
326
+ const channelList = Object.entries(this.state.channels)
327
+ .filter(([, v]) => v)
328
+ .map(([k]) => k)
329
+ .join(", ") || "none";
330
+
331
+ return `You are **${this.config.node.name}** — a 24/7 AI assistant.
288
332
 
289
- ## What You Manage
290
- - **Ralph Loops**: Autonomous AI agents running tasks
291
- - **Git Worktrees**: Isolated development environments
292
- - **Node Monitoring**: CPU, memory, disk usage
293
- - **Cross-Channel Memory**: Shared context between SSH, Telegram, and API
333
+ ## Capabilities
334
+ - Cross-channel memory (shared context between channels)
335
+ - Tool execution via MCP
336
+ - Node monitoring and management
294
337
 
295
338
  ## Commands
296
- - \`/ralph start <prompt>\` — Start a Ralph loop
297
- - \`/ralph list\` — List running loops
298
- - \`/ralph stop <id>\` — Stop a loop
299
339
  - \`/status\` — Node status
300
340
  - \`/memory <cmd>\` — Memory management (grant, revoke, list, clear)
301
341
 
302
- ## Stack Info
342
+ ## Info
303
343
  - Node: ${this.config.node.name}
304
- - Channels: ${Object.entries(this.state.channels).filter(([, v]) => v).map(([k]) => k).join(", ") || "none"}
344
+ - Channels: ${channelList}
345
+ - Node Agent: ${this.state.nodeAgent.enabled ? "ready (Ralph/Git disabled)" : "off"}
305
346
  - API: :${this.config.api?.port ?? 8911}`;
306
347
  }
307
348
 
308
- // ============================================================
309
- // Ralph Loop Management (delegates to node-agent)
310
- // ============================================================
311
-
312
- private async handleRalphCommand(channel: string, text: string): Promise<string | null> {
313
- const parts = text.trim().split(/\s+/);
314
- const cmd = parts[0].toLowerCase();
315
-
316
- if (cmd === "/ralph" || cmd === "/ralph") {
317
- const subCmd = parts[1]?.toLowerCase();
318
-
319
- switch (subCmd) {
320
- case "start": {
321
- const prompt = parts.slice(2).join(" ");
322
- if (!prompt) return "Usage: /ralph start <prompt>";
323
- return await this.startRalphLoop(prompt);
324
- }
325
- case "list":
326
- return this.listRalphLoops();
327
- case "stop": {
328
- const id = parts[2];
329
- if (!id) return "Usage: /ralph stop <id>";
330
- return await this.stopRalphLoop(id);
331
- }
332
- default:
333
- return "Ralph commands: start, list, stop";
334
- }
335
- }
336
-
337
- if (cmd === "/status") {
338
- return this.getStatus();
339
- }
340
-
341
- return null;
342
- }
343
-
344
- private async startRalphLoop(prompt: string): Promise<string> {
345
- // TODO: Delegate to @ebowwa/node-agent RalphService
346
- const id = `ralph-${Date.now()}`;
347
- this.state.ralphLoops.set(id, {
348
- id,
349
- worktree: `${this.config.ralph.worktreesDir}/${id}`,
350
- prompt,
351
- status: "running",
352
- iterations: 0,
353
- started: new Date(),
354
- });
355
- return `Started Ralph loop: ${id}\nPrompt: ${prompt.slice(0, 100)}...`;
356
- }
357
-
358
- private listRalphLoops(): string {
359
- if (this.state.ralphLoops.size === 0) {
360
- return "No Ralph loops running";
361
- }
362
- const lines = ["Ralph Loops:"];
363
- for (const [id, info] of this.state.ralphLoops) {
364
- lines.push(` ${id}: ${info.status} (${info.iterations} iters)`);
365
- }
366
- return lines.join("\n");
367
- }
368
-
369
- private async stopRalphLoop(id: string): Promise<string> {
370
- const info = this.state.ralphLoops.get(id);
371
- if (!info) return `Ralph loop not found: ${id}`;
372
- info.status = "paused";
373
- return `Stopped Ralph loop: ${id}`;
374
- }
375
-
376
349
  private getStatus(): string {
377
350
  const lines = [
378
351
  `**${this.config.node.name} Status**`,
379
352
  `Channels: SSH=${this.state.channels.ssh}, Telegram=${this.state.channels.telegram}`,
380
- `Ralph Loops: ${this.state.ralphLoops.size}`,
353
+ `Node Agent: ${this.state.nodeAgent.enabled ? "ready (Ralph/Git disabled)" : "off"}`,
381
354
  `Uptime: ${Math.floor((Date.now() - this.state.started.getTime()) / 1000)}s`,
382
355
  ];
383
356
  return lines.join("\n");
@@ -393,17 +366,16 @@ export class Stack {
393
366
  const port = this.config.api.port ?? 8911;
394
367
  const host = this.config.api.host ?? "0.0.0.0";
395
368
 
396
- const server = Bun.serve({
369
+ Bun.serve({
397
370
  port,
398
371
  host,
399
372
  fetch: async (req) => {
400
373
  const url = new URL(req.url);
401
374
  const path = url.pathname;
402
375
 
403
- // CORS headers
404
376
  const corsHeaders = {
405
377
  "Access-Control-Allow-Origin": "*",
406
- "Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
378
+ "Access-Control-Allow-Methods": "GET, OPTIONS",
407
379
  "Access-Control-Allow-Headers": "Content-Type",
408
380
  };
409
381
 
@@ -411,69 +383,24 @@ export class Stack {
411
383
  return new Response(null, { headers: corsHeaders });
412
384
  }
413
385
 
414
- try {
415
- // GET /api/status
416
- if (path === "/api/status" && req.method === "GET") {
417
- return Response.json(this.getStatusJSON(), { headers: corsHeaders });
418
- }
419
-
420
- // GET /api/ralph-loops
421
- if (path === "/api/ralph-loops" && req.method === "GET") {
422
- return Response.json(this.listRalphLoopsJSON(), { headers: corsHeaders });
423
- }
424
-
425
- // POST /api/ralph-loops
426
- if (path === "/api/ralph-loops" && req.method === "POST") {
427
- const body = await req.json();
428
- const prompt = body.prompt;
429
- if (!prompt) {
430
- return Response.json({ error: "Missing prompt" }, { status: 400, headers: corsHeaders });
431
- }
432
- const result = await this.startRalphLoop(prompt);
433
- return Response.json({ message: result }, { headers: corsHeaders });
434
- }
435
-
436
- // DELETE /api/ralph-loops/:id
437
- const match = path.match(/^\/api\/ralph-loops\/(.+)$/);
438
- if (match && req.method === "DELETE") {
439
- const result = await this.stopRalphLoop(match[1]);
440
- return Response.json({ message: result }, { headers: corsHeaders });
441
- }
442
-
443
- // GET /health
444
- if (path === "/health") {
445
- return Response.json({ status: "ok" }, { headers: corsHeaders });
446
- }
447
-
448
- return Response.json({ error: "Not found" }, { status: 404, headers: corsHeaders });
449
- } catch (error) {
450
- const errorMsg = error instanceof Error ? error.message : String(error);
451
- return Response.json({ error: errorMsg }, { status: 500, headers: corsHeaders });
386
+ if (path === "/api/status" && req.method === "GET") {
387
+ return Response.json({
388
+ node: this.config.node.name,
389
+ channels: this.state.channels,
390
+ nodeAgent: this.state.nodeAgent,
391
+ uptime: Math.floor((Date.now() - this.state.started.getTime()) / 1000),
392
+ }, { headers: corsHeaders });
452
393
  }
453
- },
454
- });
455
394
 
456
- console.log(`[Stack] API running on http://${host}:${port}`);
457
- }
395
+ if (path === "/health") {
396
+ return Response.json({ status: "ok" }, { headers: corsHeaders });
397
+ }
458
398
 
459
- private getStatusJSON(): object {
460
- return {
461
- node: this.config.node.name,
462
- channels: {
463
- ssh: this.state.channels.ssh,
464
- telegram: this.state.channels.telegram,
465
- },
466
- api: {
467
- enabled: this.state.api.enabled,
468
- port: this.state.api.port,
399
+ return Response.json({ error: "Not found" }, { status: 404, headers: corsHeaders });
469
400
  },
470
- ralphLoops: this.state.ralphLoops.size,
471
- uptime: Math.floor((Date.now() - this.state.started.getTime()) / 1000),
472
- };
473
- }
401
+ });
474
402
 
475
- private listRalphLoopsJSON(): object[] {
476
- return Array.from(this.state.ralphLoops.values());
403
+ console.log(`[Stack] API running on http://${host}:${port}`);
477
404
  }
478
405
 
479
406
  // ============================================================
@@ -485,17 +412,16 @@ export class Stack {
485
412
 
486
413
  this.abortController = new AbortController();
487
414
 
488
- // Register channels (only if configured)
415
+ // Initialize Node Agent (services ready but features disabled)
416
+ await this.initializeNodeAgent();
417
+
418
+ // Register channels
489
419
  await this.registerSSH();
490
420
  await this.registerTelegram();
491
421
 
492
- // Set handler
493
422
  this.router.setHandler((routed) => this.handleMessage(routed));
494
-
495
- // Start router
496
423
  await this.router.start();
497
424
 
498
- // Start API (optional)
499
425
  this.startAPI();
500
426
 
501
427
  console.log("[Stack] Running!");
@@ -508,10 +434,10 @@ export class Stack {
508
434
  if (this.state.api.enabled) {
509
435
  console.log(` - API: :${this.state.api.port}`);
510
436
  }
511
- console.log(" - Commands: /ralph start|list|stop, /status, /memory <cmd>");
437
+ console.log(" - Node Agent: ready (Ralph/Git disabled)");
438
+ console.log(" - Commands: /status, /memory <cmd>");
512
439
 
513
- // Keep running
514
- await new Promise(() => {}); // Run forever
440
+ await new Promise(() => {});
515
441
  }
516
442
 
517
443
  async stop(): Promise<void> {
@@ -519,7 +445,6 @@ export class Stack {
519
445
  this.abortController?.abort();
520
446
  await this.router.stop();
521
447
 
522
- // Stop all channels
523
448
  for (const [name, channel] of this.channels) {
524
449
  console.log(`[Stack] Stopping ${name}...`);
525
450
  await channel.stop();
@@ -534,40 +459,32 @@ export class Stack {
534
459
  // ============================================================
535
460
 
536
461
  async function main() {
537
- // Build config - only enable channels when explicitly configured
538
462
  const config: StackConfig = {
539
- // SSH only enabled if SSH_CHAT_DIR is set
540
463
  ...(process.env.SSH_CHAT_DIR ? {
541
464
  ssh: {
542
465
  chatDir: process.env.SSH_CHAT_DIR,
543
466
  pollInterval: parseInt(process.env.SSH_POLL_INTERVAL || "500", 10),
544
467
  }
545
468
  } : {}),
546
- // Telegram only enabled if bot token is set
547
469
  ...(process.env.TELEGRAM_BOT_TOKEN ? {
548
470
  telegram: {
549
471
  botToken: process.env.TELEGRAM_BOT_TOKEN,
550
472
  allowedChats: process.env.TELEGRAM_CHAT_ID ? [parseInt(process.env.TELEGRAM_CHAT_ID, 10)] : undefined,
551
473
  }
552
474
  } : {}),
553
- // API enabled by default
554
475
  api: {
555
476
  port: parseInt(process.env.API_PORT || "8911", 10),
556
477
  host: process.env.API_HOST,
557
478
  },
558
- ralph: {
559
- worktreesDir: process.env.WORKTREES_DIR || "/root/worktrees",
560
- repoUrl: process.env.REPO_URL || "",
561
- },
562
479
  node: {
563
480
  name: process.env.NODE_NAME || "stack",
564
481
  hostname: process.env.HOSTNAME || "localhost",
565
482
  },
483
+ enableNodeAgent: process.env.ENABLE_NODE_AGENT !== "false",
566
484
  };
567
485
 
568
486
  const stack = new Stack(config);
569
487
 
570
- // Handle shutdown
571
488
  process.on("SIGINT", async () => {
572
489
  await stack.stop();
573
490
  process.exit(0);
@@ -581,7 +498,6 @@ async function main() {
581
498
  await stack.start();
582
499
  }
583
500
 
584
- // Run if executed directly
585
501
  if (import.meta.main) {
586
502
  main().catch((error) => {
587
503
  console.error("[Stack] Fatal error:", error);