Compare commits
4 Commits
ec09578de3
...
917cb344cf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
917cb344cf | ||
|
|
64a9c431bf | ||
| 957bcbb4a8 | |||
|
|
9195dc6bd1 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,3 +4,5 @@ plugin/**/*.js
|
||||
plugin/**/*.js.map
|
||||
plugin/**/*.d.ts
|
||||
plugin/**/*.d.ts.map
|
||||
# Hand-written ambient declarations are tracked; only compiled .d.ts above is ignored.
|
||||
!plugin/openclaw-sdk.d.ts
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
CalendarSlotResponse,
|
||||
SlotAgentUpdate,
|
||||
SlotStatus,
|
||||
} from './types';
|
||||
} from './types.js';
|
||||
|
||||
export interface CalendarBridgeConfig {
|
||||
/** HarborForge backend base URL (e.g. "https://monitor.hangman-lab.top") */
|
||||
@@ -292,7 +292,7 @@ export class CalendarBridgeClient {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
import { hostname } from 'os';
|
||||
import { getPluginConfig } from '../core/config';
|
||||
import { getPluginConfig } from '../core/config.js';
|
||||
|
||||
export interface CalendarPluginConfig {
|
||||
/** Backend URL for calendar API (overrides monitor backendUrl) */
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* • AgentWakeContext — context passed to agent when waking
|
||||
*
|
||||
* Usage in plugin/index.ts:
|
||||
* import { createCalendarBridgeClient, createCalendarScheduler } from './calendar';
|
||||
* import { createCalendarBridgeClient, createCalendarScheduler } from './calendar.js';
|
||||
*
|
||||
* const agentId = process.env.AGENT_ID || 'unknown';
|
||||
* const calendar = createCalendarBridgeClient(api, 'https://monitor.hangman-lab.top', agentId);
|
||||
@@ -28,7 +28,7 @@
|
||||
* scheduler.start();
|
||||
*/
|
||||
|
||||
export * from './types';
|
||||
export * from './calendar-bridge';
|
||||
export * from './scheduler';
|
||||
export * from './schedule-cache';
|
||||
export * from './types.js';
|
||||
export * from './calendar-bridge.js';
|
||||
export * from './scheduler.js';
|
||||
export * from './schedule-cache.js';
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
|
||||
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { CalendarBridgeClient } from './calendar-bridge';
|
||||
import { ScheduleCache } from './schedule-cache';
|
||||
import { CalendarBridgeClient } from './calendar-bridge.js';
|
||||
import {
|
||||
CalendarSlotResponse,
|
||||
SlotStatus,
|
||||
@@ -27,7 +26,7 @@ import {
|
||||
SlotAgentUpdate,
|
||||
CalendarEventDataJob,
|
||||
CalendarEventDataSystemEvent,
|
||||
} from './types';
|
||||
} from './types.js';
|
||||
|
||||
export interface CalendarSchedulerConfig {
|
||||
/** Calendar bridge client for backend communication */
|
||||
@@ -45,8 +44,6 @@ export interface CalendarSchedulerConfig {
|
||||
};
|
||||
/** Heartbeat interval in milliseconds (default: 60000) */
|
||||
heartbeatIntervalMs?: number;
|
||||
/** Schedule sync interval in milliseconds (default: 300000 = 5 min) */
|
||||
syncIntervalMs?: number;
|
||||
/** Enable verbose debug logging */
|
||||
debug?: boolean;
|
||||
/** Directory for state persistence (default: plugin data dir) */
|
||||
@@ -98,10 +95,8 @@ interface SchedulerState {
|
||||
currentSlot: CalendarSlotResponse | null;
|
||||
/** Last heartbeat timestamp */
|
||||
lastHeartbeatAt: Date | null;
|
||||
/** Heartbeat interval handle */
|
||||
/** Interval handle for cleanup */
|
||||
intervalHandle: ReturnType<typeof setInterval> | null;
|
||||
/** Schedule sync interval handle */
|
||||
syncIntervalHandle: ReturnType<typeof setInterval> | null;
|
||||
/** Set of slot IDs that have been deferred in current session */
|
||||
deferredSlotIds: Set<string>;
|
||||
/** Whether agent is currently processing a slot */
|
||||
@@ -122,13 +117,10 @@ export class CalendarScheduler {
|
||||
private config: Required<CalendarSchedulerConfig>;
|
||||
private state: SchedulerState;
|
||||
private stateFilePath: string;
|
||||
/** Local cache of today's full schedule, synced periodically from backend */
|
||||
private scheduleCache: ScheduleCache = new ScheduleCache();
|
||||
|
||||
constructor(config: CalendarSchedulerConfig) {
|
||||
this.config = {
|
||||
heartbeatIntervalMs: 60000, // 1 minute default
|
||||
syncIntervalMs: 300_000, // 5 minutes default
|
||||
debug: false,
|
||||
stateDir: this.getDefaultStateDir(),
|
||||
...config,
|
||||
@@ -141,7 +133,6 @@ export class CalendarScheduler {
|
||||
currentSlot: null,
|
||||
lastHeartbeatAt: null,
|
||||
intervalHandle: null,
|
||||
syncIntervalHandle: null,
|
||||
deferredSlotIds: new Set(),
|
||||
isProcessing: false,
|
||||
isRestartPending: false,
|
||||
@@ -336,21 +327,14 @@ export class CalendarScheduler {
|
||||
this.state.isRestartPending = false;
|
||||
this.config.logger.info('Calendar scheduler started');
|
||||
|
||||
// Run initial sync + heartbeat immediately
|
||||
this.runSync();
|
||||
// Run initial heartbeat immediately
|
||||
this.runHeartbeat();
|
||||
|
||||
// Schedule periodic heartbeats (slot execution checks)
|
||||
// Schedule periodic heartbeats
|
||||
this.state.intervalHandle = setInterval(
|
||||
() => this.runHeartbeat(),
|
||||
this.config.heartbeatIntervalMs
|
||||
);
|
||||
|
||||
// Schedule periodic schedule sync (full day schedule refresh)
|
||||
this.state.syncIntervalHandle = setInterval(
|
||||
() => this.runSync(),
|
||||
this.config.syncIntervalMs
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -364,41 +348,10 @@ export class CalendarScheduler {
|
||||
clearInterval(this.state.intervalHandle);
|
||||
this.state.intervalHandle = null;
|
||||
}
|
||||
if (this.state.syncIntervalHandle) {
|
||||
clearInterval(this.state.syncIntervalHandle);
|
||||
this.state.syncIntervalHandle = null;
|
||||
}
|
||||
|
||||
this.config.logger.info('Calendar scheduler stopped');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync today's full schedule from backend into local cache.
|
||||
* Runs every syncIntervalMs (default: 5 min).
|
||||
* Catches new slots assigned by other agents or the manager.
|
||||
*/
|
||||
async runSync(): Promise<void> {
|
||||
if (!this.state.isRunning || this.state.isRestartPending) return;
|
||||
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
try {
|
||||
const slots = await this.config.bridge.getDaySchedule(today);
|
||||
if (slots) {
|
||||
this.scheduleCache.sync(today, slots);
|
||||
this.logDebug(`Schedule synced: ${slots.length} slots for ${today}`);
|
||||
}
|
||||
} catch (err) {
|
||||
this.config.logger.warn(`Schedule sync failed: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local schedule cache (for status reporting / tools).
|
||||
*/
|
||||
getScheduleCache(): ScheduleCache {
|
||||
return this.scheduleCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a single heartbeat cycle.
|
||||
* Fetches pending slots and handles execution logic.
|
||||
@@ -658,11 +611,13 @@ Task Code: ${code}
|
||||
Estimated Duration: ${duration} minutes
|
||||
Slot Type: ${slot.slot_type}
|
||||
Priority: ${slot.priority}
|
||||
Working Sessions: ${jobData.working_sessions?.join(', ') || 'none recorded'}
|
||||
|
||||
Follow the daily-routine skill's task-handson workflow to execute this task.
|
||||
Use harborforge_calendar_complete when finished, or harborforge_calendar_pause to pause.
|
||||
Before going idle, check for overdue slots as described in the slot-complete workflow.`;
|
||||
Please focus on this task for the allocated time. When you finish or need to pause,
|
||||
report your progress back to the calendar system.
|
||||
|
||||
Working sessions: ${jobData.working_sessions?.join(', ') || 'none recorded'}
|
||||
|
||||
Start working on ${code} now.`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -675,15 +630,19 @@ Before going idle, check for overdue slots as described in the slot-complete wor
|
||||
switch (sysData.event) {
|
||||
case 'ScheduleToday':
|
||||
return `System Event: Schedule Today
|
||||
|
||||
Please review today's calendar and schedule any pending tasks or planning activities.
|
||||
Estimated time: ${slot.estimated_duration} minutes.
|
||||
|
||||
Follow the daily-routine skill's plan-schedule workflow to plan today's work.`;
|
||||
Check your calendar and plan the day's work.`;
|
||||
|
||||
case 'SummaryToday':
|
||||
return `System Event: Daily Summary
|
||||
|
||||
Please provide a summary of today's activities and progress.
|
||||
Estimated time: ${slot.estimated_duration} minutes.
|
||||
|
||||
Review today's completed, deferred, and abandoned slots. Write a summary to your daily note (memory/YYYY-MM-DD.md).`;
|
||||
Review what was accomplished and prepare end-of-day notes.`;
|
||||
|
||||
case 'ScheduledGatewayRestart':
|
||||
return `System Event: Scheduled Gateway Restart
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { hostname } from 'os';
|
||||
import { getPluginConfig } from '../core/config';
|
||||
import { startManagedMonitor } from '../core/managed-monitor';
|
||||
import { getPluginConfig } from '../core/config.js';
|
||||
import { startManagedMonitor } from '../core/managed-monitor.js';
|
||||
|
||||
export function registerGatewayStartHook(api: any, deps: {
|
||||
logger: any;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { stopManagedMonitor } from '../core/managed-monitor';
|
||||
import { stopManagedMonitor } from '../core/managed-monitor.js';
|
||||
|
||||
export function registerGatewayStopHook(api: any, deps: {
|
||||
logger: any;
|
||||
|
||||
@@ -11,18 +11,20 @@
|
||||
* served directly by the plugin when Monitor queries via the
|
||||
* local monitor_port communication path.
|
||||
*/
|
||||
import { hostname, freemem, totalmem, uptime, loadavg, platform } from 'os';
|
||||
import { getPluginConfig } from './core/config';
|
||||
import { MonitorBridgeClient, type OpenClawMeta } from './core/monitor-bridge';
|
||||
import type { OpenClawAgentInfo } from './core/openclaw-agents';
|
||||
import { registerGatewayStartHook } from './hooks/gateway-start';
|
||||
import { registerGatewayStopHook } from './hooks/gateway-stop';
|
||||
import { hostname, freemem, totalmem, uptime, loadavg, platform } from 'node:os';
|
||||
import { definePluginEntry } from 'openclaw/plugin-sdk/plugin-entry';
|
||||
import { MultiAgentScheduleCache } from './calendar/schedule-cache.js';
|
||||
import { getPluginConfig } from './core/config.js';
|
||||
import { MonitorBridgeClient, type OpenClawMeta } from './core/monitor-bridge.js';
|
||||
import type { OpenClawAgentInfo } from './core/openclaw-agents.js';
|
||||
import { registerGatewayStartHook } from './hooks/gateway-start.js';
|
||||
import { registerGatewayStopHook } from './hooks/gateway-stop.js';
|
||||
import {
|
||||
createCalendarBridgeClient,
|
||||
createCalendarScheduler,
|
||||
CalendarScheduler,
|
||||
AgentWakeContext,
|
||||
} from './calendar';
|
||||
} from './calendar/index.js';
|
||||
|
||||
interface PluginAPI {
|
||||
logger: {
|
||||
@@ -53,10 +55,7 @@ interface PluginAPI {
|
||||
getAgentStatus?: () => Promise<{ status: string } | null>;
|
||||
}
|
||||
|
||||
export default {
|
||||
id: 'harbor-forge',
|
||||
name: 'HarborForge',
|
||||
register(api: PluginAPI) {
|
||||
function register(api: PluginAPI): void {
|
||||
const logger = api.logger || {
|
||||
info: (...args: any[]) => console.log('[HarborForge]', ...args),
|
||||
error: (...args: any[]) => console.error('[HarborForge]', ...args),
|
||||
@@ -290,7 +289,6 @@ export default {
|
||||
);
|
||||
|
||||
// Multi-agent sync + check loop
|
||||
const { MultiAgentScheduleCache } = require('./calendar/schedule-cache') as typeof import('./calendar/schedule-cache');
|
||||
const scheduleCache = new MultiAgentScheduleCache();
|
||||
|
||||
const SYNC_INTERVAL_MS = 300_000; // 5 min
|
||||
@@ -595,5 +593,16 @@ export default {
|
||||
}));
|
||||
|
||||
logger.info('HarborForge plugin registered (id: harbor-forge)');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// HarborForge's local PluginAPI is broader than the standard OpenClawPluginApi
|
||||
// (it surfaces optional `version`/`runtime`/`spawn` accessors that older
|
||||
// OpenClaw builds exposed). The cast at the definePluginEntry boundary
|
||||
// acknowledges that gap — the runtime api object is whatever the gateway
|
||||
// passes us, and each access is guarded with optional chaining / fallbacks.
|
||||
export default definePluginEntry({
|
||||
id: 'harbor-forge',
|
||||
name: 'HarborForge',
|
||||
description: 'HarborForge plugin for OpenClaw - project management, monitoring, and CLI integration',
|
||||
register: register as (api: any) => void,
|
||||
});
|
||||
|
||||
25
plugin/openclaw-sdk.d.ts
vendored
Normal file
25
plugin/openclaw-sdk.d.ts
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// Ambient declarations for the focused subpaths of the openclaw plugin SDK
|
||||
// that this plugin needs at compile time.
|
||||
//
|
||||
// We intentionally do NOT take a `dependencies` (or `devDependencies`) entry
|
||||
// on the openclaw npm package itself: openclaw is provided by the host
|
||||
// gateway at runtime, and listing it as a file:/.../openclaw devDep breaks
|
||||
// the installer's `npm install --omit=dev` step because npm/arborist trips
|
||||
// over openclaw's own (deeply nested) dependency graph.
|
||||
//
|
||||
// These declarations cover only what we use here. They are deliberately
|
||||
// permissive — the runtime contract is whatever the gateway hands us, and
|
||||
// we guard each api access with optional chaining or a fallback at call site.
|
||||
|
||||
declare module 'openclaw/plugin-sdk/plugin-entry' {
|
||||
export function definePluginEntry<T extends {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
register: (api: any) => void | Promise<void>;
|
||||
}>(opts: T): T;
|
||||
}
|
||||
|
||||
declare module 'openclaw/plugin-sdk/core' {
|
||||
export type OpenClawPluginApi = unknown;
|
||||
}
|
||||
@@ -1,9 +1,19 @@
|
||||
{
|
||||
"id": "harbor-forge",
|
||||
"name": "HarborForge",
|
||||
"version": "0.2.0",
|
||||
"description": "HarborForge plugin for OpenClaw - project management, monitoring, and CLI integration",
|
||||
"entry": "./dist/index.js",
|
||||
"activation": {
|
||||
"onStartup": true
|
||||
},
|
||||
"contracts": {
|
||||
"tools": [
|
||||
"harborforge_status",
|
||||
"harborforge_telemetry",
|
||||
"harborforge_monitor_telemetry",
|
||||
"harborforge_calendar_status",
|
||||
"harborforge_calendar_complete"
|
||||
]
|
||||
},
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"name": "harbor-forge-plugin",
|
||||
"version": "0.2.0",
|
||||
"description": "OpenClaw plugin for HarborForge monitor bridge and CLI integration",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"clean": "rm -rf dist",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
|
||||
Reference in New Issue
Block a user