Merge pull request 'fix: lift calendarScheduler to module scope (multi-register singleton)' (#8) from fix/scheduler-module-singleton into main

This commit is contained in:
h z
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,
} 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)

View File

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