feat(FN-3040): make peer exchange shutdown deterministic and improve dashbo

This merge lands seven features and fixes across the dashboard and engine. Notable changes: restructured TodoView rows with improved action row styling, added `/clear` command to Chat and QuickChat, persisted session banner dismissals with a hide-banner setting, made peer exchange shutdown determini

Fusion-Task-Id: FN-3040
This commit is contained in:
Fusion
2026-05-01 13:32:23 -07:00
committed by gsxdsm
parent 58d6dfbc14
commit 70ed2a9494
7 changed files with 143 additions and 7 deletions

View File

@@ -48,7 +48,7 @@ export class PeerExchangeService {
private syncIntervalMs: number;
private interval: ReturnType<typeof setInterval> | null = null;
private activeSync: Promise<void> | null = null;
private stopped = false;
private running = false;
/** Whether settings sync is enabled. Default: false. */
private settingsSyncEnabled: boolean;
/** Minimum interval between settings syncs with the same node in ms. Default: 5 minutes. */
@@ -94,11 +94,13 @@ export class PeerExchangeService {
* Begins periodic gossip with all online remote nodes.
*/
start(): void {
if (this.stopped) {
peerExchangeLog.warn("Cannot start - service has been stopped");
if (this.running) {
peerExchangeLog.log("Peer exchange service already running");
return;
}
this.running = true;
// Get initial peer count for logging (async call)
this.centralCore.listNodes().then((nodes) => {
const onlineRemoteCount = nodes.filter(
@@ -112,6 +114,7 @@ export class PeerExchangeService {
// Start periodic sync
this.interval = setInterval(() => {
if (!this.running) return;
void this.syncWithAllPeers();
}, this.syncIntervalMs);
}
@@ -120,13 +123,28 @@ export class PeerExchangeService {
* Stop the peer exchange service.
* Clears the sync interval and prevents further syncs.
*/
stop(): void {
async stop(): Promise<void> {
if (!this.running) {
return;
}
this.running = false;
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
this.stopped = true;
// If a sync cycle is already in flight, wait for it to settle so shutdown
// does not leave partially completed gossip work behind.
if (this.activeSync) {
try {
await this.activeSync;
} catch {
// best-effort shutdown; sync errors are already logged by run loop
}
}
peerExchangeLog.log("Stopped peer exchange service");
}

View File

@@ -48,6 +48,12 @@ import { TriageProcessor } from "../triage.js";
* - Graceful shutdown with configurable timeout
* - Automatic orphaned task recovery on startup
*
* Lifecycle boundary:
* - Mesh networking services (PeerExchangeService + mDNS discovery) are process-level
* concerns owned by CLI startup paths (`runServe`/`runDashboard`) because discovery
* requires the final bound HTTP port. InProcessRuntime remains project-scoped and
* intentionally does not start process-level mesh services.
*
* @example
* ```typescript
* const config: ProjectRuntimeConfig = {