From 0a25568b234e48bce7c5c7c9e77c8a1caf7a119d Mon Sep 17 00:00:00 2001 From: Toby Date: Mon, 3 Aug 2026 22:15:52 +0000 Subject: [PATCH] fix(pi): use resolved config + auto-recover from offline status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs caused the extension to report 'hexus: offline': 1. Config captured before async load: getConfig() returns DEFAULTS until initConfig() resolves, and the old code froze `const config` at init — real config.json values (apiUrl, agentIdentity, recallLimit, ...) were silently ignored for the whole session. Capture as `let config` and swap in the resolved config when the async load completes. 2. Sticky offline status: if /api/health failed at session_start, nothing re-polled until the next user turn, so the status stayed 'hexus: offline' even after the server came back. Add scheduleHealthRetry() which re-checks every 10s while offline and clears itself on reconnect (and on session_shutdown). --- pi-extension/index.ts | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/pi-extension/index.ts b/pi-extension/index.ts index d636255..5b19883 100644 --- a/pi-extension/index.ts +++ b/pi-extension/index.ts @@ -136,9 +136,12 @@ Empty array if nothing worth remembering. Only output JSON.`; export default function hexus(pi: ExtensionAPI) { // Bootstrap config asynchronously — initConfig() runs once and caches. - // getConfig() returns defaults until the promise resolves, then returns real config. - initConfig().catch((err) => console.warn("hexus: config load failed:", err)); - const config = getConfig(); + // getConfig() returns defaults until the promise resolves, so capture the + // resolved config once it's available instead of freezing the defaults. + let config = getConfig(); + initConfig() + .then((cfg) => { config = cfg; }) + .catch((err) => console.warn("hexus: config load failed:", err)); const reflConfig = getReflectionConfig(); // State @@ -146,6 +149,7 @@ export default function hexus(pi: ExtensionAPI) { let turnsSinceReflection = 0; let lastReflectionTurn = 0; let idleTimer: ReturnType | null = null; + let healthRetryTimer: ReturnType | null = null; let isReflecting = false; let recallInFlight = false; // Dedupe concurrent recall calls @@ -244,6 +248,25 @@ export default function hexus(pi: ExtensionAPI) { }, reflConfig.idleSeconds * 1000); } + // Re-check /api/health every few seconds while offline so the status + // recovers automatically once the server is reachable again. + function scheduleHealthRetry(ctx: ExtensionAPI): void { + if (healthRetryTimer) clearTimeout(healthRetryTimer); + healthRetryTimer = setTimeout(async () => { + healthRetryTimer = null; + const client = getClient(); + const health = await client.health(true).catch(() => null); + if (health?.status === "ok") { + ctx.ui.notify(`hexus connected (${health.row_counts.memory_entries} memories)`, "info"); + ctx.ui.setStatus("hexus", `hexus: ${health.row_counts.memory_entries} memories`); + if (reflConfig.enabled) scheduleReflection(ctx); + } else { + ctx.ui.setStatus("hexus", "hexus: offline"); + scheduleHealthRetry(ctx); + } + }, 10_000); + } + // ------------------------------------------------------------------------- // Events // ------------------------------------------------------------------------- @@ -261,6 +284,9 @@ export default function hexus(pi: ExtensionAPI) { } else { ctx.ui.notify("hexus: connection failed", "warning"); ctx.ui.setStatus("hexus", "hexus: offline"); + // Keep polling so the status recovers without waiting for the next turn + // (or a user prompt) once the server is reachable again. + scheduleHealthRetry(ctx); } }); @@ -426,6 +452,7 @@ export default function hexus(pi: ExtensionAPI) { pi.on("session_shutdown", () => { if (idleTimer) { clearTimeout(idleTimer); idleTimer = null; } + if (healthRetryTimer) { clearTimeout(healthRetryTimer); healthRetryTimer = null; } // Reset in-flight state so a new session starts clean isReflecting = false; recallInFlight = false;