fix: lift calendarScheduler to module scope (multi-register singleton) #8

Merged
hzhang merged 1 commits from fix/scheduler-module-singleton into main 2026-05-21 09:54:55 +00:00
2 changed files with 33 additions and 10 deletions

View File

@@ -25,6 +25,25 @@ import {
CalendarScheduler, CalendarScheduler,
} from './calendar/index.js'; } 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 { interface PluginAPI {
logger: { logger: {
info: (...args: any[]) => void; info: (...args: any[]) => void;
@@ -108,7 +127,7 @@ function register(api: PluginAPI): void {
}, },
openclaw: { openclaw: {
version: api.runtime?.version || api.version || 'unknown', 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(), timestamp: new Date().toISOString(),
}; };
@@ -117,13 +136,9 @@ function register(api: PluginAPI): void {
// Periodic metadata push interval handle // Periodic metadata push interval handle
let metaPushInterval: ReturnType<typeof setInterval> | null = null; let metaPushInterval: ReturnType<typeof setInterval> | null = null;
// Calendar scheduler instance. // (calendarScheduler is module-scope — see top of file for the why.
// // Tools and lifecycle hooks all reference the same binding so the
// In multi-agent sync mode (the only path today) this is a // multi-register/single-start mismatch can't shadow them again.)
// {@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;
/** /**
* Push OpenClaw metadata to the Monitor bridge. * Push OpenClaw metadata to the Monitor bridge.
@@ -147,7 +162,7 @@ function register(api: PluginAPI): void {
const meta: OpenClawMeta = { const meta: OpenClawMeta = {
version: api.runtime?.version || api.version || 'unknown', version: api.runtime?.version || api.version || 'unknown',
plugin_version: '0.3.2', plugin_version: '0.3.3',
agents: agentNames.map(name => ({ name })), agents: agentNames.map(name => ({ name })),
}; };
@@ -291,8 +306,16 @@ function register(api: PluginAPI): void {
/** /**
* Initialize and start the calendar scheduler. * 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 { function startCalendarScheduler(): void {
if (calendarScheduler) {
logger.info('Calendar scheduler already started, skipping duplicate gateway_start');
return;
}
const live = resolveConfig(); const live = resolveConfig();
// Create bridge client (claw-instance level, not per-agent) // Create bridge client (claw-instance level, not per-agent)

View File

@@ -1,6 +1,6 @@
{ {
"name": "harbor-forge-plugin", "name": "harbor-forge-plugin",
"version": "0.3.2", "version": "0.3.3",
"description": "OpenClaw plugin for HarborForge monitor bridge and CLI integration", "description": "OpenClaw plugin for HarborForge monitor bridge and CLI integration",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",