pi-harness-runtime 1.1.375 → 1.1.376

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 (116) hide show
  1. package/README.md +68 -39
  2. package/package.json +2 -1
  3. package/packages/a2a-adapter/package.json +1 -1
  4. package/packages/agent-policy/package.json +1 -1
  5. package/packages/architecture-generator/package.json +1 -1
  6. package/packages/auth/package.json +1 -1
  7. package/packages/autonomous-refactor/package.json +1 -1
  8. package/packages/autonomous-runtime/package.json +1 -1
  9. package/packages/cache-strategy/package.json +1 -1
  10. package/packages/capability-registry/package.json +1 -1
  11. package/packages/checkpoint/package.json +1 -1
  12. package/packages/cli-plugin-sdk/package.json +1 -1
  13. package/packages/clipboard/package.json +1 -1
  14. package/packages/clipboard-plugin/package.json +1 -1
  15. package/packages/code-generation/package.json +1 -1
  16. package/packages/code-review/package.json +1 -1
  17. package/packages/codex-adapter/package.json +1 -1
  18. package/packages/codex-watcher/package.json +1 -1
  19. package/packages/config-capture/package.json +1 -1
  20. package/packages/connection-runtime/package.json +1 -1
  21. package/packages/context-compiler/package.json +1 -1
  22. package/packages/context-discovery/package.json +1 -1
  23. package/packages/context-manager/package.json +1 -1
  24. package/packages/contracts/package.json +1 -1
  25. package/packages/cookie-sanitizer/package.json +1 -1
  26. package/packages/cost-optimizer/package.json +1 -1
  27. package/packages/dependency-analyzer/package.json +1 -1
  28. package/packages/django-plugin/package.json +1 -1
  29. package/packages/doc-generator/package.json +1 -1
  30. package/packages/durable-review/package.json +1 -1
  31. package/packages/e2e-tester/package.json +1 -1
  32. package/packages/evaluation-engine/package.json +1 -1
  33. package/packages/evaluation-runner/package.json +1 -1
  34. package/packages/event-bus/package.json +1 -1
  35. package/packages/event-sink/package.json +1 -1
  36. package/packages/event-store/package.json +1 -1
  37. package/packages/experience-replay/package.json +1 -1
  38. package/packages/feedback-collector/package.json +1 -1
  39. package/packages/file-copy-helper/package.json +1 -1
  40. package/packages/framework-detector/package.json +1 -1
  41. package/packages/framework-plugin-sdk/package.json +1 -1
  42. package/packages/frappe-plugin/package.json +1 -1
  43. package/packages/generic-web-plugin/package.json +1 -1
  44. package/packages/health-monitor/package.json +1 -1
  45. package/packages/intent-analyzer/package.json +1 -1
  46. package/packages/jev-judge/package.json +1 -1
  47. package/packages/knowledge-graph/package.json +1 -1
  48. package/packages/knowledge-retrieval/package.json +1 -1
  49. package/packages/laravel-plugin/package.json +1 -1
  50. package/packages/learning-engine/package.json +1 -1
  51. package/packages/mcp-adapter/package.json +1 -1
  52. package/packages/memory-engine/package.json +1 -1
  53. package/packages/milestone-manager/package.json +1 -1
  54. package/packages/model-registry/package.json +1 -1
  55. package/packages/nextjs-plugin/package.json +1 -1
  56. package/packages/notification/package.json +1 -1
  57. package/packages/observability/package.json +1 -1
  58. package/packages/okf-indexer/package.json +1 -1
  59. package/packages/paperclip-integration/package.json +1 -1
  60. package/packages/performance-optimizer/package.json +1 -1
  61. package/packages/permission-mode/package.json +1 -1
  62. package/packages/privilege-broker/package.json +1 -1
  63. package/packages/project-analyzer/package.json +1 -1
  64. package/packages/project-bootstrap/package.json +1 -1
  65. package/packages/projection-engine/package.json +1 -1
  66. package/packages/prompt-compiler/package.json +1 -1
  67. package/packages/prompt-versioning/package.json +1 -1
  68. package/packages/provider-adapter-sdk/package.json +1 -1
  69. package/packages/provider-router/package.json +1 -1
  70. package/packages/provider-selector/package.json +1 -1
  71. package/packages/providers/package.json +1 -1
  72. package/packages/qdrant-skills/package.json +1 -1
  73. package/packages/quota-manager/package.json +1 -1
  74. package/packages/rate-limiter/package.json +1 -1
  75. package/packages/react-vite-plugin/package.json +1 -1
  76. package/packages/relay/dist/ws-server.d.ts +48 -0
  77. package/packages/relay/dist/ws-server.js +130 -0
  78. package/packages/relay/package.json +1 -1
  79. package/packages/relay/src/ws-server.ts +165 -0
  80. package/packages/release-manager/package.json +1 -1
  81. package/packages/requirement-compiler/package.json +1 -1
  82. package/packages/runtime/package.json +1 -1
  83. package/packages/scheduler/package.json +1 -1
  84. package/packages/scheduler-adapter/package.json +1 -1
  85. package/packages/session/package.json +1 -1
  86. package/packages/session-api/package.json +1 -1
  87. package/packages/session-export/package.json +1 -1
  88. package/packages/shared-context/package.json +1 -1
  89. package/packages/skill-mcp-client/package.json +1 -1
  90. package/packages/skill-registry/package.json +1 -1
  91. package/packages/skills/package.json +1 -1
  92. package/packages/sprint-planner/package.json +1 -1
  93. package/packages/subscription-engine/package.json +1 -1
  94. package/packages/task-compiler/package.json +1 -1
  95. package/packages/telegram/package.json +1 -1
  96. package/packages/tencentdb-memory/package.json +1 -1
  97. package/packages/tencentdb-sync/package.json +1 -1
  98. package/packages/test-data-generator/package.json +1 -1
  99. package/packages/test-generator/package.json +1 -1
  100. package/packages/todo-bd-sync/package.json +1 -1
  101. package/packages/token-estimation/package.json +1 -1
  102. package/packages/token-optimizer/package.json +1 -1
  103. package/packages/trajectory/package.json +1 -1
  104. package/packages/tui/package.json +1 -1
  105. package/packages/types/package.json +1 -1
  106. package/packages/voice-api/package.json +1 -1
  107. package/packages/webmcp-tester/package.json +1 -1
  108. package/packages/wiki/package.json +1 -1
  109. package/packages/workflow-events/package.json +1 -1
  110. package/packages/workspace-scanner/package.json +1 -1
  111. package/packages/worktree/package.json +1 -1
  112. package/packages/write-review/package.json +1 -1
  113. package/scripts/relay-client.ts +175 -0
  114. package/scripts/sync-callback-queue.ts +57 -0
  115. package/scripts/telegram-callback-processor.ts +16 -0
  116. package/scripts/unified-webhook-server.ts +142 -129
@@ -1,26 +1,22 @@
1
1
  #!/usr/bin/env bun
2
2
  /**
3
- * Unified Webhook Server — Handles both Telegram and LINE on single domain
3
+ * Unified Webhook Server — Handles Telegram, LINE, and WebSocket relay
4
4
  *
5
5
  * Routes:
6
6
  * /telegram → Telegram webhook handler
7
7
  * /line → LINE webhook handler
8
+ * /ws → WebSocket relay (port 8444)
8
9
  *
9
10
  * Usage:
10
11
  * bun run scripts/unified-webhook-server.ts
11
- *
12
- * Environment variables (or keys/*.txt files):
13
- * TELEGRAM_BOT_TOKEN, TELEGRAM_WEBHOOK_SECRET, TELEGRAM_ALLOWED_USERS
14
- * LINE_CHANNEL_SECRET, LINE_ACCESS_TOKEN
15
- *
16
- * Webhook URLs to configure:
17
- * Telegram: https://webhook.bunchee.online/telegram
18
- * LINE: https://webhook.bunchee.online/line
19
12
  */
20
13
 
21
14
  import { existsSync, readFileSync, appendFileSync } from "node:fs";
22
15
  import { join } from "node:path";
16
+ import { homedir } from "os";
23
17
  import { createServer, IncomingMessage, ServerResponse } from "node:http";
18
+ import { WebSocketServer, WebSocket } from "ws";
19
+ import { randomUUID } from "node:crypto";
24
20
 
25
21
  // --- Configuration ---
26
22
  interface Config {
@@ -30,68 +26,58 @@ interface Config {
30
26
  allowedUsers: Set<number>;
31
27
  };
32
28
  line?: {
33
- channelSecret: string;
34
29
  accessToken: string;
30
+ channelSecret: string;
35
31
  };
36
32
  }
37
33
 
38
- function getConfig(): Config {
39
- const homedir = process.env.HOME || "/home/frappe";
40
- const keysDir = join(homedir, ".pi-harness-runtime", "keys");
34
+ const PORT = parseInt(process.env.PORT || "8443", 10);
35
+ const WS_PORT = parseInt(process.env.WS_PORT || "8444", 10);
36
+ const RELAY_AUTH_TOKEN = process.env.RELAY_AUTH_TOKEN || "b61940936a2625036d6faf7fe32c52b3"; // Same as webhook secret
37
+
38
+ // --- Keys Loader ---
39
+ function loadConfig(): Config {
40
+ const keysDir = join(homedir(), ".pi-harness-runtime", "keys");
41
41
  const config: Config = {};
42
42
 
43
- // Telegram config
43
+ // Telegram
44
44
  const telegramTokenPath = join(keysDir, "telegram-bot-token.txt");
45
45
  const telegramSecretPath = join(keysDir, "telegram-webhook-secret.txt");
46
46
  const telegramUsersPath = join(keysDir, "telegram-allowed-users.txt");
47
47
 
48
48
  if (existsSync(telegramTokenPath)) {
49
- const token = process.env.TELEGRAM_BOT_TOKEN || readFileSync(telegramTokenPath, "utf8").trim();
50
- const secret = process.env.TELEGRAM_WEBHOOK_SECRET || (existsSync(telegramSecretPath) ? readFileSync(telegramSecretPath, "utf8").trim() : "");
49
+ const token = readFileSync(telegramTokenPath, "utf8").trim();
50
+ const secret = existsSync(telegramSecretPath)
51
+ ? readFileSync(telegramSecretPath, "utf8").trim()
52
+ : process.env.TELEGRAM_WEBHOOK_SECRET || "";
51
53
 
52
54
  const allowedUsers = new Set<number>();
53
- const envUsers = process.env.TELEGRAM_ALLOWED_USERS;
54
- if (envUsers) {
55
- for (const part of envUsers.split(",")) {
56
- const num = parseInt(part.trim(), 10);
57
- if (!isNaN(num) && num > 0) allowedUsers.add(num);
58
- }
59
- }
60
55
  if (existsSync(telegramUsersPath)) {
61
- const content = readFileSync(telegramUsersPath, "utf8").trim();
62
- for (const part of content.split(",")) {
63
- const num = parseInt(part.trim(), 10);
64
- if (!isNaN(num) && num > 0) allowedUsers.add(num);
56
+ const users = readFileSync(telegramUsersPath, "utf8").trim();
57
+ for (const id of users.split(",").map((s) => parseInt(s.trim(), 10))) {
58
+ if (!isNaN(id) && id > 0) allowedUsers.add(id);
65
59
  }
66
60
  }
67
61
 
68
62
  config.telegram = { botToken: token, secret, allowedUsers };
69
63
  }
70
64
 
71
- // LINE config
72
- const lineSecretPath = join(keysDir, "line-channel-secret.txt");
65
+ // LINE
73
66
  const lineTokenPath = join(keysDir, "line-access-token.txt");
67
+ const lineSecretPath = join(keysDir, "line-channel-secret.txt");
74
68
 
75
- if (existsSync(lineSecretPath) && existsSync(lineTokenPath)) {
76
- const channelSecret = process.env.LINE_CHANNEL_SECRET || readFileSync(lineSecretPath, "utf8").trim();
77
- const accessToken = process.env.LINE_ACCESS_TOKEN || readFileSync(lineTokenPath, "utf8").trim();
78
- config.line = { channelSecret, accessToken };
69
+ if (existsSync(lineTokenPath) && existsSync(lineSecretPath)) {
70
+ config.line = {
71
+ accessToken: readFileSync(lineTokenPath, "utf8").trim(),
72
+ channelSecret: readFileSync(lineSecretPath, "utf8").trim(),
73
+ };
79
74
  }
80
75
 
81
76
  return config;
82
77
  }
83
78
 
84
- // --- Logging ---
85
- const LOG_FILE = "/tmp/unified-webhook.log";
86
- function log(prefix: string, ...args: unknown[]) {
87
- const msg = args.map(a => typeof a === "object" ? JSON.stringify(a) : String(a)).join(" ");
88
- const line = `[${new Date().toISOString()}] [${prefix}] ${msg}\n`;
89
- appendFileSync(LOG_FILE, line);
90
- console.log(line.trim());
91
- }
92
-
93
- // --- Crypto utilities ---
94
- async function verifyTelegram(data: string, secret: string, hash: string): Promise<boolean> {
79
+ // --- Telegram HMAC Verification (for future use with secret hash) ---
80
+ async function _verifyTelegramHmac(data: string, secret: string, hash: string): Promise<boolean> {
95
81
  const encoder = new TextEncoder();
96
82
  const key = await crypto.subtle.importKey(
97
83
  "raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]
@@ -101,6 +87,7 @@ async function verifyTelegram(data: string, secret: string, hash: string): Promi
101
87
  return expected === hash;
102
88
  }
103
89
 
90
+ // --- LINE Verification ---
104
91
  async function verifyLINE(body: string, channelSecret: string, xLineSignature: string): Promise<boolean> {
105
92
  const encoder = new TextEncoder();
106
93
  const key = await crypto.subtle.importKey(
@@ -111,13 +98,57 @@ async function verifyLINE(body: string, channelSecret: string, xLineSignature: s
111
98
  return expected === xLineSignature;
112
99
  }
113
100
 
114
- // --- Callback queue ---
101
+ // --- Callback Queue (file-based fallback) ---
115
102
  const QUEUE_FILE = join(process.env.HOME || "/tmp", ".pi-harness-runtime", "callback-queue.jsonl");
116
103
  function queueCallback(callback: Record<string, unknown>) {
117
104
  appendFileSync(QUEUE_FILE, JSON.stringify({ ...callback, timestamp: new Date().toISOString() }) + "\n");
118
105
  }
119
106
 
120
- // --- Telegram handler ---
107
+ // --- WebSocket Relay ---
108
+ const wsClients = new Map<string, WebSocket>();
109
+
110
+ function broadcastCallback(callback: Record<string, unknown>) {
111
+ const msg = JSON.stringify({ type: "callback", payload: callback });
112
+ let count = 0;
113
+ for (const ws of wsClients.values()) {
114
+ if (ws.readyState === WebSocket.OPEN) {
115
+ ws.send(msg);
116
+ count++;
117
+ }
118
+ }
119
+ return count;
120
+ }
121
+
122
+ function startWebSocketServer() {
123
+ const wss = new WebSocketServer({ port: WS_PORT });
124
+
125
+ wss.on("connection", (ws: WebSocket) => {
126
+ const clientId = randomUUID();
127
+ wsClients.set(clientId, ws);
128
+ console.log(`[WS] Client connected: ${clientId}`);
129
+
130
+ ws.on("message", (data) => {
131
+ try {
132
+ const msg = JSON.parse(data.toString());
133
+ if (msg.type === "auth" && msg.payload === RELAY_AUTH_TOKEN) {
134
+ ws.send(JSON.stringify({ type: "auth", payload: { success: true } }));
135
+ console.log(`[WS] Client authenticated: ${clientId}`);
136
+ }
137
+ } catch (err) {
138
+ console.error(`[WS] Invalid message from ${clientId}:`, err);
139
+ }
140
+ });
141
+
142
+ ws.on("close", () => {
143
+ wsClients.delete(clientId);
144
+ console.log(`[WS] Client disconnected: ${clientId}`);
145
+ });
146
+ });
147
+
148
+ console.log(`[WS] WebSocket server started on port ${WS_PORT}`);
149
+ }
150
+
151
+ // --- Telegram Handler ---
121
152
  async function handleTelegram(req: IncomingMessage, res: ServerResponse, config: Config) {
122
153
  if (!config.telegram) {
123
154
  res.writeHead(404);
@@ -128,7 +159,6 @@ async function handleTelegram(req: IncomingMessage, res: ServerResponse, config:
128
159
  // Verify secret token
129
160
  const secretToken = req.headers["x-telegram-bot-api-secret-token"];
130
161
  if (secretToken !== config.telegram.secret) {
131
- log("Telegram", "Unauthorized - secret mismatch");
132
162
  res.writeHead(403);
133
163
  res.end("Forbidden");
134
164
  return;
@@ -149,7 +179,6 @@ async function handleTelegram(req: IncomingMessage, res: ServerResponse, config:
149
179
 
150
180
  // Check authorization
151
181
  if (config.telegram.allowedUsers.size > 0 && !config.telegram.allowedUsers.has(from.id)) {
152
- log("Telegram", `Unauthorized user: ${from.id}`);
153
182
  res.writeHead(403);
154
183
  res.end("Forbidden");
155
184
  return;
@@ -171,15 +200,22 @@ async function handleTelegram(req: IncomingMessage, res: ServerResponse, config:
171
200
  targetId = "";
172
201
  }
173
202
 
174
- // Queue the callback
175
- queueCallback({
203
+ const callback = {
176
204
  type: "telegram_callback",
177
205
  action,
178
206
  targetId,
179
207
  userId: from.id,
180
208
  username: from.username,
181
209
  messageId: query.message?.message_id,
182
- });
210
+ };
211
+
212
+ // Broadcast via WebSocket
213
+ const wsCount = broadcastCallback(callback);
214
+ if (wsCount === 0) {
215
+ // Fallback to file queue
216
+ queueCallback(callback);
217
+ }
218
+ console.log(`[Telegram] Callback: ${action} ${targetId} from @${from.username || from.id} (WS clients: ${wsCount})`);
183
219
 
184
220
  // Acknowledge immediately
185
221
  await fetch(`https://api.telegram.org/bot${config.telegram.botToken}/answerCallbackQuery`, {
@@ -187,20 +223,18 @@ async function handleTelegram(req: IncomingMessage, res: ServerResponse, config:
187
223
  headers: { "Content-Type": "application/json" },
188
224
  body: JSON.stringify({ callback_query_id: query.id }),
189
225
  });
190
-
191
- log("Telegram", `Callback: ${action} ${targetId} from @${from.username || from.id}`);
192
226
  }
193
227
 
194
228
  res.writeHead(200);
195
229
  res.end("OK");
196
230
  } catch (err) {
197
- log("Telegram", "Error:", err);
231
+ console.error("[Telegram] Error:", err);
198
232
  res.writeHead(500);
199
233
  res.end("Error");
200
234
  }
201
235
  }
202
236
 
203
- // --- LINE handler ---
237
+ // --- LINE Handler ---
204
238
  async function handleLINE(req: IncomingMessage, res: ServerResponse, config: Config) {
205
239
  if (!config.line) {
206
240
  res.writeHead(404);
@@ -211,7 +245,6 @@ async function handleLINE(req: IncomingMessage, res: ServerResponse, config: Con
211
245
  // Verify signature
212
246
  const signature = req.headers["x-line-signature"] as string;
213
247
  if (!signature) {
214
- log("LINE", "Missing signature");
215
248
  res.writeHead(400);
216
249
  res.end("Bad Request");
217
250
  return;
@@ -224,108 +257,88 @@ async function handleLINE(req: IncomingMessage, res: ServerResponse, config: Con
224
257
 
225
258
  // Verify
226
259
  if (!(await verifyLINE(body, config.line.channelSecret, signature))) {
227
- log("LINE", "Invalid signature");
228
260
  res.writeHead(403);
229
261
  res.end("Forbidden");
230
262
  return;
231
263
  }
232
264
 
233
265
  try {
234
- const events = JSON.parse(body).events || [];
266
+ const bodyJson = JSON.parse(body);
267
+ const events = bodyJson.events || [];
235
268
 
236
269
  for (const event of events) {
237
270
  if (event.type === "postback") {
238
- // Parse postback data
239
271
  const data = event.postback?.data || "";
240
272
  const colonIdx = data.indexOf(":");
241
- let action: string, targetId: string;
242
- if (colonIdx !== -1) {
243
- action = data.slice(0, colonIdx);
244
- targetId = data.slice(colonIdx + 1);
245
- } else {
246
- action = data;
247
- targetId = "";
248
- }
273
+ let action = data.slice(0, colonIdx !== -1 ? colonIdx : undefined);
274
+ let targetId = colonIdx !== -1 ? data.slice(colonIdx + 1) : "";
249
275
 
250
- queueCallback({
276
+ const callback = {
251
277
  type: "line_callback",
252
278
  action,
253
279
  targetId,
254
280
  userId: event.source?.userId,
255
- replyToken: event.replyToken,
256
- });
257
-
258
- log("LINE", `Postback: ${action} ${targetId}`);
259
- } else if (event.type === "message" && event.message?.type === "text") {
260
- // Forward text messages to agent
261
- queueCallback({
262
- type: "line_message",
263
- text: event.message.text,
264
- userId: event.source?.userId,
265
- replyToken: event.replyToken,
266
- });
281
+ timestamp: new Date().toISOString(),
282
+ };
267
283
 
268
- log("LINE", `Message: ${event.message.text}`);
284
+ // Broadcast via WebSocket
285
+ const wsCount = broadcastCallback(callback);
286
+ if (wsCount === 0) {
287
+ queueCallback(callback);
288
+ }
289
+ console.log(`[LINE] Postback: ${action} ${targetId} (WS clients: ${wsCount})`);
269
290
  }
270
291
  }
271
292
 
272
293
  res.writeHead(200);
273
- res.end("OK");
294
+ res.end(JSON.stringify({ status: "ok" }));
274
295
  } catch (err) {
275
- log("LINE", "Error:", err);
296
+ console.error("[LINE] Error:", err);
276
297
  res.writeHead(500);
277
298
  res.end("Error");
278
299
  }
279
300
  }
280
301
 
281
- // --- Main server ---
282
- const PORT = parseInt(process.env.PORT || "3000", 10);
302
+ // --- Main ---
303
+ const config = loadConfig();
283
304
 
284
- async function main() {
285
- const config = getConfig();
305
+ console.log("===========================================");
306
+ console.log("Unified Webhook Server");
307
+ console.log("===========================================");
308
+ if (config.telegram) console.log(" /telegram - Telegram webhook");
309
+ if (config.line) console.log(" /line - LINE webhook");
310
+ console.log("===========================================");
286
311
 
287
- if (!config.telegram && !config.line) {
288
- console.error("No webhooks configured. Set up Telegram and/or LINE keys.");
289
- process.exit(1);
290
- }
312
+ // Start WebSocket relay
313
+ startWebSocketServer();
291
314
 
292
- console.log("Unified Webhook Server");
293
- console.log(`Listening on port ${PORT}`);
294
- if (config.telegram) console.log(" /telegram - Telegram webhook");
295
- if (config.line) console.log(" /line - LINE webhook");
296
-
297
- const server = createServer(async (req, res) => {
298
- const url = new URL(req.url || "/", `http://localhost:${PORT}`);
299
-
300
- try {
301
- if (url.pathname === "/telegram" && req.method === "POST") {
302
- await handleTelegram(req, res, config);
303
- } else if (url.pathname === "/line" && req.method === "POST") {
304
- await handleLINE(req, res, config);
305
- } else if (url.pathname === "/health") {
306
- res.writeHead(200);
307
- res.end("OK");
308
- } else {
309
- res.writeHead(404);
310
- res.end("Not Found");
311
- }
312
- } catch (err) {
313
- log("Server", "Error:", err);
314
- res.writeHead(500);
315
- res.end("Error");
316
- }
317
- });
315
+ // HTTP webhook server
316
+ const server = createServer(async (req, res) => {
317
+ const url = new URL(req.url || "/", `http://localhost:${PORT}`);
318
318
 
319
- server.listen(PORT, () => {
320
- console.log(`Server running on http://0.0.0.0:${PORT}`);
321
- console.log(`Configure webhooks:`);
322
- if (config.telegram) {
323
- console.log(` Telegram: https://webhook.bunchee.online/telegram`);
324
- }
325
- if (config.line) {
326
- console.log(` LINE: https://webhook.bunchee.online/line`);
319
+ try {
320
+ if (url.pathname === "/telegram" && req.method === "POST") {
321
+ await handleTelegram(req, res, config);
322
+ } else if (url.pathname === "/line" && req.method === "POST") {
323
+ await handleLINE(req, res, config);
324
+ } else if (url.pathname === "/health") {
325
+ res.writeHead(200);
326
+ res.end("OK");
327
+ } else {
328
+ res.writeHead(404);
329
+ res.end("Not Found");
327
330
  }
328
- });
329
- }
330
-
331
- main();
331
+ } catch (err) {
332
+ console.error("[Server] Error:", err);
333
+ res.writeHead(500);
334
+ res.end("Error");
335
+ }
336
+ });
337
+
338
+ server.listen(PORT, () => {
339
+ console.log(`Server running on http://0.0.0.0:${PORT}`);
340
+ console.log(`WebSocket relay on ws://0.0.0.0:${WS_PORT}`);
341
+ console.log("Configure webhooks:");
342
+ if (config.telegram) console.log(` Telegram: https://webhook.bunchee.online/telegram`);
343
+ if (config.line) console.log(` LINE: https://webhook.bunchee.online/line`);
344
+ });