From 709f7e09abfef5a6e7c77437ca72ce14bae5e21a Mon Sep 17 00:00:00 2001 From: hzhang Date: Sat, 23 May 2026 11:31:27 +0100 Subject: [PATCH] feat(hf-plugin): expose globalThis.__hfAgentStatus.get(agentId) Cross-plugin agent-status accessor for use by Fabric.OpenclawPlugin's presence-sync loop (and any future plugin needing 'is agent X busy right now'). Backed by CalendarBridgeClient.getAgentStatus() with a 30s in-memory TTL cache to avoid hammering the HF backend. Returns one of 'idle' | 'on_call' | 'busy' | 'exhausted' | 'offline' or undefined when the agent isn't known to HF. Cache miss + bridge failure returns the last cached value (stale-data better than no data for delivery-decision use cases). Part of DIALECTIC-V2 Phase 1 (Fabric announce channel + busy-discard). See /home/hzhang/arch/DIALECTIC-V2-DESIGN.md sections 7+8. --- plugin/index.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/plugin/index.ts b/plugin/index.ts index 3fe19f4..c1cc19a 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -396,6 +396,36 @@ function register(api: PluginAPI): void { } } + // Cross-plugin exposure: agent status lookup for other plugins + // (currently Fabric.OpenclawPlugin uses this to skip delivering + // `announce` channel messages to busy agents — see DIALECTIC-V2 + // design doc, Phase 1). Backed by calendarBridge.getAgentStatus + // with a small TTL cache to avoid hammering the HF backend. + type HfStatus = 'idle' | 'on_call' | 'busy' | 'exhausted' | 'offline'; + const HF_STATUS_CACHE_TTL_MS = 30_000; + const hfStatusCache = new Map(); + const _G = globalThis as Record; + _G['__hfAgentStatus'] = { + async get(agentId: string): Promise { + if (!agentId) return undefined; + const cached = hfStatusCache.get(agentId); + if (cached && Date.now() - cached.at < HF_STATUS_CACHE_TTL_MS) { + return cached.status; + } + try { + const status = await calendarBridge.getAgentStatus(agentId); + if (status) { + const typed = status as HfStatus; + hfStatusCache.set(agentId, { status: typed, at: Date.now() }); + return typed; + } + } catch { + /* fall through to cached-or-undefined */ + } + return cached?.status; + }, + }; + // Track wakes already dispatched for a slot in the current sync // window — the simplified inline scheduler does not PATCH slot // status server-side, so without dedupe the check loop re-wakes