@feynmanzhang/open-party 0.1.4 → 0.1.6

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 (35) hide show
  1. package/dist/claude-code/{open-party-0.1.0 → open-party-0.1.6}/.claude-plugin/plugin.json +1 -1
  2. package/dist/claude-code/open-party-0.1.6/BUILD_INFO.json +6 -0
  3. package/dist/claude-code/{open-party-0.1.1 → open-party-0.1.6}/dist/hook-handler.js +113 -46
  4. package/dist/claude-code/{open-party-0.1.1 → open-party-0.1.6}/dist/mcp-server.js +156 -25
  5. package/dist/claude-code/{open-party-0.1.1 → open-party-0.1.6}/dist/party-server.js +549 -64
  6. package/dist/claude-code/{open-party-0.1.1 → open-party-0.1.6}/hooks/hooks.json +1 -1
  7. package/dist/claude-code/{open-party-0.1.1 → open-party-0.1.6}/package.json +1 -1
  8. package/dist/cli/index.js +750 -135
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/openclaw/open-party-0.1.5/BUILD_INFO.json +6 -0
  11. package/dist/openclaw/open-party-0.1.5/SKILL.md +127 -0
  12. package/dist/openclaw/open-party-0.1.5/dist/index.js +550 -0
  13. package/dist/openclaw/open-party-0.1.5/dist/party-server.js +5502 -0
  14. package/dist/openclaw/open-party-0.1.5/openclaw.plugin.json +28 -0
  15. package/dist/openclaw/open-party-0.1.5/package.json +12 -0
  16. package/dist/openclaw/open-party-0.1.5/skills/open-party/SKILL.md +90 -0
  17. package/dist/openclaw/open-party-0.1.6/BUILD_INFO.json +6 -0
  18. package/dist/openclaw/open-party-0.1.6/SKILL.md +127 -0
  19. package/dist/openclaw/open-party-0.1.6/dist/index.js +550 -0
  20. package/dist/openclaw/open-party-0.1.6/dist/party-server.js +5502 -0
  21. package/dist/openclaw/open-party-0.1.6/openclaw.plugin.json +28 -0
  22. package/dist/openclaw/open-party-0.1.6/package.json +12 -0
  23. package/dist/openclaw/open-party-0.1.6/skills/open-party/SKILL.md +90 -0
  24. package/dist/party-server.js +549 -64
  25. package/dist/party-server.js.map +1 -1
  26. package/package.json +35 -4
  27. package/dist/claude-code/open-party-0.1.1/.claude-plugin/plugin.json +0 -5
  28. package/dist/claude-code/open-party-0.1.1/.mcp.json +0 -9
  29. package/dist/claude-code/open-party-0.1.1/BUILD_INFO.json +0 -6
  30. package/dist/hook-handler.js +0 -555
  31. package/dist/hook-handler.js.map +0 -1
  32. package/dist/mcp-server.js +0 -21408
  33. package/dist/mcp-server.js.map +0 -1
  34. /package/dist/claude-code/{open-party-0.1.0 → open-party-0.1.6}/.mcp.json +0 -0
  35. /package/dist/claude-code/{open-party-0.1.1 → open-party-0.1.6}/skills/open-party/SKILL.md +0 -0
@@ -16,18 +16,352 @@ var init_config = __esm({
16
16
  "src/server/config.ts"() {
17
17
  "use strict";
18
18
  PARTY_PORT = parseInt(process.env.PARTY_PORT || "8000", 10);
19
- HEARTBEAT_TIMEOUT = parseFloat(process.env.HEARTBEAT_TIMEOUT || "30");
20
- CLEANUP_INTERVAL = parseFloat(process.env.CLEANUP_INTERVAL || "10");
19
+ HEARTBEAT_TIMEOUT = parseFloat(process.env.HEARTBEAT_TIMEOUT || "60");
20
+ CLEANUP_INTERVAL = parseFloat(process.env.CLEANUP_INTERVAL || "60");
21
21
  DISCOVERY_INTERVAL = parseFloat(process.env.DISCOVERY_INTERVAL || "20");
22
22
  REMOTE_STALE_FACTOR = parseInt(process.env.REMOTE_STALE_FACTOR || "3", 10);
23
23
  PROBE_TIMEOUT = parseFloat(process.env.PROBE_TIMEOUT || "5");
24
24
  }
25
25
  });
26
26
 
27
+ // src/server/logger.ts
28
+ import { existsSync, mkdirSync, appendFileSync, readdirSync, unlinkSync, statSync } from "fs";
29
+ import { join } from "path";
30
+ import { homedir } from "os";
31
+ function getEffectiveLevel() {
32
+ const env = (process.env.LOG_LEVEL || "info").toLowerCase().trim();
33
+ if (env in LEVEL_ORDER) return env;
34
+ return "info";
35
+ }
36
+ function initLogFile() {
37
+ if (!existsSync(LOG_DIR)) {
38
+ mkdirSync(LOG_DIR, { recursive: true });
39
+ return;
40
+ }
41
+ try {
42
+ const now = Date.now();
43
+ const cutoff = now - LOG_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
44
+ const files = readdirSync(LOG_DIR);
45
+ for (const f of files) {
46
+ if (!f.endsWith("-open-party.log")) continue;
47
+ try {
48
+ const stat = statSync(join(LOG_DIR, f));
49
+ if (stat.mtimeMs < cutoff) {
50
+ unlinkSync(join(LOG_DIR, f));
51
+ }
52
+ } catch {
53
+ }
54
+ }
55
+ } catch {
56
+ }
57
+ }
58
+ function getLogFilePath() {
59
+ const d = /* @__PURE__ */ new Date();
60
+ const yy = String(d.getFullYear()).slice(2);
61
+ const mm = String(d.getMonth() + 1).padStart(2, "0");
62
+ const dd = String(d.getDate()).padStart(2, "0");
63
+ return join(LOG_DIR, `${yy}-${mm}-${dd}-open-party.log`);
64
+ }
65
+ function shouldLog(level) {
66
+ return LEVEL_ORDER[level] >= LEVEL_ORDER[effectiveLevel];
67
+ }
68
+ function extractError(err) {
69
+ if (err instanceof Error) {
70
+ const stack = err.stack ? `
71
+ ${err.stack}` : "";
72
+ return `${err.message}${stack}`;
73
+ }
74
+ return String(err);
75
+ }
76
+ function format(level, tag, message) {
77
+ const now = /* @__PURE__ */ new Date();
78
+ const levelStr = level.toUpperCase().padEnd(5);
79
+ const yy = String(now.getFullYear()).slice(2);
80
+ const mm = String(now.getMonth() + 1).padStart(2, "0");
81
+ const dd = String(now.getDate()).padStart(2, "0");
82
+ const hh = String(now.getHours()).padStart(2, "0");
83
+ const min = String(now.getMinutes()).padStart(2, "0");
84
+ const ss = String(now.getSeconds()).padStart(2, "0");
85
+ const ts = `${yy}-${mm}-${dd} ${hh}:${min}:${ss}`;
86
+ return `${ts} [${levelStr}] [${tag}] ${message}`;
87
+ }
88
+ function output(consoleFn, level, tag, message) {
89
+ if (!shouldLog(level)) return;
90
+ const line = format(level, tag, message);
91
+ consoleFn(line);
92
+ try {
93
+ appendFileSync(getLogFilePath(), line + "\n", "utf-8");
94
+ } catch {
95
+ }
96
+ }
97
+ var LEVEL_ORDER, effectiveLevel, LOG_DIR, LOG_RETENTION_DAYS, logger;
98
+ var init_logger = __esm({
99
+ "src/server/logger.ts"() {
100
+ "use strict";
101
+ LEVEL_ORDER = {
102
+ debug: 0,
103
+ info: 1,
104
+ warn: 2,
105
+ error: 3
106
+ };
107
+ effectiveLevel = getEffectiveLevel();
108
+ LOG_DIR = join(homedir(), ".open-party", "logs");
109
+ LOG_RETENTION_DAYS = 7;
110
+ initLogFile();
111
+ logger = {
112
+ info(tag, message, data) {
113
+ output(console.log, "info", tag, data ? `${message} ${JSON.stringify(data)}` : message);
114
+ },
115
+ warn(tag, message, data) {
116
+ output(console.warn, "warn", tag, data ? `${message} ${JSON.stringify(data)}` : message);
117
+ },
118
+ error(tag, message, err) {
119
+ const detail = err ? `: ${extractError(err)}` : "";
120
+ output(console.error, "error", tag, message + detail);
121
+ },
122
+ debug(tag, message, data) {
123
+ output(console.debug, "debug", tag, data ? `${message} ${JSON.stringify(data)}` : message);
124
+ }
125
+ };
126
+ }
127
+ });
128
+
129
+ // src/server/persistence.ts
130
+ import { existsSync as existsSync2, readFileSync, writeFileSync, unlinkSync as unlinkSync2, renameSync, mkdirSync as mkdirSync2 } from "fs";
131
+ import { join as join2 } from "path";
132
+ import { homedir as homedir2 } from "os";
133
+ function dataDirPath() {
134
+ const pluginData = process.env.CLAUDE_PLUGIN_DATA || "";
135
+ if (pluginData) return join2(pluginData, "data");
136
+ return join2(homedir2(), ".open-party", "data");
137
+ }
138
+ function runMigrations(raw2) {
139
+ const data = raw2;
140
+ if (!data || typeof data !== "object") {
141
+ throw new Error("Snapshot is not a valid object");
142
+ }
143
+ const version = typeof data.version === "number" ? data.version : 0;
144
+ let snapshot = {
145
+ version,
146
+ saved_at: typeof data.saved_at === "number" ? data.saved_at : 0,
147
+ agents: Array.isArray(data.agents) ? data.agents : [],
148
+ history: typeof data.history === "object" && data.history !== null && !Array.isArray(data.history) ? data.history : {}
149
+ };
150
+ for (const m of MIGRATORS) {
151
+ if (m.version > snapshot.version) {
152
+ snapshot = m.migrate(snapshot);
153
+ snapshot.version = m.version;
154
+ }
155
+ }
156
+ return snapshot;
157
+ }
158
+ function abortableSleep(ms, signal) {
159
+ return new Promise((resolve, reject) => {
160
+ const timer = setTimeout(resolve, ms);
161
+ signal?.addEventListener(
162
+ "abort",
163
+ () => {
164
+ clearTimeout(timer);
165
+ reject(new DOMException("Aborted", "AbortError"));
166
+ },
167
+ { once: true }
168
+ );
169
+ });
170
+ }
171
+ var CURRENT_SCHEMA_VERSION, SNAPSHOT_FILE, SHUTDOWN_MARKER_FILE, DEFAULT_SNAPSHOT_INTERVAL_MS, DEBOUNCE_MS, MIGRATORS, SnapshotManager;
172
+ var init_persistence = __esm({
173
+ "src/server/persistence.ts"() {
174
+ "use strict";
175
+ init_logger();
176
+ CURRENT_SCHEMA_VERSION = 1;
177
+ SNAPSHOT_FILE = "snapshot.json";
178
+ SHUTDOWN_MARKER_FILE = "shutdown-marker.json";
179
+ DEFAULT_SNAPSHOT_INTERVAL_MS = 6e4;
180
+ DEBOUNCE_MS = 5e3;
181
+ MIGRATORS = [
182
+ // Future: { version: 2, migrate(snapshot) { ... } },
183
+ ];
184
+ SnapshotManager = class {
185
+ _dir;
186
+ _snapshotPath;
187
+ _markerPath;
188
+ _debounceTimer = null;
189
+ constructor(dataDir) {
190
+ this._dir = dataDir ?? dataDirPath();
191
+ this._snapshotPath = join2(this._dir, SNAPSHOT_FILE);
192
+ this._markerPath = join2(this._dir, SHUTDOWN_MARKER_FILE);
193
+ mkdirSync2(this._dir, { recursive: true });
194
+ const tmpPath = this._snapshotPath + ".tmp";
195
+ try {
196
+ if (existsSync2(tmpPath)) {
197
+ unlinkSync2(tmpPath);
198
+ }
199
+ } catch (error) {
200
+ logger.warn("Persistence", "Failed to clean up stale tmp file", error);
201
+ }
202
+ }
203
+ // ------------------------------------------------------------------
204
+ // Write / Load
205
+ // ------------------------------------------------------------------
206
+ /** Atomically write a snapshot of registry agents and message queue history. */
207
+ writeSnapshot(agents, history) {
208
+ const snapshot = {
209
+ version: CURRENT_SCHEMA_VERSION,
210
+ saved_at: Date.now(),
211
+ agents,
212
+ history
213
+ };
214
+ const serialized = JSON.stringify(snapshot, null, 2);
215
+ const tmpPath = this._snapshotPath + ".tmp";
216
+ try {
217
+ writeFileSync(tmpPath, serialized, "utf-8");
218
+ renameSync(tmpPath, this._snapshotPath);
219
+ } catch (error) {
220
+ logger.error("Persistence", "Failed to write snapshot", error);
221
+ try {
222
+ unlinkSync2(tmpPath);
223
+ } catch {
224
+ }
225
+ throw error;
226
+ }
227
+ }
228
+ /** Load and validate snapshot. Returns null if file missing or corrupt. */
229
+ loadSnapshot() {
230
+ if (!existsSync2(this._snapshotPath)) {
231
+ return null;
232
+ }
233
+ try {
234
+ const raw2 = JSON.parse(readFileSync(this._snapshotPath, "utf-8"));
235
+ return runMigrations(raw2);
236
+ } catch (error) {
237
+ logger.warn("Persistence", "Failed to load snapshot (starting fresh)", error);
238
+ return null;
239
+ }
240
+ }
241
+ // ------------------------------------------------------------------
242
+ // Hydration (restore in-memory state from snapshot)
243
+ // ------------------------------------------------------------------
244
+ /** Restore agents into registry. Overwrites host_ip with current selfIp. */
245
+ hydrateAgents(registry2, selfIp) {
246
+ const snapshot = this.loadSnapshot();
247
+ if (!snapshot || snapshot.agents.length === 0) return 0;
248
+ const now = Date.now() / 1e3;
249
+ let count = 0;
250
+ for (const agent of snapshot.agents) {
251
+ agent.last_heartbeat = now;
252
+ const info = registry2.register({
253
+ agent_id: agent.agent_id,
254
+ display_name: agent.display_name,
255
+ metadata: agent.metadata ?? {}
256
+ });
257
+ info.host_ip = selfIp;
258
+ count++;
259
+ }
260
+ return count;
261
+ }
262
+ /** Restore message history into queue. */
263
+ hydrateHistory(queue) {
264
+ const snapshot = this.loadSnapshot();
265
+ if (!snapshot || Object.keys(snapshot.history).length === 0) return 0;
266
+ let totalEntries = 0;
267
+ for (const [agentId, entries] of Object.entries(snapshot.history)) {
268
+ for (const entry of entries) {
269
+ queue.logToHistory(agentId, entry.direction, {
270
+ sender_id: entry.sender_id,
271
+ recipient_id: entry.recipient_id,
272
+ summary: entry.summary,
273
+ content: entry.content,
274
+ timestamp: entry.timestamp
275
+ });
276
+ totalEntries++;
277
+ }
278
+ }
279
+ return totalEntries;
280
+ }
281
+ // ------------------------------------------------------------------
282
+ // Shutdown marker
283
+ // ------------------------------------------------------------------
284
+ /** Write shutdown marker to detect interrupted shutdown on next start. */
285
+ writeShutdownMarker() {
286
+ try {
287
+ writeFileSync(
288
+ this._markerPath,
289
+ JSON.stringify({ started_at: Date.now() }),
290
+ "utf-8"
291
+ );
292
+ } catch (error) {
293
+ logger.warn("Persistence", "Failed to write shutdown marker", error);
294
+ }
295
+ }
296
+ /** Remove shutdown marker — called after successful shutdown. */
297
+ removeShutdownMarker() {
298
+ try {
299
+ if (existsSync2(this._markerPath)) {
300
+ unlinkSync2(this._markerPath);
301
+ }
302
+ } catch (error) {
303
+ logger.warn("Persistence", "Failed to remove shutdown marker", error);
304
+ }
305
+ }
306
+ /** Check if a shutdown marker exists (indicates previous shutdown was interrupted). */
307
+ hasShutdownMarker() {
308
+ return existsSync2(this._markerPath);
309
+ }
310
+ // ------------------------------------------------------------------
311
+ // Snapshot loop & debounce
312
+ // ------------------------------------------------------------------
313
+ /**
314
+ * Start periodic snapshot background loop.
315
+ * Writes snapshot every `intervalMs` milliseconds until signal is aborted.
316
+ */
317
+ async startSnapshotLoop(signal, getAgents, getHistory, intervalMs = DEFAULT_SNAPSHOT_INTERVAL_MS) {
318
+ while (!signal.aborted) {
319
+ try {
320
+ await abortableSleep(intervalMs, signal);
321
+ } catch (e) {
322
+ if (signal.aborted) break;
323
+ throw e;
324
+ }
325
+ if (signal.aborted) break;
326
+ try {
327
+ this.writeSnapshot(getAgents(), getHistory());
328
+ } catch (error) {
329
+ logger.warn("Persistence", "Periodic snapshot failed", error);
330
+ }
331
+ }
332
+ }
333
+ /**
334
+ * Schedule a debounced snapshot write.
335
+ * Multiple calls within DEBOUNCE_MS window are coalesced into one write.
336
+ */
337
+ scheduleSnapshot(getAgents, getHistory) {
338
+ if (this._debounceTimer !== null) {
339
+ clearTimeout(this._debounceTimer);
340
+ }
341
+ this._debounceTimer = setTimeout(() => {
342
+ this._debounceTimer = null;
343
+ try {
344
+ this.writeSnapshot(getAgents(), getHistory());
345
+ } catch (error) {
346
+ logger.warn("Persistence", "Debounced snapshot failed", error);
347
+ }
348
+ }, DEBOUNCE_MS);
349
+ }
350
+ /** Cancel pending debounced snapshot (called during shutdown). */
351
+ cancelDebounce() {
352
+ if (this._debounceTimer !== null) {
353
+ clearTimeout(this._debounceTimer);
354
+ this._debounceTimer = null;
355
+ }
356
+ }
357
+ };
358
+ }
359
+ });
360
+
27
361
  // src/infra/tailscale.ts
28
362
  import { execFileSync, execSync } from "child_process";
29
- import { existsSync } from "fs";
30
- import { join } from "path";
363
+ import { existsSync as existsSync3 } from "fs";
364
+ import { join as join3 } from "path";
31
365
  import { spawn as nodeSpawn } from "child_process";
32
366
  function parsePossiblyNoisyJson(raw2) {
33
367
  const trimmed = raw2.trim();
@@ -47,7 +381,7 @@ function runExec(cmd, timeout = 5e3) {
47
381
  });
48
382
  }
49
383
  function checkBinary(path, timeout = 3e3) {
50
- if (!path || !existsSync(path)) return false;
384
+ if (!path || !existsSync3(path)) return false;
51
385
  try {
52
386
  execFileSync(path, ["--version"], { timeout, encoding: "utf-8", stdio: "pipe", windowsHide: true });
53
387
  return true;
@@ -64,9 +398,9 @@ function findTailscaleBinary() {
64
398
  } catch {
65
399
  }
66
400
  const knownPaths = process.platform === "win32" ? [
67
- join(process.env.ProgramFiles || "C:\\Program Files", "Tailscale", "tailscale.exe"),
68
- join(process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)", "Tailscale", "tailscale.exe"),
69
- join(process.env.LOCALAPPDATA || "", "Tailscale", "tailscale.exe")
401
+ join3(process.env.ProgramFiles || "C:\\Program Files", "Tailscale", "tailscale.exe"),
402
+ join3(process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)", "Tailscale", "tailscale.exe"),
403
+ join3(process.env.LOCALAPPDATA || "", "Tailscale", "tailscale.exe")
70
404
  ] : [
71
405
  "/Applications/Tailscale.app/Contents/MacOS/Tailscale",
72
406
  "/usr/bin/tailscale",
@@ -110,7 +444,7 @@ function getTailnetHostname(binary) {
110
444
  ];
111
445
  let lastErr = null;
112
446
  for (const candidate of candidates) {
113
- if (candidate.startsWith("/") && !existsSync(candidate)) continue;
447
+ if (candidate.startsWith("/") && !existsSync3(candidate)) continue;
114
448
  try {
115
449
  const stdout = runExec([candidate, "status", "--json"], 5e3);
116
450
  const parsed = stdout.trim() ? parsePossiblyNoisyJson(stdout) : {};
@@ -152,11 +486,11 @@ function execWithSudoFallback(cmd, timeout = 15e3) {
152
486
  function joinTailnet(authKey, timeout = 3e4) {
153
487
  const binary = getTailscaleBinary();
154
488
  try {
155
- const output = execWithSudoFallback(
489
+ const output2 = execWithSudoFallback(
156
490
  [binary, "up", "--authkey", authKey],
157
491
  timeout
158
492
  );
159
- return { success: true, output: output.trim() };
493
+ return { success: true, output: output2.trim() };
160
494
  } catch (e) {
161
495
  const err = e;
162
496
  return { success: false, output: (err.stderr ?? err.message ?? "").trim() };
@@ -182,11 +516,12 @@ function getTailscaleInstallationStatus() {
182
516
  };
183
517
  }
184
518
  return { state: "not_connected", binary };
185
- } catch (e) {
519
+ } catch (error) {
520
+ console.error("[Tailscale] Failed to get installation status:", error);
186
521
  return {
187
522
  state: "not_connected",
188
523
  binary,
189
- error: e.message
524
+ error: error instanceof Error ? error.message : String(error)
190
525
  };
191
526
  }
192
527
  }
@@ -196,9 +531,9 @@ function resetTailscaleBinaryCache() {
196
531
  function logoutTailscale(timeout = 15e3) {
197
532
  const binary = getTailscaleBinary();
198
533
  try {
199
- const output = runExec([binary, "logout"], timeout);
534
+ const output2 = runExec([binary, "logout"], timeout);
200
535
  resetTailscaleBinaryCache();
201
- return { success: true, output: output.trim() };
536
+ return { success: true, output: output2.trim() };
202
537
  } catch (e) {
203
538
  const err = e;
204
539
  return { success: false, output: (err.stderr ?? err.message ?? "").trim() };
@@ -362,6 +697,14 @@ var init_message_queue = __esm({
362
697
  removeAgentHistory(agentId) {
363
698
  this._history.delete(agentId);
364
699
  }
700
+ /** Return a shallow copy of the full history map (for persistence snapshots). */
701
+ getHistorySnapshot() {
702
+ const copy = {};
703
+ for (const [agentId, entries] of this._history) {
704
+ copy[agentId] = [...entries];
705
+ }
706
+ return copy;
707
+ }
365
708
  };
366
709
  }
367
710
  });
@@ -376,15 +719,14 @@ function classifyFetchError(error) {
376
719
  if (error instanceof DOMException && error.name === "AbortError") return null;
377
720
  return null;
378
721
  }
379
- function sleep(ms) {
380
- return new Promise((resolve) => setTimeout(resolve, ms));
381
- }
382
722
  var UNKNOWN, PARTY_SERVER, DEGRADED, SUSPECT, DOWN, NOT_SERVER, MAYBE, MAYBE_MAX_RETRIES, BACKOFF_BASE, BACKOFF_CAP, FAILURE_SUSPECT, FAILURE_DOWN, PeerDiscovery;
383
723
  var init_peer_discovery = __esm({
384
724
  "src/server/peer-discovery.ts"() {
385
725
  "use strict";
386
726
  init_config();
387
727
  init_tailscale();
728
+ init_persistence();
729
+ init_logger();
388
730
  UNKNOWN = "UNKNOWN";
389
731
  PARTY_SERVER = "PARTY_SERVER";
390
732
  DEGRADED = "DEGRADED";
@@ -446,14 +788,14 @@ var init_peer_discovery = __esm({
446
788
  // ------------------------------------------------------------------
447
789
  // Main discovery loop
448
790
  // ------------------------------------------------------------------
449
- async runLoop() {
450
- while (true) {
791
+ async runLoop(signal) {
792
+ while (!signal?.aborted) {
451
793
  try {
452
794
  await this.discoveryCycle();
453
795
  } catch (e) {
454
- console.error("[Discovery] Cycle failed:", e);
796
+ logger.error("Discovery", "Cycle failed", e);
455
797
  }
456
- await sleep(DISCOVERY_INTERVAL * 1e3);
798
+ await abortableSleep(DISCOVERY_INTERVAL * 1e3, signal);
457
799
  }
458
800
  }
459
801
  async discoveryCycle() {
@@ -486,8 +828,8 @@ var init_peer_discovery = __esm({
486
828
  let status;
487
829
  try {
488
830
  status = readTailscaleStatus();
489
- } catch {
490
- console.warn("[Discovery] Failed to read Tailscale status");
831
+ } catch (error) {
832
+ logger.warn("Discovery", "Failed to read Tailscale status", error);
491
833
  return [];
492
834
  }
493
835
  const peersRaw = status.Peer;
@@ -512,7 +854,7 @@ var init_peer_discovery = __esm({
512
854
  // Peer probing
513
855
  // ------------------------------------------------------------------
514
856
  async probePeer(ps) {
515
- ps.lastProbeAt = performance.now() / 1e3;
857
+ ps.lastProbeAt = Date.now() / 1e3;
516
858
  const healthy = await this.checkHealth(ps.ip);
517
859
  if (healthy === null) {
518
860
  this.handleProbeFailure(ps, true);
@@ -581,7 +923,7 @@ var init_peer_discovery = __esm({
581
923
  const old = ps.status;
582
924
  ps.status = newStatus;
583
925
  if (old !== newStatus) {
584
- console.log(`[Discovery] Peer ${ps.ip}: ${old} -> ${newStatus}`);
926
+ logger.info("Discovery", `Peer ${ps.ip}: ${old} -> ${newStatus}`);
585
927
  }
586
928
  if (newStatus === NOT_SERVER) {
587
929
  const retries = ps.maybeRetries > 0 ? ps.maybeRetries : 1;
@@ -624,8 +966,8 @@ var init_peer_discovery = __esm({
624
966
  this._remoteAgents.delete(aid);
625
967
  }
626
968
  }
627
- } catch {
628
- console.warn(`[Discovery] Failed to sync agents from ${peerIp}`);
969
+ } catch (error) {
970
+ logger.warn("Discovery", `Failed to sync agents from ${peerIp}`, error);
629
971
  }
630
972
  }
631
973
  // ------------------------------------------------------------------
@@ -681,7 +1023,8 @@ var init_registry = __esm({
681
1023
  return info;
682
1024
  }
683
1025
  remove(agentId) {
684
- return this._agents.delete(agentId);
1026
+ const existed = this._agents.delete(agentId);
1027
+ return existed;
685
1028
  }
686
1029
  heartbeat(agentId) {
687
1030
  const info = this._agents.get(agentId);
@@ -719,9 +1062,14 @@ __export(state_exports, {
719
1062
  STARTED_AT: () => STARTED_AT,
720
1063
  discovery: () => discovery,
721
1064
  getSelfIp: () => getSelfIp,
1065
+ getSnapshotManager: () => getSnapshotManager,
1066
+ initSnapshotManager: () => initSnapshotManager,
1067
+ lifecycleController: () => lifecycleController,
722
1068
  messageQueue: () => messageQueue,
723
1069
  refreshSelfIp: () => refreshSelfIp,
724
- registry: () => registry
1070
+ registry: () => registry,
1071
+ scheduleSnapshot: () => scheduleSnapshot,
1072
+ snapshotManager: () => snapshotManager
725
1073
  });
726
1074
  function resolveSelfIp() {
727
1075
  try {
@@ -741,7 +1089,19 @@ function refreshSelfIp() {
741
1089
  _selfIp = resolveSelfIp();
742
1090
  return _selfIp;
743
1091
  }
744
- var _selfIp, STARTED_AT, registry, messageQueue, discovery;
1092
+ function scheduleSnapshot() {
1093
+ snapshotManager?.scheduleSnapshot(
1094
+ () => registry.listAll(),
1095
+ () => messageQueue.getHistorySnapshot()
1096
+ );
1097
+ }
1098
+ function initSnapshotManager(mgr) {
1099
+ snapshotManager = mgr;
1100
+ }
1101
+ function getSnapshotManager() {
1102
+ return snapshotManager;
1103
+ }
1104
+ var _selfIp, STARTED_AT, registry, messageQueue, discovery, snapshotManager, lifecycleController;
745
1105
  var init_state = __esm({
746
1106
  "src/server/state.ts"() {
747
1107
  "use strict";
@@ -754,6 +1114,8 @@ var init_state = __esm({
754
1114
  registry = new AgentRegistry(getSelfIp());
755
1115
  messageQueue = new MessageQueue();
756
1116
  discovery = new PeerDiscovery(getSelfIp());
1117
+ snapshotManager = null;
1118
+ lifecycleController = new AbortController();
757
1119
  }
758
1120
  });
759
1121
 
@@ -3610,10 +3972,12 @@ var serve = (options, listeningListener) => {
3610
3972
 
3611
3973
  // src/server/index.ts
3612
3974
  init_config();
3975
+ init_persistence();
3976
+ init_logger();
3613
3977
  init_state();
3614
- import { mkdirSync, writeFileSync, unlinkSync } from "fs";
3615
- import { join as join2, dirname } from "path";
3616
- import { homedir } from "os";
3978
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync3 } from "fs";
3979
+ import { join as join4, dirname as dirname2 } from "path";
3980
+ import { homedir as homedir3 } from "os";
3617
3981
 
3618
3982
  // src/server/models.ts
3619
3983
  function sanitizeAgentInfo(info) {
@@ -3626,6 +3990,7 @@ function sanitizeAgentList(agents) {
3626
3990
 
3627
3991
  // src/server/routes/agent.ts
3628
3992
  init_config();
3993
+ init_logger();
3629
3994
  var agentRoutes = new Hono2();
3630
3995
  async function forwardToPeer(peerIp, envelope) {
3631
3996
  const url = `http://${peerIp}:${PARTY_PORT}/proxy/receive`;
@@ -3644,21 +4009,26 @@ async function forwardToPeer(peerIp, envelope) {
3644
4009
  }
3645
4010
  }
3646
4011
  agentRoutes.post("/register", async (c) => {
3647
- const { registry: registry2 } = await Promise.resolve().then(() => (init_state(), state_exports));
4012
+ const { registry: registry2, scheduleSnapshot: scheduleSnapshot3 } = await Promise.resolve().then(() => (init_state(), state_exports));
3648
4013
  const req = await c.req.json();
3649
4014
  const info = registry2.register(req);
4015
+ scheduleSnapshot3();
4016
+ logger.info("Agent", `Registered: ${info.agent_id} (display: "${info.display_name ?? "N/A"}")`);
3650
4017
  return c.json(sanitizeAgentInfo(info));
3651
4018
  });
3652
4019
  agentRoutes.post("/remove", async (c) => {
3653
- const { registry: registry2 } = await Promise.resolve().then(() => (init_state(), state_exports));
4020
+ const { registry: registry2, scheduleSnapshot: scheduleSnapshot3 } = await Promise.resolve().then(() => (init_state(), state_exports));
3654
4021
  const req = await c.req.json();
3655
4022
  const removed = registry2.remove(req.agent_id);
4023
+ if (removed) scheduleSnapshot3();
4024
+ logger.info("Agent", removed ? `Removed: ${req.agent_id}` : `Remove failed: ${req.agent_id} (not found)`);
3656
4025
  return c.json({ status: removed ? "removed" : "not_found" });
3657
4026
  });
3658
4027
  agentRoutes.post("/heartbeat", async (c) => {
3659
4028
  const { registry: registry2 } = await Promise.resolve().then(() => (init_state(), state_exports));
3660
4029
  const req = await c.req.json();
3661
4030
  const info = registry2.heartbeat(req.agent_id);
4031
+ logger.info("Agent", `Heartbeat from ${req.agent_id}`);
3662
4032
  return c.json({ status: "ok", last_heartbeat: info.last_heartbeat });
3663
4033
  });
3664
4034
  agentRoutes.get("/list", async (c) => {
@@ -3669,7 +4039,7 @@ agentRoutes.get("/list", async (c) => {
3669
4039
  return c.json({ agents: sanitizeAgentList(allAgents), count: allAgents.length });
3670
4040
  });
3671
4041
  agentRoutes.post("/send", async (c) => {
3672
- const { registry: registry2, messageQueue: messageQueue2, discovery: discovery2 } = await Promise.resolve().then(() => (init_state(), state_exports));
4042
+ const { registry: registry2, messageQueue: messageQueue2, discovery: discovery2, scheduleSnapshot: scheduleSnapshot3 } = await Promise.resolve().then(() => (init_state(), state_exports));
3673
4043
  const envelope = await c.req.json();
3674
4044
  const recipient = envelope.recipient_id;
3675
4045
  if (!recipient) {
@@ -3680,6 +4050,8 @@ agentRoutes.post("/send", async (c) => {
3680
4050
  const count = messageQueue2.enqueue(recipient, stamped);
3681
4051
  messageQueue2.logToHistory(envelope.sender_id, "sent", stamped);
3682
4052
  messageQueue2.logToHistory(recipient, "received", stamped);
4053
+ scheduleSnapshot3();
4054
+ logger.info("Agent", `Send ${envelope.sender_id} -> ${recipient}: delivered_locally`);
3683
4055
  return c.json({ status: "delivered_locally", target: recipient });
3684
4056
  }
3685
4057
  const peerIp = discovery2.getPeerForAgent(recipient);
@@ -3688,12 +4060,16 @@ agentRoutes.post("/send", async (c) => {
3688
4060
  const result = await forwardToPeer(peerIp, envelope);
3689
4061
  if (result.status === "forwarded") {
3690
4062
  messageQueue2.logToHistory(envelope.sender_id, "sent", { ...envelope, timestamp: envelope.timestamp ?? Date.now() / 1e3 });
4063
+ scheduleSnapshot3();
4064
+ logger.info("Agent", `Send ${envelope.sender_id} -> ${recipient}: forwarded (peer ${peerIp})`);
3691
4065
  }
3692
4066
  return c.json(result);
3693
4067
  } else {
4068
+ logger.info("Agent", `Send ${envelope.sender_id} -> ${recipient}: peer_unreachable (peer ${peerIp})`);
3694
4069
  return c.json({ status: "peer_unreachable", target: peerIp });
3695
4070
  }
3696
4071
  }
4072
+ logger.info("Agent", `Send ${envelope.sender_id} -> ${recipient}: agent_not_found`);
3697
4073
  return c.json({ status: "agent_not_found", target: recipient });
3698
4074
  });
3699
4075
  agentRoutes.get("/messages/:agent_id", async (c) => {
@@ -3715,6 +4091,7 @@ agentRoutes.get("/history/:agent_id", async (c) => {
3715
4091
  init_config();
3716
4092
  init_tailscale();
3717
4093
  init_state();
4094
+ init_logger();
3718
4095
  var proxyRoutes = new Hono2();
3719
4096
  proxyRoutes.get("/health", async (c) => {
3720
4097
  let hostname = "127.0.0.1";
@@ -3739,13 +4116,13 @@ proxyRoutes.post("/receive", async (c) => {
3739
4116
  if (envelope.recipient_id) {
3740
4117
  messageQueue.enqueue(envelope.recipient_id, stamped);
3741
4118
  messageQueue.logToHistory(envelope.recipient_id, "received", stamped);
3742
- console.log(`[\u6536\u5230\u6D88\u606F] ${envelope.sender_id} -> ${envelope.recipient_id}: ${envelope.content}`);
4119
+ logger.info("Proxy", `Received msg ${envelope.sender_id} -> ${envelope.recipient_id}`);
3743
4120
  } else {
3744
4121
  for (const agent of registry.listAll()) {
3745
4122
  messageQueue.enqueue(agent.agent_id, stamped);
3746
4123
  messageQueue.logToHistory(agent.agent_id, "received", stamped);
3747
4124
  }
3748
- console.log(`[\u5E7F\u64AD\u6D88\u606F] ${envelope.sender_id} -> all: ${envelope.content}`);
4125
+ logger.info("Proxy", `Broadcast from ${envelope.sender_id} to all agents`);
3749
4126
  }
3750
4127
  return c.json({ status: "received" });
3751
4128
  });
@@ -3763,10 +4140,10 @@ proxyRoutes.post("/send/:target_ip", async (c) => {
3763
4140
  headers: { "Content-Type": "application/json" },
3764
4141
  body: JSON.stringify(payload)
3765
4142
  });
3766
- console.log(`[\u53D1\u9001\u6210\u529F] \u76EE\u6807 ${targetIp}`);
4143
+ logger.info("Proxy", `Forwarded to ${targetIp}`);
3767
4144
  return c.json({ status: "forwarded", target: targetIp });
3768
4145
  } catch (e) {
3769
- console.log(`[\u53D1\u9001\u5931\u8D25] \u76EE\u6807 ${targetIp}, \u9519\u8BEF: ${e.message}`);
4146
+ logger.warn("Proxy", `Forward to ${targetIp} failed: ${e.message}`);
3770
4147
  return c.json({ status: "error", target: targetIp, error: e.message });
3771
4148
  }
3772
4149
  });
@@ -4420,6 +4797,16 @@ body::after{
4420
4797
  }).join('');
4421
4798
  }
4422
4799
 
4800
+ // Build agent_id \u2192 display_name lookup from local + remote agents
4801
+ function buildNameMap(data) {
4802
+ const map = {};
4803
+ const all = [...(data.agents.local_agents || []), ...(data.agents.remote_agents || [])];
4804
+ for (const a of all) { map[a.agent_id] = a.display_name || a.agent_id; }
4805
+ return map;
4806
+ }
4807
+
4808
+ function resolveName(map, id) { return map[id] || id; }
4809
+
4423
4810
  function renderMessages(data) {
4424
4811
  const container = $('#msgFeed');
4425
4812
  const msgs = data.messages.recent || [];
@@ -4429,12 +4816,14 @@ body::after{
4429
4816
  return;
4430
4817
  }
4431
4818
 
4819
+ const names = buildNameMap(data);
4820
+
4432
4821
  container.innerHTML = msgs.map(function(m) {
4433
4822
  const dir = m.direction === 'received' ? 'received' : '';
4434
4823
  const arrow = m.direction === 'received' ? '&#8592;' : '&#8594;';
4435
4824
  const flow = m.direction === 'received'
4436
- ? m.sender_id + ' <span class="arrow">' + arrow + '</span> ' + (m.agent_id || '?')
4437
- : (m.agent_id || '?') + ' <span class="arrow">' + arrow + '</span> ' + (m.recipient_id || 'broadcast');
4825
+ ? resolveName(names, m.sender_id) + ' <span class="arrow">' + arrow + '</span> ' + resolveName(names, m.agent_id)
4826
+ : resolveName(names, m.agent_id) + ' <span class="arrow">' + arrow + '</span> ' + resolveName(names, m.recipient_id) || 'broadcast';
4438
4827
  return '<div class="msg-item ' + dir + '">'
4439
4828
  + '<div class="msg-top">'
4440
4829
  + '<div class="msg-flow">' + flow + '</div>'
@@ -4839,6 +5228,7 @@ body::after{
4839
5228
  // src/server/routes/dashboard.ts
4840
5229
  init_tailscale();
4841
5230
  init_state();
5231
+ init_logger();
4842
5232
  var dashboardRoutes = new Hono2();
4843
5233
  dashboardRoutes.get("/", (c) => {
4844
5234
  return c.html(DASHBOARD_HTML);
@@ -4864,10 +5254,14 @@ dashboardRoutes.get("/api/overview", async (c) => {
4864
5254
  const localAgents = sanitizeAgentList(registry.listAll());
4865
5255
  const remoteEntries = discovery.getRemoteAgentEntries();
4866
5256
  const peerStates = discovery.getPeerStates();
5257
+ const seen = /* @__PURE__ */ new Set();
4867
5258
  const recentMessages = [];
4868
5259
  for (const agent of localAgents) {
4869
5260
  const history = messageQueue.getHistory(agent.agent_id, 5);
4870
5261
  for (const entry of history) {
5262
+ const key = `${entry.sender_id}:${entry.recipient_id ?? ""}:${Math.floor(entry.timestamp)}`;
5263
+ if (seen.has(key)) continue;
5264
+ seen.add(key);
4871
5265
  recentMessages.push({ agent_id: agent.agent_id, ...entry });
4872
5266
  }
4873
5267
  }
@@ -4931,6 +5325,7 @@ dashboardRoutes.post("/api/join-network", async (c) => {
4931
5325
  return c.json({ success: false, output: "auth_key is required" }, 400);
4932
5326
  }
4933
5327
  const result = joinTailnet(authKey);
5328
+ logger.info("Dashboard", `Join network: ${result.success ? "success" : "failed"}`);
4934
5329
  return c.json(result, result.success ? 200 : 500);
4935
5330
  } catch (e) {
4936
5331
  return c.json({ success: false, output: e.message }, 500);
@@ -4939,6 +5334,7 @@ dashboardRoutes.post("/api/join-network", async (c) => {
4939
5334
  var activeLogin = null;
4940
5335
  dashboardRoutes.post("/api/logout", async (c) => {
4941
5336
  const result = logoutTailscale();
5337
+ logger.info("Dashboard", `Logout: ${result.success ? "success" : "failed"}`);
4942
5338
  if (result.success) {
4943
5339
  resetTailscaleBinaryCache();
4944
5340
  refreshSelfIp();
@@ -4951,6 +5347,7 @@ dashboardRoutes.post("/api/tailscale-login", async (c) => {
4951
5347
  }
4952
5348
  const { promise, process: process2 } = startInteractiveLogin();
4953
5349
  activeLogin = { process: process2 };
5350
+ logger.info("Dashboard", "Tailscale login initiated");
4954
5351
  const result = await promise;
4955
5352
  if (result.success && result.url) {
4956
5353
  activeLogin.url = result.url;
@@ -4962,6 +5359,7 @@ dashboardRoutes.post("/api/tailscale-login", async (c) => {
4962
5359
  dashboardRoutes.post("/api/install-tailscale", async (c) => {
4963
5360
  const { installTailscale: installTailscale2 } = await Promise.resolve().then(() => (init_tailscale_installer(), tailscale_installer_exports));
4964
5361
  const result = await installTailscale2(process.platform);
5362
+ logger.info("Dashboard", `Install Tailscale: ${result.success ? "success" : "failed"}`);
4965
5363
  if (result.success) {
4966
5364
  resetTailscaleBinaryCache();
4967
5365
  }
@@ -4970,48 +5368,135 @@ dashboardRoutes.post("/api/install-tailscale", async (c) => {
4970
5368
 
4971
5369
  // src/server/index.ts
4972
5370
  async function periodicCleanup() {
5371
+ while (!lifecycleController.signal.aborted) {
5372
+ try {
5373
+ const removed = registry.cleanupStale(HEARTBEAT_TIMEOUT);
5374
+ if (removed.length > 0) {
5375
+ logger.info("Cleanup", `Removed ${removed.length} stale agent(s): ${removed.join(", ")}`);
5376
+ }
5377
+ } catch (e) {
5378
+ logger.error("Cleanup", "Error during cleanup", e);
5379
+ }
5380
+ await abortableSleep(CLEANUP_INTERVAL * 1e3, lifecycleController.signal);
5381
+ }
4973
5382
  }
4974
5383
  var app = new Hono2();
4975
5384
  app.use("*", cors());
5385
+ app.use("*", async (c, next) => {
5386
+ const start = Date.now();
5387
+ await next();
5388
+ const ms = Date.now() - start;
5389
+ const ip = c.req.header("x-forwarded-for")?.split(",")[0]?.trim() ?? c.info?.remote?.address ?? "unknown";
5390
+ const path = c.req.path;
5391
+ if (path === "/proxy/health") {
5392
+ logger.debug("HTTP", `${c.req.method} ${path} ${c.res.status} ${ms}ms ${ip}`);
5393
+ } else {
5394
+ logger.info("HTTP", `${c.req.method} ${path} ${c.res.status} ${ms}ms ${ip}`);
5395
+ }
5396
+ });
5397
+ app.onError((err, c) => {
5398
+ logger.error("HTTP", `${c.req.method} ${c.req.path} failed (${err.status ?? 500}): ${err.message}`, err);
5399
+ return c.json({
5400
+ status: "error",
5401
+ error: (err.status ?? 500) >= 500 ? "Internal server error" : err.message
5402
+ }, err.status ?? 500);
5403
+ });
4976
5404
  app.route("/agent", agentRoutes);
4977
5405
  app.route("/proxy", proxyRoutes);
4978
5406
  app.route("/dashboard", dashboardRoutes);
4979
5407
  function pidFilePath() {
4980
5408
  const pluginData = process.env.CLAUDE_PLUGIN_DATA || "";
4981
- if (pluginData) return join2(pluginData, "server.pid");
4982
- return join2(homedir(), ".open-party", "server.pid");
5409
+ if (pluginData) return join4(pluginData, "server.pid");
5410
+ return join4(homedir3(), ".open-party", "server.pid");
5411
+ }
5412
+ var shutdownInitiated = false;
5413
+ async function performShutdown(server, pidPath) {
5414
+ if (shutdownInitiated) return;
5415
+ shutdownInitiated = true;
5416
+ logger.info("Shutdown", "Shutting down Party Server...");
5417
+ try {
5418
+ lifecycleController.abort();
5419
+ getSnapshotManager()?.cancelDebounce();
5420
+ try {
5421
+ getSnapshotManager()?.writeSnapshot(registry.listAll(), messageQueue.getHistorySnapshot());
5422
+ logger.info("Shutdown", "Final snapshot written.");
5423
+ } catch (error) {
5424
+ logger.error("Shutdown", "Failed to write final snapshot", error);
5425
+ }
5426
+ if (server.closeAllConnections) {
5427
+ server.closeAllConnections();
5428
+ }
5429
+ if (process.platform === "win32") {
5430
+ await new Promise((r) => setTimeout(r, 500));
5431
+ }
5432
+ await new Promise((resolve, reject) => {
5433
+ server.close((err) => err ? reject(err) : resolve());
5434
+ });
5435
+ if (process.platform === "win32") {
5436
+ await new Promise((r) => setTimeout(r, 500));
5437
+ }
5438
+ getSnapshotManager()?.removeShutdownMarker();
5439
+ try {
5440
+ unlinkSync3(pidPath);
5441
+ } catch {
5442
+ }
5443
+ logger.info("Shutdown", "Party Server shut down cleanly.");
5444
+ } catch (error) {
5445
+ logger.error("Shutdown", "Error during shutdown sequence", error);
5446
+ } finally {
5447
+ process.exit(0);
5448
+ }
4983
5449
  }
4984
5450
  async function main() {
4985
5451
  const pidPath = pidFilePath();
4986
- mkdirSync(dirname(pidPath), { recursive: true });
4987
- writeFileSync(pidPath, String(process.pid));
4988
- console.log(`Starting Party Server on port ${PARTY_PORT} (Tailscale IP: ${getSelfIp()})`);
5452
+ mkdirSync3(dirname2(pidPath), { recursive: true });
5453
+ writeFileSync2(pidPath, String(process.pid));
5454
+ initSnapshotManager(new SnapshotManager());
5455
+ const sm = getSnapshotManager();
5456
+ let recoveredAgents = 0;
5457
+ let recoveredHistoryEntries = 0;
5458
+ if (sm.hasShutdownMarker()) {
5459
+ logger.info("Recovery", "Previous shutdown was interrupted (shutdown marker found).");
5460
+ }
5461
+ const savedSnapshot = sm.loadSnapshot();
5462
+ if (savedSnapshot) {
5463
+ recoveredAgents = sm.hydrateAgents(registry, getSelfIp());
5464
+ recoveredHistoryEntries = sm.hydrateHistory(messageQueue);
5465
+ if (recoveredAgents > 0 || recoveredHistoryEntries > 0) {
5466
+ logger.info(
5467
+ "Recovery",
5468
+ `Recovered ${recoveredAgents} agent(s), ${recoveredHistoryEntries} history entry/entries.`
5469
+ );
5470
+ }
5471
+ }
5472
+ logger.info("Server", `Starting Party Server on port ${PARTY_PORT} (Tailscale IP: ${getSelfIp()})`);
4989
5473
  process.on("SIGHUP", () => {
4990
5474
  });
4991
5475
  const server = serve({ fetch: app.fetch, port: PARTY_PORT });
4992
- const discoveryPromise = discovery.runLoop();
5476
+ const discoveryPromise = discovery.runLoop(lifecycleController.signal);
4993
5477
  const cleanupPromise = periodicCleanup();
4994
- const shutdown = () => {
4995
- console.log("\nShutting down Party Server...");
4996
- try {
4997
- unlinkSync(pidPath);
4998
- } catch {
4999
- }
5000
- server.close();
5001
- process.exit(0);
5002
- };
5003
- process.on("SIGINT", shutdown);
5004
- process.on("SIGTERM", shutdown);
5005
- await Promise.race([discoveryPromise, cleanupPromise]);
5478
+ const snapshotLoopPromise = sm.startSnapshotLoop(
5479
+ lifecycleController.signal,
5480
+ () => registry.listAll(),
5481
+ () => messageQueue.getHistorySnapshot()
5482
+ );
5483
+ const shutdownHandler = () => void performShutdown(server, pidPath);
5484
+ process.on("SIGINT", shutdownHandler);
5485
+ process.on("SIGTERM", shutdownHandler);
5486
+ app.post("/api/shutdown", (c) => {
5487
+ void performShutdown(server, pidPath);
5488
+ return c.json({ status: "shutting_down" });
5489
+ });
5490
+ await Promise.race([discoveryPromise, cleanupPromise, snapshotLoopPromise]);
5006
5491
  }
5007
5492
  main().catch((e) => {
5008
- console.error("Fatal error:", e);
5493
+ logger.error("Server", "Fatal error", e);
5009
5494
  process.exit(1);
5010
5495
  });
5011
5496
  process.on("uncaughtException", (e) => {
5012
- console.error("Uncaught exception:", e);
5497
+ logger.error("Server", "Uncaught exception", e);
5013
5498
  });
5014
5499
  process.on("unhandledRejection", (e) => {
5015
- console.error("Unhandled rejection:", e);
5500
+ logger.error("Server", "Unhandled rejection", e);
5016
5501
  });
5017
5502
  //# sourceMappingURL=party-server.js.map