fix(plugin): lift calendarScheduler to module scope (multi-register singleton)
Trying the prior multi-agent-handle fix in dind-t2 surfaced a second bug that PR #7 didn't reach: `harborforge_calendar_status` still returned `Calendar scheduler not running` even though the gateway log showed the scheduler had started 30+ seconds before the agent's call. ## Root cause `register()` is invoked once per agent — `grep -c "HarborForge plugin registered" /tmp/gw-stdout.log` reports 5 for a 5-agent claw. Every invocation creates its own `let calendarScheduler` closure binding. But `gateway_start` fires once and we only call `startCalendarScheduler()` through that single hook, so exactly one of the five closures sees the handle and the other four keep their bindings at `null`. The host's tool router picks one of the five duplicate `harborforge_calendar_status` registrations to dispatch to — most of the time it's one of the four "null" closures, which is why every wakeup the agent saw `Calendar scheduler not running`. ## Fix Lift `let calendarScheduler` out of `register()` and into module scope. All five register-call closures now reference the same binding; once the single `gateway_start` initialises it, every tool sees it. `startCalendarScheduler()` now early-returns when `calendarScheduler` is already set, so duplicate `gateway_start` firings (if the host ever does that) don't double-install intervals. Bumps version 0.3.2 → 0.3.3. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,25 @@ import {
|
||||
CalendarScheduler,
|
||||
} from './calendar/index.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Module-scope calendar scheduler singleton.
|
||||
//
|
||||
// `register()` is called multiple times per gateway boot — once per agent
|
||||
// (we see 5 `HarborForge plugin registered` lines for 5 agents on dind-t2).
|
||||
// `gateway_start` only fires once, so before this lift the
|
||||
// `startCalendarScheduler()` setup ran inside ONE closure while four other
|
||||
// closures kept their own `calendarScheduler = null`. Whichever of the five
|
||||
// tool registrations the gateway picked at call time was effectively a coin
|
||||
// flip, and four times out of five `harborforge_calendar_status` returned
|
||||
// `Calendar scheduler not running` even though the scheduler was active.
|
||||
//
|
||||
// Keeping the singleton at module scope removes the per-`register()` shadow:
|
||||
// the scheduler is started once, every closure reads the same binding, and
|
||||
// `startCalendarScheduler()` is idempotent so duplicate `gateway_start`
|
||||
// firings are harmless.
|
||||
// ---------------------------------------------------------------------------
|
||||
let calendarScheduler: MultiAgentSchedulerHandle | CalendarScheduler | null = null;
|
||||
|
||||
interface PluginAPI {
|
||||
logger: {
|
||||
info: (...args: any[]) => void;
|
||||
@@ -108,7 +127,7 @@ function register(api: PluginAPI): void {
|
||||
},
|
||||
openclaw: {
|
||||
version: api.runtime?.version || api.version || 'unknown',
|
||||
pluginVersion: '0.3.2', // Bumped for PLG-CAL-004
|
||||
pluginVersion: '0.3.3', // Bumped for PLG-CAL-004
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
@@ -117,13 +136,9 @@ function register(api: PluginAPI): void {
|
||||
// Periodic metadata push interval handle
|
||||
let metaPushInterval: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
// Calendar scheduler instance.
|
||||
//
|
||||
// In multi-agent sync mode (the only path today) this is a
|
||||
// {@link MultiAgentSchedulerHandle}. The legacy `CalendarScheduler` type
|
||||
// is retained in the union for compatibility with the typed-only single-
|
||||
// agent path that may be reintroduced later.
|
||||
let calendarScheduler: MultiAgentSchedulerHandle | CalendarScheduler | null = null;
|
||||
// (calendarScheduler is module-scope — see top of file for the why.
|
||||
// Tools and lifecycle hooks all reference the same binding so the
|
||||
// multi-register/single-start mismatch can't shadow them again.)
|
||||
|
||||
/**
|
||||
* Push OpenClaw metadata to the Monitor bridge.
|
||||
@@ -147,7 +162,7 @@ function register(api: PluginAPI): void {
|
||||
|
||||
const meta: OpenClawMeta = {
|
||||
version: api.runtime?.version || api.version || 'unknown',
|
||||
plugin_version: '0.3.2',
|
||||
plugin_version: '0.3.3',
|
||||
agents: agentNames.map(name => ({ name })),
|
||||
};
|
||||
|
||||
@@ -291,8 +306,16 @@ function register(api: PluginAPI): void {
|
||||
|
||||
/**
|
||||
* Initialize and start the calendar scheduler.
|
||||
*
|
||||
* Idempotent — `gateway_start` may fire once per `register()` invocation
|
||||
* (the host calls `register` per agent), and we only want one set of
|
||||
* sync/check intervals across the whole process.
|
||||
*/
|
||||
function startCalendarScheduler(): void {
|
||||
if (calendarScheduler) {
|
||||
logger.info('Calendar scheduler already started, skipping duplicate gateway_start');
|
||||
return;
|
||||
}
|
||||
const live = resolveConfig();
|
||||
|
||||
// Create bridge client (claw-instance level, not per-agent)
|
||||
|
||||
Reference in New Issue
Block a user