@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.
- package/README.md +26 -11
- package/dist/index.js +64318 -4384
- package/package.json +9 -9
- package/src/index.ts +122 -206
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ebowwa/stack",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
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
|
-
"
|
|
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/
|
|
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 -
|
|
3
|
+
* @ebowwa/stack - Cross-Channel AI Stack with Node Agent
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Features:
|
|
6
6
|
* - Unified Router: Cross-channel communication (SSH + Telegram)
|
|
7
|
-
* -
|
|
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
|
-
* │ │
|
|
14
|
-
* │ │
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
131
|
+
nodeAgent: { enabled: false },
|
|
122
132
|
};
|
|
123
133
|
|
|
124
|
-
// Build memory channels dynamically
|
|
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.
|
|
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"
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
240
|
-
|
|
241
|
-
if (ralphResult) {
|
|
270
|
+
// Status command
|
|
271
|
+
if (text.trim().toLowerCase() === "/status") {
|
|
242
272
|
stopTyping();
|
|
243
273
|
return {
|
|
244
|
-
content: { text:
|
|
274
|
+
content: { text: this.getStatus() },
|
|
245
275
|
replyTo: { messageId: message.messageId, channelId: message.channelId },
|
|
246
276
|
};
|
|
247
277
|
}
|
|
248
278
|
|
|
249
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
##
|
|
290
|
-
-
|
|
291
|
-
-
|
|
292
|
-
-
|
|
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
|
-
##
|
|
342
|
+
## Info
|
|
303
343
|
- Node: ${this.config.node.name}
|
|
304
|
-
- Channels: ${
|
|
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
|
-
`
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
|
|
457
|
-
|
|
395
|
+
if (path === "/health") {
|
|
396
|
+
return Response.json({ status: "ok" }, { headers: corsHeaders });
|
|
397
|
+
}
|
|
458
398
|
|
|
459
|
-
|
|
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
|
-
|
|
471
|
-
uptime: Math.floor((Date.now() - this.state.started.getTime()) / 1000),
|
|
472
|
-
};
|
|
473
|
-
}
|
|
401
|
+
});
|
|
474
402
|
|
|
475
|
-
|
|
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
|
-
//
|
|
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(" -
|
|
437
|
+
console.log(" - Node Agent: ready (Ralph/Git disabled)");
|
|
438
|
+
console.log(" - Commands: /status, /memory <cmd>");
|
|
512
439
|
|
|
513
|
-
|
|
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);
|