6 Commits

Author SHA1 Message Date
c9f61419cb Merge docs/readme-refresh into main
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:55:57 +01:00
cc807484fc docs: refresh README — accuracy pass + HarborForge platform context
Verified against current code; fixed stale/inaccurate sections and
documented previously-undocumented features/flags/endpoints. Added a
"Part of the HarborForge platform" reference and role/port.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 17:50:01 +01:00
operator
917cb344cf chore: track ambient openclaw-sdk.d.ts (was caught by *.d.ts ignore)
The .gitignore was set up to skip tsc's compiled .d.ts output but it also
matched the hand-written ambient declarations file added in the previous
commit. Add a negation rule so that one file is tracked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 08:29:35 +00:00
operator
64a9c431bf chore: convert plugin to ESM and migrate to current openclaw plugin SDK
ESM conversion:
- plugin/package.json: add "type": "module".
- plugin/tsconfig.json: switch module/moduleResolution to "nodenext"; bump
  target to ES2022.
- All relative imports across plugin/ now carry .js extensions as required
  by Node ESM (nodenext module resolution).
- plugin/index.ts: replace `require('./calendar/schedule-cache')` with a
  proper top-level import; switch `from 'os'` to `from 'node:os'`.

Plugin SDK convention update:
- Wrap default export with definePluginEntry({ id, name, description,
  register }) per the current openclaw plugin authoring contract.
- Modernize plugin/openclaw.plugin.json: drop entry/version, add
  activation.onStartup so gateway_start fires for this plugin at boot,
  declare contracts.tools listing the five harborforge_* tools.
- Add a local plugin/openclaw-sdk.d.ts with ambient declarations for the
  focused subpaths (openclaw/plugin-sdk/plugin-entry,
  openclaw/plugin-sdk/core). We deliberately do NOT add openclaw as an
  npm devDependency: the installer's `npm install --omit=dev` step trips
  over openclaw's own (deeply nested) dependency graph when listed via
  file:.../openclaw, and the runtime contract is provided by the gateway
  loader anyway.
- The local PluginAPI interface is preserved (broader than the standard
  OpenClawPluginApi — it surfaces `version`/`runtime`/`spawn`); the
  register function is cast at the definePluginEntry boundary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 08:28:58 +00:00
h z
957bcbb4a8 Merge pull request 'feat: schedule cache, workflow-aligned prompts, dispatchInbound wakeup' (#5) from zhi-2026-04-18 into main
Reviewed-on: #5
2026-05-01 07:23:25 +00:00
operator
9195dc6bd1 feat: schedule cache, workflow-aligned prompts, dispatchInbound wakeup
1. ScheduleCache: local cache of today's schedule, synced every 5 min
   from HF backend via new getDaySchedule() API

2. Wakeup prompts updated to reference daily-routine skill workflows
   (task-handson, plan-schedule, slot-complete)

3. Agent wakeup via dispatchInboundMessageWithDispatcher (in-process)
   - Same mechanism as Discord plugin
   - Creates unique session per slot: agent:{agentId}:hf-calendar:slot-{slotId}
   - No WebSocket, CLI, or cron dependency
   - Verified working on test environment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 12:24:21 +00:00
12 changed files with 212 additions and 158 deletions

2
.gitignore vendored
View File

@@ -4,3 +4,5 @@ plugin/**/*.js
plugin/**/*.js.map plugin/**/*.js.map
plugin/**/*.d.ts plugin/**/*.d.ts
plugin/**/*.d.ts.map plugin/**/*.d.ts.map
# Hand-written ambient declarations are tracked; only compiled .d.ts above is ignored.
!plugin/openclaw-sdk.d.ts

192
README.md
View File

@@ -1,84 +1,107 @@
# HarborForge OpenClaw Plugin # HarborForge OpenClaw Plugin
OpenClaw 插件:向 HarborForge Monitor 暴露 OpenClaw 侧元数据,并提供可选的本地桥接能力;安装时也可顺带安装 `hf` CLI OpenClaw plugin that exposes OpenClaw-side metadata to the HarborForge Monitor, provides an optional local Monitor bridge, drives the HarborForge Calendar scheduler, and can optionally install the `hf` CLI.
## 当前状态 Part of the [HarborForge](../README.md) platform.
- 插件注册名:`harbor-forge` - Role: OpenClaw integration layer for HarborForge (registered plugin id: `harbor-forge`).
- 旧 sidecar `server/` 架构已移除 - Talks to the HarborForge backend (`backendUrl`, default `https://monitor.hangman-lab.top`) for Calendar APIs.
- 监控桥接走本地 `monitor_port` - Talks to a local HarborForge.Monitor bridge over `127.0.0.1:<monitor_port>` (no fixed default; commonly `9100`).
- 安装脚本支持 `--install-cli`
- `skills/hf/` 仅在 `--install-cli` 时一并安装
## 项目结构 ## Current State
- Plugin registration id: `harbor-forge` (was `harborforge-monitor`)
- Plugin version: `0.2.0` (package manifests); telemetry reports `pluginVersion` `0.3.1`
- Legacy sidecar `server/` architecture removed — telemetry is served directly by the plugin
- Monitor bridge runs over the local `monitor_port`
- Calendar scheduler integration (PLG-CAL-001 / 002 / 004)
- Installer supports `--install-cli` and an optional managed Monitor (`--install-monitor`)
- `skills/hf/` is installed only with `--install-cli`
## Project Structure
```text ```text
HarborForge.OpenclawPlugin/ HarborForge.OpenclawPlugin/
├── package.json ├── package.json
├── README.md ├── README.md
├── docs/ # design notes (calendar, monitor connector)
├── plugin/ ├── plugin/
│ ├── openclaw.plugin.json │ ├── openclaw.plugin.json # plugin manifest + config schema
│ ├── index.ts │ ├── index.ts # plugin entry, tool registration
│ ├── tsconfig.json
│ ├── core/ │ ├── core/
│ │ ├── config.ts │ │ ├── config.ts # config defaults / resolution
│ │ ├── managed-monitor.ts │ │ ├── managed-monitor.ts # optionally spawn HarborForge.Monitor
│ │ ── monitor-bridge.ts │ │ ── monitor-bridge.ts # client for the Monitor bridge
│ │ └── openclaw-agents.ts # enumerate OpenClaw agents
│ ├── calendar/ # Calendar scheduler + bridge
│ │ ├── index.ts
│ │ ├── scheduler.ts
│ │ ├── calendar-bridge.ts
│ │ └── types.ts
│ ├── hooks/ │ ├── hooks/
│ │ ├── gateway-start.ts │ │ ├── gateway-start.ts
│ │ └── gateway-stop.ts │ │ └── gateway-stop.ts
│ └── package.json │ └── package.json
├── skills/ ├── skills/
│ └── hf/ │ └── hf/
│ └── SKILL.md │ └── SKILL.md # installed only with --install-cli
└── scripts/ └── scripts/
└── install.mjs └── install.mjs
``` ```
## 安装 ## Installation
### 普通安装 ### Standard Install
```bash ```bash
node scripts/install.mjs node scripts/install.mjs
``` ```
这会: This will:
- 构建并安装 OpenClaw 插件 - Build and install the OpenClaw plugin
- 复制常规 skills - Copy regular skills
- **不会**安装 `hf` 二进制 - **Not** install the `hf` binary
- **不会**复制 `skills/hf/` - **Not** copy `skills/hf/`
### 安装插件 + `hf` CLI ### Plugin + `hf` CLI
```bash ```bash
node scripts/install.mjs --install-cli node scripts/install.mjs --install-cli
``` ```
这会额外: This additionally:
- 构建 `HarborForge.Cli` - Builds `HarborForge.Cli` (`go build ./cmd/hf`)
- 安装 `hf` `~/.openclaw/bin/hf` - Installs `hf` to `<openclaw>/bin/hf` (default `~/.openclaw/bin/hf`) and `chmod +x`
- `chmod +x ~/.openclaw/bin/hf` - Copies `skills/hf/` into the OpenClaw skills directory
- 复制 `skills/hf/` 到 OpenClaw profile skills 目录
### 常用选项 ### Common Options
```bash ```bash
# 仅构建 # Build only (no install / config)
node scripts/install.mjs --build-only node scripts/install.mjs --build-only
# 指定 OpenClaw profile # Install only, skip dependency checks
node scripts/install.mjs --skip-check
# Specify OpenClaw profile path (also honors OPENCLAW_PATH env)
node scripts/install.mjs --openclaw-profile-path /custom/path/.openclaw node scripts/install.mjs --openclaw-profile-path /custom/path/.openclaw
# 详细日志 # Build and install a managed HarborForge.Monitor binary alongside the plugin
node scripts/install.mjs --install-monitor yes --monitor-branch main
# Verbose logs
node scripts/install.mjs --verbose node scripts/install.mjs --verbose
# 卸载 # Uninstall (plugin, config entries, hf binary, managed monitor)
node scripts/install.mjs --uninstall node scripts/install.mjs --uninstall
``` ```
## 配置 The installer also updates OpenClaw config (`plugins.load.paths`, `plugins.allow`, `plugins.entries.harbor-forge.enabled`) via `openclaw config`.
编辑 `~/.openclaw/openclaw.json` ## Configuration
Edit `~/.openclaw/openclaw.json`:
```json ```json
{ {
@@ -94,7 +117,9 @@ node scripts/install.mjs --uninstall
"monitor_port": 9100, "monitor_port": 9100,
"reportIntervalSec": 30, "reportIntervalSec": 30,
"httpFallbackIntervalSec": 60, "httpFallbackIntervalSec": 60,
"logLevel": "info" "logLevel": "info",
"calendarEnabled": true,
"calendarHeartbeatIntervalSec": 60
} }
} }
} }
@@ -102,51 +127,72 @@ node scripts/install.mjs --uninstall
} }
``` ```
然后重启: Then restart:
```bash ```bash
openclaw gateway restart openclaw gateway restart
``` ```
## 配置项 ## Config Options
| 选项 | 类型 | 默认值 | 说明 | | Option | Type | Default | Description |
|------|------|--------|------| |--------|------|---------|-------------|
| `enabled` | boolean | `true` | 是否启用插件 | | `enabled` | boolean | `true` | Enable the plugin |
| `backendUrl` | string | `https://monitor.hangman-lab.top` | HarborForge Monitor 后端地址 | | `backendUrl` | string | `https://monitor.hangman-lab.top` | HarborForge backend base URL (Monitor + Calendar APIs) |
| `identifier` | string | 主机名 | 服务器标识符 | | `identifier` | string | hostname | Server / claw identifier |
| `apiKey` | string | 无 | HarborForge Monitor 生成的服务器 API Key | | `apiKey` | string | (none) | Server API key from the HarborForge Monitor admin panel |
| `monitor_port` | number | 无 | 本地桥接端口;插件通过 `127.0.0.1:<monitor_port>` 与 HarborForge.Monitor 通信 | | `monitor_port` | number | (none) | Local bridge port; plugin talks to HarborForge.Monitor via `127.0.0.1:<monitor_port>` |
| `reportIntervalSec` | number | `30` | 报告间隔(秒) | | `reportIntervalSec` | number | `30` | Metadata push interval (seconds) |
| `httpFallbackIntervalSec` | number | `60` | HTTP 回退间隔(秒) | | `httpFallbackIntervalSec` | number | `60` | HTTP heartbeat interval when WS unavailable |
| `logLevel` | string | `info` | 日志级别:`debug` / `info` / `warn` / `error` | | `logLevel` | string | `info` | Log level: `debug` / `info` / `warn` / `error` |
| `calendarEnabled` | boolean | `true` | Enable Calendar scheduler integration (PLG-CAL-001) |
| `calendarHeartbeatIntervalSec` | number | `60` | Calendar heartbeat interval (seconds) |
| `calendarApiKey` | string | (none) | API key for Calendar API auth; falls back to `apiKey` / `X-Agent-ID` |
| `managedMonitor` | string | (none) | Absolute path to a HarborForge.Monitor binary; if set, gateway start/stop hooks spawn/stop it |
## 本地桥接说明 ## Local Monitor Bridge
当插件配置了 `monitor_port`,并且 HarborForge.Monitor 也使用相同的 `MONITOR_PORT` 时: When the plugin has `monitor_port` configured and HarborForge.Monitor uses the same `MONITOR_PORT`:
- Monitor `127.0.0.1:<MONITOR_PORT>` 提供本地桥接服务 - Monitor serves a local bridge on `127.0.0.1:<MONITOR_PORT>`
- 插件可探测 `GET /health` - The plugin probes `GET /health`
- 插件工具 `harborforge_monitor_telemetry` 可读取 `GET /telemetry` - The plugin tool `harborforge_monitor_telemetry` reads `GET /telemetry`
- 如果桥接端口未配置或不可达,插件仍可正常运行 - The plugin pushes OpenClaw metadata (version, plugin version, agents) via `POST /openclaw` on the `reportIntervalSec` cadence, enriching Monitor heartbeats
- If the bridge port is unconfigured or unreachable, the plugin still works normally
也就是说,这条链路是**可选增强**,不是插件启动或 Monitor 心跳的前置条件。 This link is an **optional enhancement**, not a precondition for the plugin to start or for Monitor heartbeats.
## 插件提供的信息 ## Managed Monitor
### OpenClaw 元数据 If `managedMonitor` points to an installed HarborForge.Monitor binary, the `gateway_start` hook spawns it (passing `--backend-url`, `--identifier`, `--api-key`, `--monitor-port`, `--report-interval`, `--log-level` from the plugin config) and `gateway_stop` terminates it. Use `install.mjs --install-monitor yes` to build and wire this automatically.
- OpenClaw version
- plugin version
- 标识符 / 主机名
- 时间戳
### 系统快照 ## Calendar Scheduler
- uptime
- memory total/free/used/usagePercent
- load avg1/avg5/avg15
- platform
## 开发 When `calendarEnabled` is true, on gateway start the plugin starts a Calendar scheduler that heartbeats the backend (`/calendar/agent/heartbeat`, `/calendar/agent/status`, `/calendar/agent/notify`) to receive and run scheduled TimeSlots, waking/spawning agents via the OpenClaw `spawn` API (with a notification fallback). Scheduler state is persisted to a state file; gateway restarts can be requested by the backend (PLG-CAL-004).
## Tools Provided
| Tool | Description |
|------|-------------|
| `harborforge_status` | Plugin status, resolved config, Monitor bridge health, calendar status, telemetry snapshot |
| `harborforge_telemetry` | Current system telemetry snapshot from this host |
| `harborforge_monitor_telemetry` | Query the Monitor bridge for host hardware telemetry |
| `harborforge_calendar_status` | Calendar scheduler status and current slot |
| `harborforge_calendar_complete` | Complete the current calendar slot with actual duration |
| `harborforge_calendar_abort` | Abort the current calendar slot |
| `harborforge_calendar_pause` | Pause the current calendar slot |
| `harborforge_calendar_resume` | Resume the paused calendar slot |
| `harborforge_restart_status` | Check whether a gateway restart is pending |
### Telemetry Snapshot Fields
- `identifier`, `hostname`, `platform`, `timestamp`
- `uptime`
- `memory`: `total` / `free` / `used` / `usagePercent`
- `load`: `avg1` / `avg5` / `avg15`
- `openclaw`: `version` / `pluginVersion`
## Development
```bash ```bash
cd plugin cd plugin
@@ -154,14 +200,16 @@ npm install
npm run build npm run build
``` ```
## 依赖 The build runs `tsc` and emits `dist/` (`dist/index.js` is the plugin entry).
## Dependencies
- Node.js 18+ - Node.js 18+
- OpenClaw Gateway - OpenClaw Gateway
- Go 1.20+(仅 `--install-cli` 需要) - Go 1.20+ (only for `--install-cli` / `--install-monitor`)
## 相关提示 ## Tips
- 安装 `hf` 后,建议把 `~/.openclaw/bin` 加到 `PATH` - After installing `hf`, add `~/.openclaw/bin` to your `PATH`
- Agent 使用 `hf` 时,优先试 `hf --help-brief` - When an agent uses `hf`, try `hf --help-brief` first
- 完整命令树看 `hf --help` - For the full command tree, see `hf --help`

View File

@@ -29,7 +29,7 @@ import {
CalendarSlotResponse, CalendarSlotResponse,
SlotAgentUpdate, SlotAgentUpdate,
SlotStatus, SlotStatus,
} from './types'; } from './types.js';
export interface CalendarBridgeConfig { export interface CalendarBridgeConfig {
/** HarborForge backend base URL (e.g. "https://monitor.hangman-lab.top") */ /** HarborForge backend base URL (e.g. "https://monitor.hangman-lab.top") */
@@ -292,7 +292,7 @@ export class CalendarBridgeClient {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
import { hostname } from 'os'; import { hostname } from 'os';
import { getPluginConfig } from '../core/config'; import { getPluginConfig } from '../core/config.js';
export interface CalendarPluginConfig { export interface CalendarPluginConfig {
/** Backend URL for calendar API (overrides monitor backendUrl) */ /** Backend URL for calendar API (overrides monitor backendUrl) */

View File

@@ -13,7 +13,7 @@
* • AgentWakeContext — context passed to agent when waking * • AgentWakeContext — context passed to agent when waking
* *
* Usage in plugin/index.ts: * Usage in plugin/index.ts:
* import { createCalendarBridgeClient, createCalendarScheduler } from './calendar'; * import { createCalendarBridgeClient, createCalendarScheduler } from './calendar.js';
* *
* const agentId = process.env.AGENT_ID || 'unknown'; * const agentId = process.env.AGENT_ID || 'unknown';
* const calendar = createCalendarBridgeClient(api, 'https://monitor.hangman-lab.top', agentId); * const calendar = createCalendarBridgeClient(api, 'https://monitor.hangman-lab.top', agentId);
@@ -28,7 +28,7 @@
* scheduler.start(); * scheduler.start();
*/ */
export * from './types'; export * from './types.js';
export * from './calendar-bridge'; export * from './calendar-bridge.js';
export * from './scheduler'; export * from './scheduler.js';
export * from './schedule-cache'; export * from './schedule-cache.js';

View File

@@ -18,8 +18,7 @@
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs'; import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
import { join, dirname } from 'path'; import { join, dirname } from 'path';
import { CalendarBridgeClient } from './calendar-bridge'; import { CalendarBridgeClient } from './calendar-bridge.js';
import { ScheduleCache } from './schedule-cache';
import { import {
CalendarSlotResponse, CalendarSlotResponse,
SlotStatus, SlotStatus,
@@ -27,7 +26,7 @@ import {
SlotAgentUpdate, SlotAgentUpdate,
CalendarEventDataJob, CalendarEventDataJob,
CalendarEventDataSystemEvent, CalendarEventDataSystemEvent,
} from './types'; } from './types.js';
export interface CalendarSchedulerConfig { export interface CalendarSchedulerConfig {
/** Calendar bridge client for backend communication */ /** Calendar bridge client for backend communication */
@@ -45,8 +44,6 @@ export interface CalendarSchedulerConfig {
}; };
/** Heartbeat interval in milliseconds (default: 60000) */ /** Heartbeat interval in milliseconds (default: 60000) */
heartbeatIntervalMs?: number; heartbeatIntervalMs?: number;
/** Schedule sync interval in milliseconds (default: 300000 = 5 min) */
syncIntervalMs?: number;
/** Enable verbose debug logging */ /** Enable verbose debug logging */
debug?: boolean; debug?: boolean;
/** Directory for state persistence (default: plugin data dir) */ /** Directory for state persistence (default: plugin data dir) */
@@ -98,10 +95,8 @@ interface SchedulerState {
currentSlot: CalendarSlotResponse | null; currentSlot: CalendarSlotResponse | null;
/** Last heartbeat timestamp */ /** Last heartbeat timestamp */
lastHeartbeatAt: Date | null; lastHeartbeatAt: Date | null;
/** Heartbeat interval handle */ /** Interval handle for cleanup */
intervalHandle: ReturnType<typeof setInterval> | null; 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 */ /** Set of slot IDs that have been deferred in current session */
deferredSlotIds: Set<string>; deferredSlotIds: Set<string>;
/** Whether agent is currently processing a slot */ /** Whether agent is currently processing a slot */
@@ -122,13 +117,10 @@ export class CalendarScheduler {
private config: Required<CalendarSchedulerConfig>; private config: Required<CalendarSchedulerConfig>;
private state: SchedulerState; private state: SchedulerState;
private stateFilePath: string; private stateFilePath: string;
/** Local cache of today's full schedule, synced periodically from backend */
private scheduleCache: ScheduleCache = new ScheduleCache();
constructor(config: CalendarSchedulerConfig) { constructor(config: CalendarSchedulerConfig) {
this.config = { this.config = {
heartbeatIntervalMs: 60000, // 1 minute default heartbeatIntervalMs: 60000, // 1 minute default
syncIntervalMs: 300_000, // 5 minutes default
debug: false, debug: false,
stateDir: this.getDefaultStateDir(), stateDir: this.getDefaultStateDir(),
...config, ...config,
@@ -141,7 +133,6 @@ export class CalendarScheduler {
currentSlot: null, currentSlot: null,
lastHeartbeatAt: null, lastHeartbeatAt: null,
intervalHandle: null, intervalHandle: null,
syncIntervalHandle: null,
deferredSlotIds: new Set(), deferredSlotIds: new Set(),
isProcessing: false, isProcessing: false,
isRestartPending: false, isRestartPending: false,
@@ -336,21 +327,14 @@ export class CalendarScheduler {
this.state.isRestartPending = false; this.state.isRestartPending = false;
this.config.logger.info('Calendar scheduler started'); this.config.logger.info('Calendar scheduler started');
// Run initial sync + heartbeat immediately // Run initial heartbeat immediately
this.runSync();
this.runHeartbeat(); this.runHeartbeat();
// Schedule periodic heartbeats (slot execution checks) // Schedule periodic heartbeats
this.state.intervalHandle = setInterval( this.state.intervalHandle = setInterval(
() => this.runHeartbeat(), () => this.runHeartbeat(),
this.config.heartbeatIntervalMs 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); clearInterval(this.state.intervalHandle);
this.state.intervalHandle = null; this.state.intervalHandle = null;
} }
if (this.state.syncIntervalHandle) {
clearInterval(this.state.syncIntervalHandle);
this.state.syncIntervalHandle = null;
}
this.config.logger.info('Calendar scheduler stopped'); 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. * Execute a single heartbeat cycle.
* Fetches pending slots and handles execution logic. * Fetches pending slots and handles execution logic.
@@ -658,11 +611,13 @@ Task Code: ${code}
Estimated Duration: ${duration} minutes Estimated Duration: ${duration} minutes
Slot Type: ${slot.slot_type} Slot Type: ${slot.slot_type}
Priority: ${slot.priority} Priority: ${slot.priority}
Working Sessions: ${jobData.working_sessions?.join(', ') || 'none recorded'}
Follow the daily-routine skill's task-handson workflow to execute this task. Please focus on this task for the allocated time. When you finish or need to pause,
Use harborforge_calendar_complete when finished, or harborforge_calendar_pause to pause. report your progress back to the calendar system.
Before going idle, check for overdue slots as described in the slot-complete workflow.`;
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) { switch (sysData.event) {
case 'ScheduleToday': case 'ScheduleToday':
return `System Event: Schedule Today return `System Event: Schedule Today
Please review today's calendar and schedule any pending tasks or planning activities.
Estimated time: ${slot.estimated_duration} minutes. 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': case 'SummaryToday':
return `System Event: Daily Summary return `System Event: Daily Summary
Please provide a summary of today's activities and progress.
Estimated time: ${slot.estimated_duration} minutes. 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': case 'ScheduledGatewayRestart':
return `System Event: Scheduled Gateway Restart return `System Event: Scheduled Gateway Restart

View File

@@ -1,6 +1,6 @@
import { hostname } from 'os'; import { hostname } from 'os';
import { getPluginConfig } from '../core/config'; import { getPluginConfig } from '../core/config.js';
import { startManagedMonitor } from '../core/managed-monitor'; import { startManagedMonitor } from '../core/managed-monitor.js';
export function registerGatewayStartHook(api: any, deps: { export function registerGatewayStartHook(api: any, deps: {
logger: any; logger: any;

View File

@@ -1,4 +1,4 @@
import { stopManagedMonitor } from '../core/managed-monitor'; import { stopManagedMonitor } from '../core/managed-monitor.js';
export function registerGatewayStopHook(api: any, deps: { export function registerGatewayStopHook(api: any, deps: {
logger: any; logger: any;

View File

@@ -11,18 +11,20 @@
* served directly by the plugin when Monitor queries via the * served directly by the plugin when Monitor queries via the
* local monitor_port communication path. * local monitor_port communication path.
*/ */
import { hostname, freemem, totalmem, uptime, loadavg, platform } from 'os'; import { hostname, freemem, totalmem, uptime, loadavg, platform } from 'node:os';
import { getPluginConfig } from './core/config'; import { definePluginEntry } from 'openclaw/plugin-sdk/plugin-entry';
import { MonitorBridgeClient, type OpenClawMeta } from './core/monitor-bridge'; import { MultiAgentScheduleCache } from './calendar/schedule-cache.js';
import type { OpenClawAgentInfo } from './core/openclaw-agents'; import { getPluginConfig } from './core/config.js';
import { registerGatewayStartHook } from './hooks/gateway-start'; import { MonitorBridgeClient, type OpenClawMeta } from './core/monitor-bridge.js';
import { registerGatewayStopHook } from './hooks/gateway-stop'; import type { OpenClawAgentInfo } from './core/openclaw-agents.js';
import { registerGatewayStartHook } from './hooks/gateway-start.js';
import { registerGatewayStopHook } from './hooks/gateway-stop.js';
import { import {
createCalendarBridgeClient, createCalendarBridgeClient,
createCalendarScheduler, createCalendarScheduler,
CalendarScheduler, CalendarScheduler,
AgentWakeContext, AgentWakeContext,
} from './calendar'; } from './calendar/index.js';
interface PluginAPI { interface PluginAPI {
logger: { logger: {
@@ -53,10 +55,7 @@ interface PluginAPI {
getAgentStatus?: () => Promise<{ status: string } | null>; getAgentStatus?: () => Promise<{ status: string } | null>;
} }
export default { function register(api: PluginAPI): void {
id: 'harbor-forge',
name: 'HarborForge',
register(api: PluginAPI) {
const logger = api.logger || { const logger = api.logger || {
info: (...args: any[]) => console.log('[HarborForge]', ...args), info: (...args: any[]) => console.log('[HarborForge]', ...args),
error: (...args: any[]) => console.error('[HarborForge]', ...args), error: (...args: any[]) => console.error('[HarborForge]', ...args),
@@ -290,7 +289,6 @@ export default {
); );
// Multi-agent sync + check loop // Multi-agent sync + check loop
const { MultiAgentScheduleCache } = require('./calendar/schedule-cache') as typeof import('./calendar/schedule-cache');
const scheduleCache = new MultiAgentScheduleCache(); const scheduleCache = new MultiAgentScheduleCache();
const SYNC_INTERVAL_MS = 300_000; // 5 min const SYNC_INTERVAL_MS = 300_000; // 5 min
@@ -595,5 +593,16 @@ export default {
})); }));
logger.info('HarborForge plugin registered (id: harbor-forge)'); 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
View 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;
}

View File

@@ -1,9 +1,19 @@
{ {
"id": "harbor-forge", "id": "harbor-forge",
"name": "HarborForge", "name": "HarborForge",
"version": "0.2.0",
"description": "HarborForge plugin for OpenClaw - project management, monitoring, and CLI integration", "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": { "configSchema": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,

View File

@@ -2,6 +2,7 @@
"name": "harbor-forge-plugin", "name": "harbor-forge-plugin",
"version": "0.2.0", "version": "0.2.0",
"description": "OpenClaw plugin for HarborForge monitor bridge and CLI integration", "description": "OpenClaw plugin for HarborForge monitor bridge and CLI integration",
"type": "module",
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
"clean": "rm -rf dist", "clean": "rm -rf dist",

View File

@@ -1,8 +1,8 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2022", "target": "ES2022",
"module": "CommonJS", "module": "nodenext",
"moduleResolution": "node", "moduleResolution": "nodenext",
"esModuleInterop": true, "esModuleInterop": true,
"strict": true, "strict": true,
"skipLibCheck": true, "skipLibCheck": true,