@feynmanzhang/open-party 0.1.3 → 0.1.5
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/dist/claude-code/{open-party-0.1.0 → open-party-0.1.5}/.claude-plugin/plugin.json +1 -1
- package/dist/claude-code/open-party-0.1.5/BUILD_INFO.json +6 -0
- package/dist/{hook-handler.js → claude-code/open-party-0.1.5/dist/hook-handler.js} +113 -46
- package/dist/{mcp-server.js → claude-code/open-party-0.1.5/dist/mcp-server.js} +156 -25
- package/dist/claude-code/open-party-0.1.5/dist/party-server.js +5502 -0
- package/dist/claude-code/open-party-0.1.5/hooks/hooks.json +50 -0
- package/dist/claude-code/open-party-0.1.5/package.json +6 -0
- package/dist/claude-code/open-party-0.1.5/skills/open-party/SKILL.md +71 -0
- package/dist/cli/index.js +700 -134
- package/dist/cli/index.js.map +1 -1
- package/dist/openclaw/open-party-0.1.5/BUILD_INFO.json +6 -0
- package/dist/openclaw/open-party-0.1.5/SKILL.md +127 -0
- package/dist/openclaw/open-party-0.1.5/dist/index.js +550 -0
- package/dist/openclaw/open-party-0.1.5/openclaw.plugin.json +28 -0
- package/dist/openclaw/open-party-0.1.5/package.json +12 -0
- package/dist/openclaw/open-party-0.1.5/skills/open-party/SKILL.md +90 -0
- package/dist/party-server.js +549 -64
- package/dist/party-server.js.map +1 -1
- package/package.json +35 -4
- package/dist/claude-code/open-party-0.1.1/.claude-plugin/plugin.json +0 -5
- package/dist/claude-code/open-party-0.1.1/.mcp.json +0 -9
- package/dist/hook-handler.js.map +0 -1
- package/dist/mcp-server.js.map +0 -1
- /package/dist/claude-code/{open-party-0.1.0 → open-party-0.1.5}/.mcp.json +0 -0
package/dist/cli/index.js
CHANGED
|
@@ -138,11 +138,11 @@ function execWithSudoFallback(cmd, timeout = 15e3) {
|
|
|
138
138
|
function joinTailnet(authKey, timeout = 3e4) {
|
|
139
139
|
const binary = getTailscaleBinary();
|
|
140
140
|
try {
|
|
141
|
-
const
|
|
141
|
+
const output2 = execWithSudoFallback(
|
|
142
142
|
[binary, "up", "--authkey", authKey],
|
|
143
143
|
timeout
|
|
144
144
|
);
|
|
145
|
-
return { success: true, output:
|
|
145
|
+
return { success: true, output: output2.trim() };
|
|
146
146
|
} catch (e) {
|
|
147
147
|
const err = e;
|
|
148
148
|
return { success: false, output: (err.stderr ?? err.message ?? "").trim() };
|
|
@@ -168,11 +168,12 @@ function getTailscaleInstallationStatus() {
|
|
|
168
168
|
};
|
|
169
169
|
}
|
|
170
170
|
return { state: "not_connected", binary };
|
|
171
|
-
} catch (
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error("[Tailscale] Failed to get installation status:", error);
|
|
172
173
|
return {
|
|
173
174
|
state: "not_connected",
|
|
174
175
|
binary,
|
|
175
|
-
error:
|
|
176
|
+
error: error instanceof Error ? error.message : String(error)
|
|
176
177
|
};
|
|
177
178
|
}
|
|
178
179
|
}
|
|
@@ -182,9 +183,9 @@ function resetTailscaleBinaryCache() {
|
|
|
182
183
|
function logoutTailscale(timeout = 15e3) {
|
|
183
184
|
const binary = getTailscaleBinary();
|
|
184
185
|
try {
|
|
185
|
-
const
|
|
186
|
+
const output2 = runExec([binary, "logout"], timeout);
|
|
186
187
|
resetTailscaleBinaryCache();
|
|
187
|
-
return { success: true, output:
|
|
188
|
+
return { success: true, output: output2.trim() };
|
|
188
189
|
} catch (e) {
|
|
189
190
|
const err = e;
|
|
190
191
|
return { success: false, output: (err.stderr ?? err.message ?? "").trim() };
|
|
@@ -3342,14 +3343,348 @@ var init_config = __esm({
|
|
|
3342
3343
|
"src/server/config.ts"() {
|
|
3343
3344
|
"use strict";
|
|
3344
3345
|
PARTY_PORT = parseInt(process.env.PARTY_PORT || "8000", 10);
|
|
3345
|
-
HEARTBEAT_TIMEOUT = parseFloat(process.env.HEARTBEAT_TIMEOUT || "
|
|
3346
|
-
CLEANUP_INTERVAL = parseFloat(process.env.CLEANUP_INTERVAL || "
|
|
3346
|
+
HEARTBEAT_TIMEOUT = parseFloat(process.env.HEARTBEAT_TIMEOUT || "60");
|
|
3347
|
+
CLEANUP_INTERVAL = parseFloat(process.env.CLEANUP_INTERVAL || "60");
|
|
3347
3348
|
DISCOVERY_INTERVAL = parseFloat(process.env.DISCOVERY_INTERVAL || "20");
|
|
3348
3349
|
REMOTE_STALE_FACTOR = parseInt(process.env.REMOTE_STALE_FACTOR || "3", 10);
|
|
3349
3350
|
PROBE_TIMEOUT = parseFloat(process.env.PROBE_TIMEOUT || "5");
|
|
3350
3351
|
}
|
|
3351
3352
|
});
|
|
3352
3353
|
|
|
3354
|
+
// src/server/logger.ts
|
|
3355
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, appendFileSync, readdirSync as readdirSync2, unlinkSync as unlinkSync2, statSync } from "fs";
|
|
3356
|
+
import { join as join5 } from "path";
|
|
3357
|
+
import { homedir as homedir4 } from "os";
|
|
3358
|
+
function getEffectiveLevel() {
|
|
3359
|
+
const env = (process.env.LOG_LEVEL || "info").toLowerCase().trim();
|
|
3360
|
+
if (env in LEVEL_ORDER) return env;
|
|
3361
|
+
return "info";
|
|
3362
|
+
}
|
|
3363
|
+
function initLogFile() {
|
|
3364
|
+
if (!existsSync5(LOG_DIR)) {
|
|
3365
|
+
mkdirSync3(LOG_DIR, { recursive: true });
|
|
3366
|
+
return;
|
|
3367
|
+
}
|
|
3368
|
+
try {
|
|
3369
|
+
const now = Date.now();
|
|
3370
|
+
const cutoff = now - LOG_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
3371
|
+
const files = readdirSync2(LOG_DIR);
|
|
3372
|
+
for (const f of files) {
|
|
3373
|
+
if (!f.endsWith("-open-party.log")) continue;
|
|
3374
|
+
try {
|
|
3375
|
+
const stat = statSync(join5(LOG_DIR, f));
|
|
3376
|
+
if (stat.mtimeMs < cutoff) {
|
|
3377
|
+
unlinkSync2(join5(LOG_DIR, f));
|
|
3378
|
+
}
|
|
3379
|
+
} catch {
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
} catch {
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
function getLogFilePath() {
|
|
3386
|
+
const d = /* @__PURE__ */ new Date();
|
|
3387
|
+
const yy = String(d.getFullYear()).slice(2);
|
|
3388
|
+
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
3389
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
3390
|
+
return join5(LOG_DIR, `${yy}-${mm}-${dd}-open-party.log`);
|
|
3391
|
+
}
|
|
3392
|
+
function shouldLog(level) {
|
|
3393
|
+
return LEVEL_ORDER[level] >= LEVEL_ORDER[effectiveLevel];
|
|
3394
|
+
}
|
|
3395
|
+
function extractError(err) {
|
|
3396
|
+
if (err instanceof Error) {
|
|
3397
|
+
const stack = err.stack ? `
|
|
3398
|
+
${err.stack}` : "";
|
|
3399
|
+
return `${err.message}${stack}`;
|
|
3400
|
+
}
|
|
3401
|
+
return String(err);
|
|
3402
|
+
}
|
|
3403
|
+
function format(level, tag, message) {
|
|
3404
|
+
const now = /* @__PURE__ */ new Date();
|
|
3405
|
+
const levelStr = level.toUpperCase().padEnd(5);
|
|
3406
|
+
const yy = String(now.getFullYear()).slice(2);
|
|
3407
|
+
const mm = String(now.getMonth() + 1).padStart(2, "0");
|
|
3408
|
+
const dd = String(now.getDate()).padStart(2, "0");
|
|
3409
|
+
const hh = String(now.getHours()).padStart(2, "0");
|
|
3410
|
+
const min = String(now.getMinutes()).padStart(2, "0");
|
|
3411
|
+
const ss = String(now.getSeconds()).padStart(2, "0");
|
|
3412
|
+
const ts = `${yy}-${mm}-${dd} ${hh}:${min}:${ss}`;
|
|
3413
|
+
return `${ts} [${levelStr}] [${tag}] ${message}`;
|
|
3414
|
+
}
|
|
3415
|
+
function output(consoleFn, level, tag, message) {
|
|
3416
|
+
if (!shouldLog(level)) return;
|
|
3417
|
+
const line = format(level, tag, message);
|
|
3418
|
+
consoleFn(line);
|
|
3419
|
+
try {
|
|
3420
|
+
appendFileSync(getLogFilePath(), line + "\n", "utf-8");
|
|
3421
|
+
} catch {
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
var LEVEL_ORDER, effectiveLevel, LOG_DIR, LOG_RETENTION_DAYS, logger;
|
|
3425
|
+
var init_logger = __esm({
|
|
3426
|
+
"src/server/logger.ts"() {
|
|
3427
|
+
"use strict";
|
|
3428
|
+
LEVEL_ORDER = {
|
|
3429
|
+
debug: 0,
|
|
3430
|
+
info: 1,
|
|
3431
|
+
warn: 2,
|
|
3432
|
+
error: 3
|
|
3433
|
+
};
|
|
3434
|
+
effectiveLevel = getEffectiveLevel();
|
|
3435
|
+
LOG_DIR = join5(homedir4(), ".open-party", "logs");
|
|
3436
|
+
LOG_RETENTION_DAYS = 7;
|
|
3437
|
+
initLogFile();
|
|
3438
|
+
logger = {
|
|
3439
|
+
info(tag, message, data) {
|
|
3440
|
+
output(console.log, "info", tag, data ? `${message} ${JSON.stringify(data)}` : message);
|
|
3441
|
+
},
|
|
3442
|
+
warn(tag, message, data) {
|
|
3443
|
+
output(console.warn, "warn", tag, data ? `${message} ${JSON.stringify(data)}` : message);
|
|
3444
|
+
},
|
|
3445
|
+
error(tag, message, err) {
|
|
3446
|
+
const detail = err ? `: ${extractError(err)}` : "";
|
|
3447
|
+
output(console.error, "error", tag, message + detail);
|
|
3448
|
+
},
|
|
3449
|
+
debug(tag, message, data) {
|
|
3450
|
+
output(console.debug, "debug", tag, data ? `${message} ${JSON.stringify(data)}` : message);
|
|
3451
|
+
}
|
|
3452
|
+
};
|
|
3453
|
+
}
|
|
3454
|
+
});
|
|
3455
|
+
|
|
3456
|
+
// src/server/persistence.ts
|
|
3457
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, renameSync, mkdirSync as mkdirSync4 } from "fs";
|
|
3458
|
+
import { join as join6 } from "path";
|
|
3459
|
+
import { homedir as homedir5 } from "os";
|
|
3460
|
+
function dataDirPath() {
|
|
3461
|
+
const pluginData = process.env.CLAUDE_PLUGIN_DATA || "";
|
|
3462
|
+
if (pluginData) return join6(pluginData, "data");
|
|
3463
|
+
return join6(homedir5(), ".open-party", "data");
|
|
3464
|
+
}
|
|
3465
|
+
function runMigrations(raw2) {
|
|
3466
|
+
const data = raw2;
|
|
3467
|
+
if (!data || typeof data !== "object") {
|
|
3468
|
+
throw new Error("Snapshot is not a valid object");
|
|
3469
|
+
}
|
|
3470
|
+
const version = typeof data.version === "number" ? data.version : 0;
|
|
3471
|
+
let snapshot = {
|
|
3472
|
+
version,
|
|
3473
|
+
saved_at: typeof data.saved_at === "number" ? data.saved_at : 0,
|
|
3474
|
+
agents: Array.isArray(data.agents) ? data.agents : [],
|
|
3475
|
+
history: typeof data.history === "object" && data.history !== null && !Array.isArray(data.history) ? data.history : {}
|
|
3476
|
+
};
|
|
3477
|
+
for (const m of MIGRATORS) {
|
|
3478
|
+
if (m.version > snapshot.version) {
|
|
3479
|
+
snapshot = m.migrate(snapshot);
|
|
3480
|
+
snapshot.version = m.version;
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
return snapshot;
|
|
3484
|
+
}
|
|
3485
|
+
function abortableSleep(ms, signal) {
|
|
3486
|
+
return new Promise((resolve4, reject) => {
|
|
3487
|
+
const timer = setTimeout(resolve4, ms);
|
|
3488
|
+
signal?.addEventListener(
|
|
3489
|
+
"abort",
|
|
3490
|
+
() => {
|
|
3491
|
+
clearTimeout(timer);
|
|
3492
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
3493
|
+
},
|
|
3494
|
+
{ once: true }
|
|
3495
|
+
);
|
|
3496
|
+
});
|
|
3497
|
+
}
|
|
3498
|
+
var CURRENT_SCHEMA_VERSION, SNAPSHOT_FILE, SHUTDOWN_MARKER_FILE, DEFAULT_SNAPSHOT_INTERVAL_MS, DEBOUNCE_MS, MIGRATORS, SnapshotManager;
|
|
3499
|
+
var init_persistence = __esm({
|
|
3500
|
+
"src/server/persistence.ts"() {
|
|
3501
|
+
"use strict";
|
|
3502
|
+
init_logger();
|
|
3503
|
+
CURRENT_SCHEMA_VERSION = 1;
|
|
3504
|
+
SNAPSHOT_FILE = "snapshot.json";
|
|
3505
|
+
SHUTDOWN_MARKER_FILE = "shutdown-marker.json";
|
|
3506
|
+
DEFAULT_SNAPSHOT_INTERVAL_MS = 6e4;
|
|
3507
|
+
DEBOUNCE_MS = 5e3;
|
|
3508
|
+
MIGRATORS = [
|
|
3509
|
+
// Future: { version: 2, migrate(snapshot) { ... } },
|
|
3510
|
+
];
|
|
3511
|
+
SnapshotManager = class {
|
|
3512
|
+
_dir;
|
|
3513
|
+
_snapshotPath;
|
|
3514
|
+
_markerPath;
|
|
3515
|
+
_debounceTimer = null;
|
|
3516
|
+
constructor(dataDir) {
|
|
3517
|
+
this._dir = dataDir ?? dataDirPath();
|
|
3518
|
+
this._snapshotPath = join6(this._dir, SNAPSHOT_FILE);
|
|
3519
|
+
this._markerPath = join6(this._dir, SHUTDOWN_MARKER_FILE);
|
|
3520
|
+
mkdirSync4(this._dir, { recursive: true });
|
|
3521
|
+
const tmpPath = this._snapshotPath + ".tmp";
|
|
3522
|
+
try {
|
|
3523
|
+
if (existsSync6(tmpPath)) {
|
|
3524
|
+
unlinkSync3(tmpPath);
|
|
3525
|
+
}
|
|
3526
|
+
} catch (error) {
|
|
3527
|
+
logger.warn("Persistence", "Failed to clean up stale tmp file", error);
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
// ------------------------------------------------------------------
|
|
3531
|
+
// Write / Load
|
|
3532
|
+
// ------------------------------------------------------------------
|
|
3533
|
+
/** Atomically write a snapshot of registry agents and message queue history. */
|
|
3534
|
+
writeSnapshot(agents, history) {
|
|
3535
|
+
const snapshot = {
|
|
3536
|
+
version: CURRENT_SCHEMA_VERSION,
|
|
3537
|
+
saved_at: Date.now(),
|
|
3538
|
+
agents,
|
|
3539
|
+
history
|
|
3540
|
+
};
|
|
3541
|
+
const serialized = JSON.stringify(snapshot, null, 2);
|
|
3542
|
+
const tmpPath = this._snapshotPath + ".tmp";
|
|
3543
|
+
try {
|
|
3544
|
+
writeFileSync3(tmpPath, serialized, "utf-8");
|
|
3545
|
+
renameSync(tmpPath, this._snapshotPath);
|
|
3546
|
+
} catch (error) {
|
|
3547
|
+
logger.error("Persistence", "Failed to write snapshot", error);
|
|
3548
|
+
try {
|
|
3549
|
+
unlinkSync3(tmpPath);
|
|
3550
|
+
} catch {
|
|
3551
|
+
}
|
|
3552
|
+
throw error;
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
/** Load and validate snapshot. Returns null if file missing or corrupt. */
|
|
3556
|
+
loadSnapshot() {
|
|
3557
|
+
if (!existsSync6(this._snapshotPath)) {
|
|
3558
|
+
return null;
|
|
3559
|
+
}
|
|
3560
|
+
try {
|
|
3561
|
+
const raw2 = JSON.parse(readFileSync3(this._snapshotPath, "utf-8"));
|
|
3562
|
+
return runMigrations(raw2);
|
|
3563
|
+
} catch (error) {
|
|
3564
|
+
logger.warn("Persistence", "Failed to load snapshot (starting fresh)", error);
|
|
3565
|
+
return null;
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
// ------------------------------------------------------------------
|
|
3569
|
+
// Hydration (restore in-memory state from snapshot)
|
|
3570
|
+
// ------------------------------------------------------------------
|
|
3571
|
+
/** Restore agents into registry. Overwrites host_ip with current selfIp. */
|
|
3572
|
+
hydrateAgents(registry2, selfIp) {
|
|
3573
|
+
const snapshot = this.loadSnapshot();
|
|
3574
|
+
if (!snapshot || snapshot.agents.length === 0) return 0;
|
|
3575
|
+
const now = Date.now() / 1e3;
|
|
3576
|
+
let count = 0;
|
|
3577
|
+
for (const agent of snapshot.agents) {
|
|
3578
|
+
agent.last_heartbeat = now;
|
|
3579
|
+
const info = registry2.register({
|
|
3580
|
+
agent_id: agent.agent_id,
|
|
3581
|
+
display_name: agent.display_name,
|
|
3582
|
+
metadata: agent.metadata ?? {}
|
|
3583
|
+
});
|
|
3584
|
+
info.host_ip = selfIp;
|
|
3585
|
+
count++;
|
|
3586
|
+
}
|
|
3587
|
+
return count;
|
|
3588
|
+
}
|
|
3589
|
+
/** Restore message history into queue. */
|
|
3590
|
+
hydrateHistory(queue) {
|
|
3591
|
+
const snapshot = this.loadSnapshot();
|
|
3592
|
+
if (!snapshot || Object.keys(snapshot.history).length === 0) return 0;
|
|
3593
|
+
let totalEntries = 0;
|
|
3594
|
+
for (const [agentId, entries] of Object.entries(snapshot.history)) {
|
|
3595
|
+
for (const entry of entries) {
|
|
3596
|
+
queue.logToHistory(agentId, entry.direction, {
|
|
3597
|
+
sender_id: entry.sender_id,
|
|
3598
|
+
recipient_id: entry.recipient_id,
|
|
3599
|
+
summary: entry.summary,
|
|
3600
|
+
content: entry.content,
|
|
3601
|
+
timestamp: entry.timestamp
|
|
3602
|
+
});
|
|
3603
|
+
totalEntries++;
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
return totalEntries;
|
|
3607
|
+
}
|
|
3608
|
+
// ------------------------------------------------------------------
|
|
3609
|
+
// Shutdown marker
|
|
3610
|
+
// ------------------------------------------------------------------
|
|
3611
|
+
/** Write shutdown marker to detect interrupted shutdown on next start. */
|
|
3612
|
+
writeShutdownMarker() {
|
|
3613
|
+
try {
|
|
3614
|
+
writeFileSync3(
|
|
3615
|
+
this._markerPath,
|
|
3616
|
+
JSON.stringify({ started_at: Date.now() }),
|
|
3617
|
+
"utf-8"
|
|
3618
|
+
);
|
|
3619
|
+
} catch (error) {
|
|
3620
|
+
logger.warn("Persistence", "Failed to write shutdown marker", error);
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
/** Remove shutdown marker — called after successful shutdown. */
|
|
3624
|
+
removeShutdownMarker() {
|
|
3625
|
+
try {
|
|
3626
|
+
if (existsSync6(this._markerPath)) {
|
|
3627
|
+
unlinkSync3(this._markerPath);
|
|
3628
|
+
}
|
|
3629
|
+
} catch (error) {
|
|
3630
|
+
logger.warn("Persistence", "Failed to remove shutdown marker", error);
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
/** Check if a shutdown marker exists (indicates previous shutdown was interrupted). */
|
|
3634
|
+
hasShutdownMarker() {
|
|
3635
|
+
return existsSync6(this._markerPath);
|
|
3636
|
+
}
|
|
3637
|
+
// ------------------------------------------------------------------
|
|
3638
|
+
// Snapshot loop & debounce
|
|
3639
|
+
// ------------------------------------------------------------------
|
|
3640
|
+
/**
|
|
3641
|
+
* Start periodic snapshot background loop.
|
|
3642
|
+
* Writes snapshot every `intervalMs` milliseconds until signal is aborted.
|
|
3643
|
+
*/
|
|
3644
|
+
async startSnapshotLoop(signal, getAgents, getHistory, intervalMs = DEFAULT_SNAPSHOT_INTERVAL_MS) {
|
|
3645
|
+
while (!signal.aborted) {
|
|
3646
|
+
try {
|
|
3647
|
+
await abortableSleep(intervalMs, signal);
|
|
3648
|
+
} catch (e) {
|
|
3649
|
+
if (signal.aborted) break;
|
|
3650
|
+
throw e;
|
|
3651
|
+
}
|
|
3652
|
+
if (signal.aborted) break;
|
|
3653
|
+
try {
|
|
3654
|
+
this.writeSnapshot(getAgents(), getHistory());
|
|
3655
|
+
} catch (error) {
|
|
3656
|
+
logger.warn("Persistence", "Periodic snapshot failed", error);
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
/**
|
|
3661
|
+
* Schedule a debounced snapshot write.
|
|
3662
|
+
* Multiple calls within DEBOUNCE_MS window are coalesced into one write.
|
|
3663
|
+
*/
|
|
3664
|
+
scheduleSnapshot(getAgents, getHistory) {
|
|
3665
|
+
if (this._debounceTimer !== null) {
|
|
3666
|
+
clearTimeout(this._debounceTimer);
|
|
3667
|
+
}
|
|
3668
|
+
this._debounceTimer = setTimeout(() => {
|
|
3669
|
+
this._debounceTimer = null;
|
|
3670
|
+
try {
|
|
3671
|
+
this.writeSnapshot(getAgents(), getHistory());
|
|
3672
|
+
} catch (error) {
|
|
3673
|
+
logger.warn("Persistence", "Debounced snapshot failed", error);
|
|
3674
|
+
}
|
|
3675
|
+
}, DEBOUNCE_MS);
|
|
3676
|
+
}
|
|
3677
|
+
/** Cancel pending debounced snapshot (called during shutdown). */
|
|
3678
|
+
cancelDebounce() {
|
|
3679
|
+
if (this._debounceTimer !== null) {
|
|
3680
|
+
clearTimeout(this._debounceTimer);
|
|
3681
|
+
this._debounceTimer = null;
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
};
|
|
3685
|
+
}
|
|
3686
|
+
});
|
|
3687
|
+
|
|
3353
3688
|
// src/server/message-queue.ts
|
|
3354
3689
|
var HISTORY_CAP, MessageQueue;
|
|
3355
3690
|
var init_message_queue = __esm({
|
|
@@ -3412,6 +3747,14 @@ var init_message_queue = __esm({
|
|
|
3412
3747
|
removeAgentHistory(agentId) {
|
|
3413
3748
|
this._history.delete(agentId);
|
|
3414
3749
|
}
|
|
3750
|
+
/** Return a shallow copy of the full history map (for persistence snapshots). */
|
|
3751
|
+
getHistorySnapshot() {
|
|
3752
|
+
const copy = {};
|
|
3753
|
+
for (const [agentId, entries] of this._history) {
|
|
3754
|
+
copy[agentId] = [...entries];
|
|
3755
|
+
}
|
|
3756
|
+
return copy;
|
|
3757
|
+
}
|
|
3415
3758
|
};
|
|
3416
3759
|
}
|
|
3417
3760
|
});
|
|
@@ -3426,15 +3769,14 @@ function classifyFetchError(error) {
|
|
|
3426
3769
|
if (error instanceof DOMException && error.name === "AbortError") return null;
|
|
3427
3770
|
return null;
|
|
3428
3771
|
}
|
|
3429
|
-
function sleep2(ms) {
|
|
3430
|
-
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
3431
|
-
}
|
|
3432
3772
|
var UNKNOWN, PARTY_SERVER, DEGRADED, SUSPECT, DOWN, NOT_SERVER, MAYBE, MAYBE_MAX_RETRIES, BACKOFF_BASE, BACKOFF_CAP, FAILURE_SUSPECT, FAILURE_DOWN, PeerDiscovery;
|
|
3433
3773
|
var init_peer_discovery = __esm({
|
|
3434
3774
|
"src/server/peer-discovery.ts"() {
|
|
3435
3775
|
"use strict";
|
|
3436
3776
|
init_config();
|
|
3437
3777
|
init_tailscale();
|
|
3778
|
+
init_persistence();
|
|
3779
|
+
init_logger();
|
|
3438
3780
|
UNKNOWN = "UNKNOWN";
|
|
3439
3781
|
PARTY_SERVER = "PARTY_SERVER";
|
|
3440
3782
|
DEGRADED = "DEGRADED";
|
|
@@ -3496,14 +3838,14 @@ var init_peer_discovery = __esm({
|
|
|
3496
3838
|
// ------------------------------------------------------------------
|
|
3497
3839
|
// Main discovery loop
|
|
3498
3840
|
// ------------------------------------------------------------------
|
|
3499
|
-
async runLoop() {
|
|
3500
|
-
while (
|
|
3841
|
+
async runLoop(signal) {
|
|
3842
|
+
while (!signal?.aborted) {
|
|
3501
3843
|
try {
|
|
3502
3844
|
await this.discoveryCycle();
|
|
3503
3845
|
} catch (e) {
|
|
3504
|
-
|
|
3846
|
+
logger.error("Discovery", "Cycle failed", e);
|
|
3505
3847
|
}
|
|
3506
|
-
await
|
|
3848
|
+
await abortableSleep(DISCOVERY_INTERVAL * 1e3, signal);
|
|
3507
3849
|
}
|
|
3508
3850
|
}
|
|
3509
3851
|
async discoveryCycle() {
|
|
@@ -3536,8 +3878,8 @@ var init_peer_discovery = __esm({
|
|
|
3536
3878
|
let status;
|
|
3537
3879
|
try {
|
|
3538
3880
|
status = readTailscaleStatus();
|
|
3539
|
-
} catch {
|
|
3540
|
-
|
|
3881
|
+
} catch (error) {
|
|
3882
|
+
logger.warn("Discovery", "Failed to read Tailscale status", error);
|
|
3541
3883
|
return [];
|
|
3542
3884
|
}
|
|
3543
3885
|
const peersRaw = status.Peer;
|
|
@@ -3562,7 +3904,7 @@ var init_peer_discovery = __esm({
|
|
|
3562
3904
|
// Peer probing
|
|
3563
3905
|
// ------------------------------------------------------------------
|
|
3564
3906
|
async probePeer(ps) {
|
|
3565
|
-
ps.lastProbeAt =
|
|
3907
|
+
ps.lastProbeAt = Date.now() / 1e3;
|
|
3566
3908
|
const healthy = await this.checkHealth(ps.ip);
|
|
3567
3909
|
if (healthy === null) {
|
|
3568
3910
|
this.handleProbeFailure(ps, true);
|
|
@@ -3631,7 +3973,7 @@ var init_peer_discovery = __esm({
|
|
|
3631
3973
|
const old = ps.status;
|
|
3632
3974
|
ps.status = newStatus;
|
|
3633
3975
|
if (old !== newStatus) {
|
|
3634
|
-
|
|
3976
|
+
logger.info("Discovery", `Peer ${ps.ip}: ${old} -> ${newStatus}`);
|
|
3635
3977
|
}
|
|
3636
3978
|
if (newStatus === NOT_SERVER) {
|
|
3637
3979
|
const retries = ps.maybeRetries > 0 ? ps.maybeRetries : 1;
|
|
@@ -3674,8 +4016,8 @@ var init_peer_discovery = __esm({
|
|
|
3674
4016
|
this._remoteAgents.delete(aid);
|
|
3675
4017
|
}
|
|
3676
4018
|
}
|
|
3677
|
-
} catch {
|
|
3678
|
-
|
|
4019
|
+
} catch (error) {
|
|
4020
|
+
logger.warn("Discovery", `Failed to sync agents from ${peerIp}`, error);
|
|
3679
4021
|
}
|
|
3680
4022
|
}
|
|
3681
4023
|
// ------------------------------------------------------------------
|
|
@@ -3731,7 +4073,8 @@ var init_registry = __esm({
|
|
|
3731
4073
|
return info;
|
|
3732
4074
|
}
|
|
3733
4075
|
remove(agentId) {
|
|
3734
|
-
|
|
4076
|
+
const existed = this._agents.delete(agentId);
|
|
4077
|
+
return existed;
|
|
3735
4078
|
}
|
|
3736
4079
|
heartbeat(agentId) {
|
|
3737
4080
|
const info = this._agents.get(agentId);
|
|
@@ -3769,9 +4112,14 @@ __export(state_exports, {
|
|
|
3769
4112
|
STARTED_AT: () => STARTED_AT,
|
|
3770
4113
|
discovery: () => discovery,
|
|
3771
4114
|
getSelfIp: () => getSelfIp,
|
|
4115
|
+
getSnapshotManager: () => getSnapshotManager,
|
|
4116
|
+
initSnapshotManager: () => initSnapshotManager,
|
|
4117
|
+
lifecycleController: () => lifecycleController,
|
|
3772
4118
|
messageQueue: () => messageQueue,
|
|
3773
4119
|
refreshSelfIp: () => refreshSelfIp,
|
|
3774
|
-
registry: () => registry
|
|
4120
|
+
registry: () => registry,
|
|
4121
|
+
scheduleSnapshot: () => scheduleSnapshot,
|
|
4122
|
+
snapshotManager: () => snapshotManager
|
|
3775
4123
|
});
|
|
3776
4124
|
function resolveSelfIp() {
|
|
3777
4125
|
try {
|
|
@@ -3791,7 +4139,19 @@ function refreshSelfIp() {
|
|
|
3791
4139
|
_selfIp = resolveSelfIp();
|
|
3792
4140
|
return _selfIp;
|
|
3793
4141
|
}
|
|
3794
|
-
|
|
4142
|
+
function scheduleSnapshot() {
|
|
4143
|
+
snapshotManager?.scheduleSnapshot(
|
|
4144
|
+
() => registry.listAll(),
|
|
4145
|
+
() => messageQueue.getHistorySnapshot()
|
|
4146
|
+
);
|
|
4147
|
+
}
|
|
4148
|
+
function initSnapshotManager(mgr) {
|
|
4149
|
+
snapshotManager = mgr;
|
|
4150
|
+
}
|
|
4151
|
+
function getSnapshotManager() {
|
|
4152
|
+
return snapshotManager;
|
|
4153
|
+
}
|
|
4154
|
+
var _selfIp, STARTED_AT, registry, messageQueue, discovery, snapshotManager, lifecycleController;
|
|
3795
4155
|
var init_state = __esm({
|
|
3796
4156
|
"src/server/state.ts"() {
|
|
3797
4157
|
"use strict";
|
|
@@ -3804,6 +4164,8 @@ var init_state = __esm({
|
|
|
3804
4164
|
registry = new AgentRegistry(getSelfIp());
|
|
3805
4165
|
messageQueue = new MessageQueue();
|
|
3806
4166
|
discovery = new PeerDiscovery(getSelfIp());
|
|
4167
|
+
snapshotManager = null;
|
|
4168
|
+
lifecycleController = new AbortController();
|
|
3807
4169
|
}
|
|
3808
4170
|
});
|
|
3809
4171
|
|
|
@@ -3845,23 +4207,29 @@ var init_agent = __esm({
|
|
|
3845
4207
|
init_dist();
|
|
3846
4208
|
init_models();
|
|
3847
4209
|
init_config();
|
|
4210
|
+
init_logger();
|
|
3848
4211
|
agentRoutes = new Hono2();
|
|
3849
4212
|
agentRoutes.post("/register", async (c) => {
|
|
3850
|
-
const { registry: registry2 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
4213
|
+
const { registry: registry2, scheduleSnapshot: scheduleSnapshot3 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
3851
4214
|
const req = await c.req.json();
|
|
3852
4215
|
const info = registry2.register(req);
|
|
4216
|
+
scheduleSnapshot3();
|
|
4217
|
+
logger.info("Agent", `Registered: ${info.agent_id} (display: "${info.display_name ?? "N/A"}")`);
|
|
3853
4218
|
return c.json(sanitizeAgentInfo(info));
|
|
3854
4219
|
});
|
|
3855
4220
|
agentRoutes.post("/remove", async (c) => {
|
|
3856
|
-
const { registry: registry2 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
4221
|
+
const { registry: registry2, scheduleSnapshot: scheduleSnapshot3 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
3857
4222
|
const req = await c.req.json();
|
|
3858
4223
|
const removed = registry2.remove(req.agent_id);
|
|
4224
|
+
if (removed) scheduleSnapshot3();
|
|
4225
|
+
logger.info("Agent", removed ? `Removed: ${req.agent_id}` : `Remove failed: ${req.agent_id} (not found)`);
|
|
3859
4226
|
return c.json({ status: removed ? "removed" : "not_found" });
|
|
3860
4227
|
});
|
|
3861
4228
|
agentRoutes.post("/heartbeat", async (c) => {
|
|
3862
4229
|
const { registry: registry2 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
3863
4230
|
const req = await c.req.json();
|
|
3864
4231
|
const info = registry2.heartbeat(req.agent_id);
|
|
4232
|
+
logger.info("Agent", `Heartbeat from ${req.agent_id}`);
|
|
3865
4233
|
return c.json({ status: "ok", last_heartbeat: info.last_heartbeat });
|
|
3866
4234
|
});
|
|
3867
4235
|
agentRoutes.get("/list", async (c) => {
|
|
@@ -3872,7 +4240,7 @@ var init_agent = __esm({
|
|
|
3872
4240
|
return c.json({ agents: sanitizeAgentList(allAgents), count: allAgents.length });
|
|
3873
4241
|
});
|
|
3874
4242
|
agentRoutes.post("/send", async (c) => {
|
|
3875
|
-
const { registry: registry2, messageQueue: messageQueue2, discovery: discovery2 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
4243
|
+
const { registry: registry2, messageQueue: messageQueue2, discovery: discovery2, scheduleSnapshot: scheduleSnapshot3 } = await Promise.resolve().then(() => (init_state(), state_exports));
|
|
3876
4244
|
const envelope = await c.req.json();
|
|
3877
4245
|
const recipient = envelope.recipient_id;
|
|
3878
4246
|
if (!recipient) {
|
|
@@ -3883,6 +4251,8 @@ var init_agent = __esm({
|
|
|
3883
4251
|
const count = messageQueue2.enqueue(recipient, stamped);
|
|
3884
4252
|
messageQueue2.logToHistory(envelope.sender_id, "sent", stamped);
|
|
3885
4253
|
messageQueue2.logToHistory(recipient, "received", stamped);
|
|
4254
|
+
scheduleSnapshot3();
|
|
4255
|
+
logger.info("Agent", `Send ${envelope.sender_id} -> ${recipient}: delivered_locally`);
|
|
3886
4256
|
return c.json({ status: "delivered_locally", target: recipient });
|
|
3887
4257
|
}
|
|
3888
4258
|
const peerIp = discovery2.getPeerForAgent(recipient);
|
|
@@ -3891,12 +4261,16 @@ var init_agent = __esm({
|
|
|
3891
4261
|
const result = await forwardToPeer(peerIp, envelope);
|
|
3892
4262
|
if (result.status === "forwarded") {
|
|
3893
4263
|
messageQueue2.logToHistory(envelope.sender_id, "sent", { ...envelope, timestamp: envelope.timestamp ?? Date.now() / 1e3 });
|
|
4264
|
+
scheduleSnapshot3();
|
|
4265
|
+
logger.info("Agent", `Send ${envelope.sender_id} -> ${recipient}: forwarded (peer ${peerIp})`);
|
|
3894
4266
|
}
|
|
3895
4267
|
return c.json(result);
|
|
3896
4268
|
} else {
|
|
4269
|
+
logger.info("Agent", `Send ${envelope.sender_id} -> ${recipient}: peer_unreachable (peer ${peerIp})`);
|
|
3897
4270
|
return c.json({ status: "peer_unreachable", target: peerIp });
|
|
3898
4271
|
}
|
|
3899
4272
|
}
|
|
4273
|
+
logger.info("Agent", `Send ${envelope.sender_id} -> ${recipient}: agent_not_found`);
|
|
3900
4274
|
return c.json({ status: "agent_not_found", target: recipient });
|
|
3901
4275
|
});
|
|
3902
4276
|
agentRoutes.get("/messages/:agent_id", async (c) => {
|
|
@@ -3925,6 +4299,7 @@ var init_proxy = __esm({
|
|
|
3925
4299
|
init_config();
|
|
3926
4300
|
init_tailscale();
|
|
3927
4301
|
init_state();
|
|
4302
|
+
init_logger();
|
|
3928
4303
|
proxyRoutes = new Hono2();
|
|
3929
4304
|
proxyRoutes.get("/health", async (c) => {
|
|
3930
4305
|
let hostname = "127.0.0.1";
|
|
@@ -3949,13 +4324,13 @@ var init_proxy = __esm({
|
|
|
3949
4324
|
if (envelope.recipient_id) {
|
|
3950
4325
|
messageQueue.enqueue(envelope.recipient_id, stamped);
|
|
3951
4326
|
messageQueue.logToHistory(envelope.recipient_id, "received", stamped);
|
|
3952
|
-
|
|
4327
|
+
logger.info("Proxy", `Received msg ${envelope.sender_id} -> ${envelope.recipient_id}`);
|
|
3953
4328
|
} else {
|
|
3954
4329
|
for (const agent of registry.listAll()) {
|
|
3955
4330
|
messageQueue.enqueue(agent.agent_id, stamped);
|
|
3956
4331
|
messageQueue.logToHistory(agent.agent_id, "received", stamped);
|
|
3957
4332
|
}
|
|
3958
|
-
|
|
4333
|
+
logger.info("Proxy", `Broadcast from ${envelope.sender_id} to all agents`);
|
|
3959
4334
|
}
|
|
3960
4335
|
return c.json({ status: "received" });
|
|
3961
4336
|
});
|
|
@@ -3973,10 +4348,10 @@ var init_proxy = __esm({
|
|
|
3973
4348
|
headers: { "Content-Type": "application/json" },
|
|
3974
4349
|
body: JSON.stringify(payload)
|
|
3975
4350
|
});
|
|
3976
|
-
|
|
4351
|
+
logger.info("Proxy", `Forwarded to ${targetIp}`);
|
|
3977
4352
|
return c.json({ status: "forwarded", target: targetIp });
|
|
3978
4353
|
} catch (e) {
|
|
3979
|
-
|
|
4354
|
+
logger.warn("Proxy", `Forward to ${targetIp} failed: ${e.message}`);
|
|
3980
4355
|
return c.json({ status: "error", target: targetIp, error: e.message });
|
|
3981
4356
|
}
|
|
3982
4357
|
});
|
|
@@ -4636,6 +5011,16 @@ body::after{
|
|
|
4636
5011
|
}).join('');
|
|
4637
5012
|
}
|
|
4638
5013
|
|
|
5014
|
+
// Build agent_id \u2192 display_name lookup from local + remote agents
|
|
5015
|
+
function buildNameMap(data) {
|
|
5016
|
+
const map = {};
|
|
5017
|
+
const all = [...(data.agents.local_agents || []), ...(data.agents.remote_agents || [])];
|
|
5018
|
+
for (const a of all) { map[a.agent_id] = a.display_name || a.agent_id; }
|
|
5019
|
+
return map;
|
|
5020
|
+
}
|
|
5021
|
+
|
|
5022
|
+
function resolveName(map, id) { return map[id] || id; }
|
|
5023
|
+
|
|
4639
5024
|
function renderMessages(data) {
|
|
4640
5025
|
const container = $('#msgFeed');
|
|
4641
5026
|
const msgs = data.messages.recent || [];
|
|
@@ -4645,12 +5030,14 @@ body::after{
|
|
|
4645
5030
|
return;
|
|
4646
5031
|
}
|
|
4647
5032
|
|
|
5033
|
+
const names = buildNameMap(data);
|
|
5034
|
+
|
|
4648
5035
|
container.innerHTML = msgs.map(function(m) {
|
|
4649
5036
|
const dir = m.direction === 'received' ? 'received' : '';
|
|
4650
5037
|
const arrow = m.direction === 'received' ? '←' : '→';
|
|
4651
5038
|
const flow = m.direction === 'received'
|
|
4652
|
-
? m.sender_id + ' <span class="arrow">' + arrow + '</span> ' + (m.agent_id
|
|
4653
|
-
: (m.agent_id
|
|
5039
|
+
? resolveName(names, m.sender_id) + ' <span class="arrow">' + arrow + '</span> ' + resolveName(names, m.agent_id)
|
|
5040
|
+
: resolveName(names, m.agent_id) + ' <span class="arrow">' + arrow + '</span> ' + resolveName(names, m.recipient_id) || 'broadcast';
|
|
4654
5041
|
return '<div class="msg-item ' + dir + '">'
|
|
4655
5042
|
+ '<div class="msg-top">'
|
|
4656
5043
|
+ '<div class="msg-flow">' + flow + '</div>'
|
|
@@ -5064,6 +5451,7 @@ var init_dashboard = __esm({
|
|
|
5064
5451
|
init_dashboard_html();
|
|
5065
5452
|
init_tailscale();
|
|
5066
5453
|
init_state();
|
|
5454
|
+
init_logger();
|
|
5067
5455
|
dashboardRoutes = new Hono2();
|
|
5068
5456
|
dashboardRoutes.get("/", (c) => {
|
|
5069
5457
|
return c.html(DASHBOARD_HTML);
|
|
@@ -5089,10 +5477,14 @@ var init_dashboard = __esm({
|
|
|
5089
5477
|
const localAgents = sanitizeAgentList(registry.listAll());
|
|
5090
5478
|
const remoteEntries = discovery.getRemoteAgentEntries();
|
|
5091
5479
|
const peerStates = discovery.getPeerStates();
|
|
5480
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5092
5481
|
const recentMessages = [];
|
|
5093
5482
|
for (const agent of localAgents) {
|
|
5094
5483
|
const history = messageQueue.getHistory(agent.agent_id, 5);
|
|
5095
5484
|
for (const entry of history) {
|
|
5485
|
+
const key = `${entry.sender_id}:${entry.recipient_id ?? ""}:${Math.floor(entry.timestamp)}`;
|
|
5486
|
+
if (seen.has(key)) continue;
|
|
5487
|
+
seen.add(key);
|
|
5096
5488
|
recentMessages.push({ agent_id: agent.agent_id, ...entry });
|
|
5097
5489
|
}
|
|
5098
5490
|
}
|
|
@@ -5156,6 +5548,7 @@ var init_dashboard = __esm({
|
|
|
5156
5548
|
return c.json({ success: false, output: "auth_key is required" }, 400);
|
|
5157
5549
|
}
|
|
5158
5550
|
const result = joinTailnet(authKey);
|
|
5551
|
+
logger.info("Dashboard", `Join network: ${result.success ? "success" : "failed"}`);
|
|
5159
5552
|
return c.json(result, result.success ? 200 : 500);
|
|
5160
5553
|
} catch (e) {
|
|
5161
5554
|
return c.json({ success: false, output: e.message }, 500);
|
|
@@ -5164,6 +5557,7 @@ var init_dashboard = __esm({
|
|
|
5164
5557
|
activeLogin = null;
|
|
5165
5558
|
dashboardRoutes.post("/api/logout", async (c) => {
|
|
5166
5559
|
const result = logoutTailscale();
|
|
5560
|
+
logger.info("Dashboard", `Logout: ${result.success ? "success" : "failed"}`);
|
|
5167
5561
|
if (result.success) {
|
|
5168
5562
|
resetTailscaleBinaryCache();
|
|
5169
5563
|
refreshSelfIp();
|
|
@@ -5176,6 +5570,7 @@ var init_dashboard = __esm({
|
|
|
5176
5570
|
}
|
|
5177
5571
|
const { promise, process: process2 } = startInteractiveLogin();
|
|
5178
5572
|
activeLogin = { process: process2 };
|
|
5573
|
+
logger.info("Dashboard", "Tailscale login initiated");
|
|
5179
5574
|
const result = await promise;
|
|
5180
5575
|
if (result.success && result.url) {
|
|
5181
5576
|
activeLogin.url = result.url;
|
|
@@ -5187,6 +5582,7 @@ var init_dashboard = __esm({
|
|
|
5187
5582
|
dashboardRoutes.post("/api/install-tailscale", async (c) => {
|
|
5188
5583
|
const { installTailscale: installTailscale2 } = await Promise.resolve().then(() => (init_tailscale_installer(), tailscale_installer_exports));
|
|
5189
5584
|
const result = await installTailscale2(process.platform);
|
|
5585
|
+
logger.info("Dashboard", `Install Tailscale: ${result.success ? "success" : "failed"}`);
|
|
5190
5586
|
if (result.success) {
|
|
5191
5587
|
resetTailscaleBinaryCache();
|
|
5192
5588
|
}
|
|
@@ -5197,40 +5593,107 @@ var init_dashboard = __esm({
|
|
|
5197
5593
|
|
|
5198
5594
|
// src/server/index.ts
|
|
5199
5595
|
var server_exports = {};
|
|
5200
|
-
import { mkdirSync as
|
|
5201
|
-
import { join as
|
|
5202
|
-
import { homedir as
|
|
5596
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync4, unlinkSync as unlinkSync4 } from "fs";
|
|
5597
|
+
import { join as join7, dirname as dirname4 } from "path";
|
|
5598
|
+
import { homedir as homedir6 } from "os";
|
|
5203
5599
|
async function periodicCleanup() {
|
|
5600
|
+
while (!lifecycleController.signal.aborted) {
|
|
5601
|
+
try {
|
|
5602
|
+
const removed = registry.cleanupStale(HEARTBEAT_TIMEOUT);
|
|
5603
|
+
if (removed.length > 0) {
|
|
5604
|
+
logger.info("Cleanup", `Removed ${removed.length} stale agent(s): ${removed.join(", ")}`);
|
|
5605
|
+
}
|
|
5606
|
+
} catch (e) {
|
|
5607
|
+
logger.error("Cleanup", "Error during cleanup", e);
|
|
5608
|
+
}
|
|
5609
|
+
await abortableSleep(CLEANUP_INTERVAL * 1e3, lifecycleController.signal);
|
|
5610
|
+
}
|
|
5204
5611
|
}
|
|
5205
5612
|
function pidFilePath2() {
|
|
5206
5613
|
const pluginData = process.env.CLAUDE_PLUGIN_DATA || "";
|
|
5207
|
-
if (pluginData) return
|
|
5208
|
-
return
|
|
5614
|
+
if (pluginData) return join7(pluginData, "server.pid");
|
|
5615
|
+
return join7(homedir6(), ".open-party", "server.pid");
|
|
5616
|
+
}
|
|
5617
|
+
async function performShutdown(server, pidPath) {
|
|
5618
|
+
if (shutdownInitiated) return;
|
|
5619
|
+
shutdownInitiated = true;
|
|
5620
|
+
logger.info("Shutdown", "Shutting down Party Server...");
|
|
5621
|
+
try {
|
|
5622
|
+
lifecycleController.abort();
|
|
5623
|
+
getSnapshotManager()?.cancelDebounce();
|
|
5624
|
+
try {
|
|
5625
|
+
getSnapshotManager()?.writeSnapshot(registry.listAll(), messageQueue.getHistorySnapshot());
|
|
5626
|
+
logger.info("Shutdown", "Final snapshot written.");
|
|
5627
|
+
} catch (error) {
|
|
5628
|
+
logger.error("Shutdown", "Failed to write final snapshot", error);
|
|
5629
|
+
}
|
|
5630
|
+
if (server.closeAllConnections) {
|
|
5631
|
+
server.closeAllConnections();
|
|
5632
|
+
}
|
|
5633
|
+
if (process.platform === "win32") {
|
|
5634
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
5635
|
+
}
|
|
5636
|
+
await new Promise((resolve4, reject) => {
|
|
5637
|
+
server.close((err) => err ? reject(err) : resolve4());
|
|
5638
|
+
});
|
|
5639
|
+
if (process.platform === "win32") {
|
|
5640
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
5641
|
+
}
|
|
5642
|
+
getSnapshotManager()?.removeShutdownMarker();
|
|
5643
|
+
try {
|
|
5644
|
+
unlinkSync4(pidPath);
|
|
5645
|
+
} catch {
|
|
5646
|
+
}
|
|
5647
|
+
logger.info("Shutdown", "Party Server shut down cleanly.");
|
|
5648
|
+
} catch (error) {
|
|
5649
|
+
logger.error("Shutdown", "Error during shutdown sequence", error);
|
|
5650
|
+
} finally {
|
|
5651
|
+
process.exit(0);
|
|
5652
|
+
}
|
|
5209
5653
|
}
|
|
5210
5654
|
async function main() {
|
|
5211
5655
|
const pidPath = pidFilePath2();
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5656
|
+
mkdirSync5(dirname4(pidPath), { recursive: true });
|
|
5657
|
+
writeFileSync4(pidPath, String(process.pid));
|
|
5658
|
+
initSnapshotManager(new SnapshotManager());
|
|
5659
|
+
const sm = getSnapshotManager();
|
|
5660
|
+
let recoveredAgents = 0;
|
|
5661
|
+
let recoveredHistoryEntries = 0;
|
|
5662
|
+
if (sm.hasShutdownMarker()) {
|
|
5663
|
+
logger.info("Recovery", "Previous shutdown was interrupted (shutdown marker found).");
|
|
5664
|
+
}
|
|
5665
|
+
const savedSnapshot = sm.loadSnapshot();
|
|
5666
|
+
if (savedSnapshot) {
|
|
5667
|
+
recoveredAgents = sm.hydrateAgents(registry, getSelfIp());
|
|
5668
|
+
recoveredHistoryEntries = sm.hydrateHistory(messageQueue);
|
|
5669
|
+
if (recoveredAgents > 0 || recoveredHistoryEntries > 0) {
|
|
5670
|
+
logger.info(
|
|
5671
|
+
"Recovery",
|
|
5672
|
+
`Recovered ${recoveredAgents} agent(s), ${recoveredHistoryEntries} history entry/entries.`
|
|
5673
|
+
);
|
|
5674
|
+
}
|
|
5675
|
+
}
|
|
5676
|
+
logger.info("Server", `Starting Party Server on port ${PARTY_PORT} (Tailscale IP: ${getSelfIp()})`);
|
|
5215
5677
|
process.on("SIGHUP", () => {
|
|
5216
5678
|
});
|
|
5217
5679
|
const server = serve({ fetch: app.fetch, port: PARTY_PORT });
|
|
5218
|
-
const discoveryPromise = discovery.runLoop();
|
|
5680
|
+
const discoveryPromise = discovery.runLoop(lifecycleController.signal);
|
|
5219
5681
|
const cleanupPromise = periodicCleanup();
|
|
5220
|
-
const
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5682
|
+
const snapshotLoopPromise = sm.startSnapshotLoop(
|
|
5683
|
+
lifecycleController.signal,
|
|
5684
|
+
() => registry.listAll(),
|
|
5685
|
+
() => messageQueue.getHistorySnapshot()
|
|
5686
|
+
);
|
|
5687
|
+
const shutdownHandler = () => void performShutdown(server, pidPath);
|
|
5688
|
+
process.on("SIGINT", shutdownHandler);
|
|
5689
|
+
process.on("SIGTERM", shutdownHandler);
|
|
5690
|
+
app.post("/api/shutdown", (c) => {
|
|
5691
|
+
void performShutdown(server, pidPath);
|
|
5692
|
+
return c.json({ status: "shutting_down" });
|
|
5693
|
+
});
|
|
5694
|
+
await Promise.race([discoveryPromise, cleanupPromise, snapshotLoopPromise]);
|
|
5232
5695
|
}
|
|
5233
|
-
var app;
|
|
5696
|
+
var app, shutdownInitiated;
|
|
5234
5697
|
var init_server = __esm({
|
|
5235
5698
|
"src/server/index.ts"() {
|
|
5236
5699
|
"use strict";
|
|
@@ -5238,24 +5701,46 @@ var init_server = __esm({
|
|
|
5238
5701
|
init_cors();
|
|
5239
5702
|
init_dist2();
|
|
5240
5703
|
init_config();
|
|
5704
|
+
init_persistence();
|
|
5705
|
+
init_logger();
|
|
5241
5706
|
init_state();
|
|
5242
5707
|
init_agent();
|
|
5243
5708
|
init_proxy();
|
|
5244
5709
|
init_dashboard();
|
|
5245
5710
|
app = new Hono2();
|
|
5246
5711
|
app.use("*", cors());
|
|
5712
|
+
app.use("*", async (c, next) => {
|
|
5713
|
+
const start = Date.now();
|
|
5714
|
+
await next();
|
|
5715
|
+
const ms = Date.now() - start;
|
|
5716
|
+
const ip = c.req.header("x-forwarded-for")?.split(",")[0]?.trim() ?? c.info?.remote?.address ?? "unknown";
|
|
5717
|
+
const path = c.req.path;
|
|
5718
|
+
if (path === "/proxy/health") {
|
|
5719
|
+
logger.debug("HTTP", `${c.req.method} ${path} ${c.res.status} ${ms}ms ${ip}`);
|
|
5720
|
+
} else {
|
|
5721
|
+
logger.info("HTTP", `${c.req.method} ${path} ${c.res.status} ${ms}ms ${ip}`);
|
|
5722
|
+
}
|
|
5723
|
+
});
|
|
5724
|
+
app.onError((err, c) => {
|
|
5725
|
+
logger.error("HTTP", `${c.req.method} ${c.req.path} failed (${err.status ?? 500}): ${err.message}`, err);
|
|
5726
|
+
return c.json({
|
|
5727
|
+
status: "error",
|
|
5728
|
+
error: (err.status ?? 500) >= 500 ? "Internal server error" : err.message
|
|
5729
|
+
}, err.status ?? 500);
|
|
5730
|
+
});
|
|
5247
5731
|
app.route("/agent", agentRoutes);
|
|
5248
5732
|
app.route("/proxy", proxyRoutes);
|
|
5249
5733
|
app.route("/dashboard", dashboardRoutes);
|
|
5734
|
+
shutdownInitiated = false;
|
|
5250
5735
|
main().catch((e) => {
|
|
5251
|
-
|
|
5736
|
+
logger.error("Server", "Fatal error", e);
|
|
5252
5737
|
process.exit(1);
|
|
5253
5738
|
});
|
|
5254
5739
|
process.on("uncaughtException", (e) => {
|
|
5255
|
-
|
|
5740
|
+
logger.error("Server", "Uncaught exception", e);
|
|
5256
5741
|
});
|
|
5257
5742
|
process.on("unhandledRejection", (e) => {
|
|
5258
|
-
|
|
5743
|
+
logger.error("Server", "Unhandled rejection", e);
|
|
5259
5744
|
});
|
|
5260
5745
|
}
|
|
5261
5746
|
});
|
|
@@ -5288,26 +5773,24 @@ function detectClaudeCode() {
|
|
|
5288
5773
|
configPath: settingsPath
|
|
5289
5774
|
};
|
|
5290
5775
|
}
|
|
5291
|
-
function
|
|
5292
|
-
const configPath =
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
configPath: join2(homedir(), ".gemini", "settings.json")
|
|
5307
|
-
};
|
|
5776
|
+
function detectOpenClaw() {
|
|
5777
|
+
const configPath = join2(homedir(), ".openclaw", "openclaw.json");
|
|
5778
|
+
if (isExecutableInPath("openclaw") || isExecutableInPath("openclaw.mjs")) {
|
|
5779
|
+
return { type: "openclaw", name: "OpenClaw", detected: true, configPath };
|
|
5780
|
+
}
|
|
5781
|
+
const candidatePaths = [
|
|
5782
|
+
join2(homedir(), ".openclaw", "openclaw.mjs"),
|
|
5783
|
+
join2(homedir(), ".openclaw", "openclaw")
|
|
5784
|
+
];
|
|
5785
|
+
for (const p of candidatePaths) {
|
|
5786
|
+
if (existsSync2(p)) {
|
|
5787
|
+
return { type: "openclaw", name: "OpenClaw", detected: true, configPath };
|
|
5788
|
+
}
|
|
5789
|
+
}
|
|
5790
|
+
return { type: "openclaw", name: "OpenClaw", detected: false, configPath };
|
|
5308
5791
|
}
|
|
5309
5792
|
function detectAgents() {
|
|
5310
|
-
return [detectClaudeCode(),
|
|
5793
|
+
return [detectClaudeCode(), detectOpenClaw()];
|
|
5311
5794
|
}
|
|
5312
5795
|
|
|
5313
5796
|
// src/cli/agent-installer.ts
|
|
@@ -5343,7 +5826,8 @@ function findPluginDistDir() {
|
|
|
5343
5826
|
if (existsSync3(join3(pluginDir, ".claude-plugin", "plugin.json"))) {
|
|
5344
5827
|
return pluginDir;
|
|
5345
5828
|
}
|
|
5346
|
-
} catch {
|
|
5829
|
+
} catch (error) {
|
|
5830
|
+
console.error("[Agent Installer] Failed to list plugin dist directory:", error instanceof Error ? error.message : String(error));
|
|
5347
5831
|
}
|
|
5348
5832
|
return null;
|
|
5349
5833
|
}
|
|
@@ -5362,25 +5846,26 @@ function findDistJsDir() {
|
|
|
5362
5846
|
function getPluginVersion() {
|
|
5363
5847
|
const pluginDir = findPluginDistDir();
|
|
5364
5848
|
if (pluginDir) {
|
|
5365
|
-
const
|
|
5849
|
+
const manifest = readJsonFile(
|
|
5366
5850
|
join3(pluginDir, ".claude-plugin", "plugin.json"),
|
|
5367
5851
|
{}
|
|
5368
5852
|
);
|
|
5369
|
-
if (
|
|
5370
|
-
}
|
|
5371
|
-
const
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5853
|
+
if (manifest.version) return manifest.version;
|
|
5854
|
+
}
|
|
5855
|
+
const distJsDir = findDistJsDir();
|
|
5856
|
+
if (distJsDir) {
|
|
5857
|
+
const buildInfo = readJsonFile(
|
|
5858
|
+
join3(distJsDir, "..", "BUILD_INFO.json"),
|
|
5859
|
+
{}
|
|
5860
|
+
);
|
|
5861
|
+
if (buildInfo.version) return buildInfo.version;
|
|
5862
|
+
}
|
|
5863
|
+
try {
|
|
5864
|
+
const pkg = JSON.parse(readFileSync(join3(import.meta.dirname ?? ".", "..", "..", "package.json"), "utf-8"));
|
|
5865
|
+
if (pkg.version) return pkg.version;
|
|
5866
|
+
} catch {
|
|
5867
|
+
}
|
|
5868
|
+
return "0.0.0";
|
|
5384
5869
|
}
|
|
5385
5870
|
function getMarketplaceDir() {
|
|
5386
5871
|
return join3(homedir2(), ".claude", "plugins", "marketplaces", "open-party");
|
|
@@ -5506,42 +5991,56 @@ function installClaudeCode() {
|
|
|
5506
5991
|
configPath: settingsPath
|
|
5507
5992
|
};
|
|
5508
5993
|
}
|
|
5509
|
-
function
|
|
5510
|
-
const
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5994
|
+
function findOpenclawDistDir() {
|
|
5995
|
+
const distDir = resolve(import.meta.dirname ?? ".", "..", "openclaw");
|
|
5996
|
+
if (!existsSync3(distDir)) return null;
|
|
5997
|
+
try {
|
|
5998
|
+
const entries = readdirSync(distDir);
|
|
5999
|
+
const dirs = entries.filter((e) => e.startsWith("open-party-"));
|
|
6000
|
+
if (dirs.length === 0) return null;
|
|
6001
|
+
return join3(distDir, dirs[dirs.length - 1]);
|
|
6002
|
+
} catch (error) {
|
|
6003
|
+
console.error("[Agent Installer] Failed to list openclaw dist directory:", error instanceof Error ? error.message : String(error));
|
|
6004
|
+
}
|
|
6005
|
+
return null;
|
|
5517
6006
|
}
|
|
5518
|
-
function
|
|
5519
|
-
const
|
|
5520
|
-
|
|
6007
|
+
function installOpenClaw() {
|
|
6008
|
+
const pluginDir = findOpenclawDistDir();
|
|
6009
|
+
if (!pluginDir) {
|
|
6010
|
+
return {
|
|
6011
|
+
success: false,
|
|
6012
|
+
error: 'OpenClaw plugin package not found. Run "npm run build:openclaw" first.'
|
|
6013
|
+
};
|
|
6014
|
+
}
|
|
6015
|
+
const configPath = join3(homedir2(), ".openclaw", "openclaw.json");
|
|
6016
|
+
const extensionDir = join3(homedir2(), ".openclaw", "extensions", "open-party");
|
|
6017
|
+
if (existsSync3(extensionDir)) {
|
|
6018
|
+
rmSync(extensionDir, { recursive: true });
|
|
6019
|
+
}
|
|
6020
|
+
mkdirSync(extensionDir, { recursive: true });
|
|
6021
|
+
cpSync(pluginDir, extensionDir, { recursive: true });
|
|
5521
6022
|
const config = readJsonFile(configPath, {});
|
|
5522
|
-
if (!config.
|
|
5523
|
-
config.
|
|
6023
|
+
if (!config.plugins) config.plugins = {};
|
|
6024
|
+
if (!config.plugins.entries) {
|
|
6025
|
+
config.plugins.entries = {};
|
|
6026
|
+
}
|
|
6027
|
+
const entries = config.plugins.entries;
|
|
6028
|
+
entries["open-party"] = {
|
|
6029
|
+
enabled: true,
|
|
6030
|
+
config: {
|
|
6031
|
+
partyServerUrl: "http://127.0.0.1:8000",
|
|
6032
|
+
heartbeatInterval: 3e4
|
|
6033
|
+
}
|
|
6034
|
+
};
|
|
5524
6035
|
writeJsonFile(configPath, config);
|
|
5525
6036
|
return { success: true, configPath };
|
|
5526
6037
|
}
|
|
5527
|
-
function getPluginCommand() {
|
|
5528
|
-
const distDir = findDistJsDir();
|
|
5529
|
-
if (distDir) {
|
|
5530
|
-
const serverPath = join3(distDir, "mcp-server.js");
|
|
5531
|
-
if (existsSync3(serverPath)) {
|
|
5532
|
-
return { command: "node", args: [serverPath] };
|
|
5533
|
-
}
|
|
5534
|
-
}
|
|
5535
|
-
return { command: "npx", args: ["@feynmanzhang/open-party", "mcp"] };
|
|
5536
|
-
}
|
|
5537
6038
|
async function installPluginToAgent(agentType) {
|
|
5538
6039
|
switch (agentType) {
|
|
5539
6040
|
case "claude-code":
|
|
5540
6041
|
return installClaudeCode();
|
|
5541
|
-
case "
|
|
5542
|
-
return
|
|
5543
|
-
case "gemini-cli":
|
|
5544
|
-
return installGeminiCli();
|
|
6042
|
+
case "openclaw":
|
|
6043
|
+
return installOpenClaw();
|
|
5545
6044
|
default:
|
|
5546
6045
|
return { success: false, error: `Unknown agent type: ${agentType}` };
|
|
5547
6046
|
}
|
|
@@ -5884,7 +6383,7 @@ ${bold(cyan("\u{1F50D} Step 2: Detecting AI agents in your environment..."))}
|
|
|
5884
6383
|
const detected = agents.filter((a) => a.detected);
|
|
5885
6384
|
if (detected.length === 0) {
|
|
5886
6385
|
console.log(yellow("No supported AI agents detected in this environment."));
|
|
5887
|
-
console.log(" Supported agents: Claude Code,
|
|
6386
|
+
console.log(" Supported agents: Claude Code, OpenClaw");
|
|
5888
6387
|
console.log("");
|
|
5889
6388
|
console.log(" Install one and re-run: open-party setup");
|
|
5890
6389
|
return;
|
|
@@ -5914,6 +6413,9 @@ Installing Open Party plugin for ${agent.name}...`);
|
|
|
5914
6413
|
if (agent.type === "claude-code") {
|
|
5915
6414
|
console.log(` ${bold("Please restart Claude Code")} for changes to take effect.`);
|
|
5916
6415
|
}
|
|
6416
|
+
if (agent.type === "openclaw") {
|
|
6417
|
+
console.log(` ${bold("Please restart OpenClaw gateway")} for changes to take effect.`);
|
|
6418
|
+
}
|
|
5917
6419
|
} else {
|
|
5918
6420
|
console.log(`${red("\u274C")} Failed to install for ${agent.name}: ${result.error}`);
|
|
5919
6421
|
}
|
|
@@ -5927,9 +6429,9 @@ async function setupCommand() {
|
|
|
5927
6429
|
console.log(`
|
|
5928
6430
|
${bold(cyan("\u{1F680} Starting Party Server..."))}`);
|
|
5929
6431
|
const { spawn: spawn4 } = await import("child_process");
|
|
5930
|
-
const { resolve: resolve4, dirname:
|
|
6432
|
+
const { resolve: resolve4, dirname: dirname6 } = await import("path");
|
|
5931
6433
|
const { fileURLToPath: fileURLToPath3 } = await import("url");
|
|
5932
|
-
const __dirname2 =
|
|
6434
|
+
const __dirname2 = dirname6(fileURLToPath3(import.meta.url));
|
|
5933
6435
|
const serverScript = resolve4(__dirname2, "..", "party-server.js");
|
|
5934
6436
|
const serverProc = spawn4(process.execPath, [serverScript], {
|
|
5935
6437
|
detached: true,
|
|
@@ -6048,12 +6550,12 @@ function removePidFile() {
|
|
|
6048
6550
|
function isProcessRunning(pid) {
|
|
6049
6551
|
if (process.platform === "win32") {
|
|
6050
6552
|
try {
|
|
6051
|
-
const
|
|
6553
|
+
const output2 = execSync3(`tasklist /FI "PID eq ${pid}" /NH`, {
|
|
6052
6554
|
encoding: "utf-8",
|
|
6053
6555
|
windowsHide: true,
|
|
6054
6556
|
stdio: ["pipe", "pipe", "pipe"]
|
|
6055
6557
|
});
|
|
6056
|
-
return
|
|
6558
|
+
return output2.includes(String(pid));
|
|
6057
6559
|
} catch {
|
|
6058
6560
|
return false;
|
|
6059
6561
|
}
|
|
@@ -6136,7 +6638,28 @@ function killServer(pid) {
|
|
|
6136
6638
|
} else {
|
|
6137
6639
|
process.kill(pid, "SIGTERM");
|
|
6138
6640
|
}
|
|
6641
|
+
} catch (error) {
|
|
6642
|
+
void error;
|
|
6643
|
+
}
|
|
6644
|
+
}
|
|
6645
|
+
function findPidByPort(port) {
|
|
6646
|
+
try {
|
|
6647
|
+
if (process.platform === "win32") {
|
|
6648
|
+
const output3 = execSync3(
|
|
6649
|
+
`netstat -ano -p tcp | findstr "LISTENING" | findstr ":${port} "`,
|
|
6650
|
+
{ encoding: "utf-8", windowsHide: true, stdio: ["pipe", "pipe", "pipe"] }
|
|
6651
|
+
);
|
|
6652
|
+
const match2 = output3.trim().match(/\s(\d+)\s*$/);
|
|
6653
|
+
return match2 ? parseInt(match2[1], 10) : null;
|
|
6654
|
+
}
|
|
6655
|
+
const output2 = execSync3(`lsof -t -i :${port} -sTCP:LISTEN`, {
|
|
6656
|
+
encoding: "utf-8",
|
|
6657
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
6658
|
+
});
|
|
6659
|
+
const pid = parseInt(output2.trim().split("\n")[0], 10);
|
|
6660
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
6139
6661
|
} catch {
|
|
6662
|
+
return null;
|
|
6140
6663
|
}
|
|
6141
6664
|
}
|
|
6142
6665
|
function parseStartArgs(args2) {
|
|
@@ -6204,14 +6727,48 @@ async function startDaemon(port) {
|
|
|
6204
6727
|
}
|
|
6205
6728
|
|
|
6206
6729
|
// src/cli/stop-server.ts
|
|
6730
|
+
async function stopServerGracefully(pid, port) {
|
|
6731
|
+
try {
|
|
6732
|
+
const controller = new AbortController();
|
|
6733
|
+
const timer = setTimeout(() => controller.abort(), 8e3);
|
|
6734
|
+
const resp = await fetch(`http://127.0.0.1:${port}/api/shutdown`, {
|
|
6735
|
+
method: "POST",
|
|
6736
|
+
signal: controller.signal
|
|
6737
|
+
});
|
|
6738
|
+
clearTimeout(timer);
|
|
6739
|
+
return resp.ok;
|
|
6740
|
+
} catch {
|
|
6741
|
+
return false;
|
|
6742
|
+
}
|
|
6743
|
+
}
|
|
6744
|
+
async function waitForProcessExit(pid, timeoutMs = 1e4) {
|
|
6745
|
+
const deadline = Date.now() + timeoutMs;
|
|
6746
|
+
while (Date.now() < deadline) {
|
|
6747
|
+
if (!isProcessRunning(pid)) return true;
|
|
6748
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
6749
|
+
}
|
|
6750
|
+
return false;
|
|
6751
|
+
}
|
|
6207
6752
|
async function stopServer() {
|
|
6208
6753
|
const pid = readPid();
|
|
6754
|
+
const port = resolvePort();
|
|
6209
6755
|
if (pid === null) {
|
|
6210
|
-
const
|
|
6211
|
-
const healthy = await isServerHealthy(port2);
|
|
6756
|
+
const healthy = await isServerHealthy(port);
|
|
6212
6757
|
if (healthy) {
|
|
6213
|
-
|
|
6214
|
-
|
|
6758
|
+
const foundPid = findPidByPort(port);
|
|
6759
|
+
if (foundPid !== null && isProcessRunning(foundPid)) {
|
|
6760
|
+
console.log(`No PID file found, but a server is responding on port ${port} (PID ${foundPid}).`);
|
|
6761
|
+
console.log(`Stopping by port lookup...`);
|
|
6762
|
+
killServer(foundPid);
|
|
6763
|
+
const stillUp2 = await isServerHealthy(port);
|
|
6764
|
+
if (!stillUp2) {
|
|
6765
|
+
console.log("Party Server stopped.");
|
|
6766
|
+
return;
|
|
6767
|
+
}
|
|
6768
|
+
console.warn(`Process ${foundPid} was killed, but port ${port} is still responding.`);
|
|
6769
|
+
} else {
|
|
6770
|
+
console.log(`No PID file found, and no process found on port ${port}.`);
|
|
6771
|
+
}
|
|
6215
6772
|
} else {
|
|
6216
6773
|
console.log("Party Server is not running (no PID file found).");
|
|
6217
6774
|
}
|
|
@@ -6223,9 +6780,18 @@ async function stopServer() {
|
|
|
6223
6780
|
return;
|
|
6224
6781
|
}
|
|
6225
6782
|
console.log(`Stopping Party Server (PID ${pid})...`);
|
|
6783
|
+
const gracefulOk = await stopServerGracefully(pid, port);
|
|
6784
|
+
if (gracefulOk) {
|
|
6785
|
+
const exited = await waitForProcessExit(pid);
|
|
6786
|
+
if (exited) {
|
|
6787
|
+
removePidFile();
|
|
6788
|
+
console.log("Party Server stopped gracefully.");
|
|
6789
|
+
return;
|
|
6790
|
+
}
|
|
6791
|
+
console.warn("Graceful shutdown timed out, falling back to force kill...");
|
|
6792
|
+
}
|
|
6226
6793
|
killServer(pid);
|
|
6227
6794
|
removePidFile();
|
|
6228
|
-
const port = resolvePort();
|
|
6229
6795
|
const stillUp = await isServerHealthy(port);
|
|
6230
6796
|
if (stillUp) {
|
|
6231
6797
|
console.warn(`Process ${pid} was killed, but port ${port} is still responding.`);
|
|
@@ -6284,12 +6850,12 @@ async function statusCommand() {
|
|
|
6284
6850
|
}
|
|
6285
6851
|
|
|
6286
6852
|
// src/cli/version.ts
|
|
6287
|
-
import { readFileSync as
|
|
6288
|
-
import { resolve as resolve3, dirname as
|
|
6853
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
6854
|
+
import { resolve as resolve3, dirname as dirname5 } from "path";
|
|
6289
6855
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6290
6856
|
function showVersion() {
|
|
6291
|
-
const pkgPath = resolve3(
|
|
6292
|
-
const pkg = JSON.parse(
|
|
6857
|
+
const pkgPath = resolve3(dirname5(fileURLToPath2(import.meta.url)), "..", "..", "package.json");
|
|
6858
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
6293
6859
|
console.log(`open-party v${pkg.version}`);
|
|
6294
6860
|
}
|
|
6295
6861
|
|