Compare commits
1 Commits
zhi-2026-0
...
ec09578de3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec09578de3 |
@@ -19,6 +19,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';
|
||||||
|
import { ScheduleCache } from './schedule-cache';
|
||||||
import {
|
import {
|
||||||
CalendarSlotResponse,
|
CalendarSlotResponse,
|
||||||
SlotStatus,
|
SlotStatus,
|
||||||
@@ -44,6 +45,8 @@ 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) */
|
||||||
@@ -95,8 +98,10 @@ interface SchedulerState {
|
|||||||
currentSlot: CalendarSlotResponse | null;
|
currentSlot: CalendarSlotResponse | null;
|
||||||
/** Last heartbeat timestamp */
|
/** Last heartbeat timestamp */
|
||||||
lastHeartbeatAt: Date | null;
|
lastHeartbeatAt: Date | null;
|
||||||
/** Interval handle for cleanup */
|
/** Heartbeat interval handle */
|
||||||
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 */
|
||||||
@@ -117,10 +122,13 @@ 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,
|
||||||
@@ -133,6 +141,7 @@ 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,
|
||||||
@@ -327,14 +336,21 @@ 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 heartbeat immediately
|
// Run initial sync + heartbeat immediately
|
||||||
|
this.runSync();
|
||||||
this.runHeartbeat();
|
this.runHeartbeat();
|
||||||
|
|
||||||
// Schedule periodic heartbeats
|
// Schedule periodic heartbeats (slot execution checks)
|
||||||
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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -348,10 +364,41 @@ 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.
|
||||||
@@ -611,13 +658,11 @@ 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'}
|
||||||
|
|
||||||
Please focus on this task for the allocated time. When you finish or need to pause,
|
Follow the daily-routine skill's task-handson workflow to execute this task.
|
||||||
report your progress back to the calendar system.
|
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.`;
|
||||||
Working sessions: ${jobData.working_sessions?.join(', ') || 'none recorded'}
|
|
||||||
|
|
||||||
Start working on ${code} now.`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -630,19 +675,15 @@ Start working on ${code} now.`;
|
|||||||
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.
|
||||||
|
|
||||||
Check your calendar and plan the day's work.`;
|
Follow the daily-routine skill's plan-schedule workflow to plan today'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 what was accomplished and prepare end-of-day notes.`;
|
Review today's completed, deferred, and abandoned slots. Write a summary to your daily note (memory/YYYY-MM-DD.md).`;
|
||||||
|
|
||||||
case 'ScheduledGatewayRestart':
|
case 'ScheduledGatewayRestart':
|
||||||
return `System Event: Scheduled Gateway Restart
|
return `System Event: Scheduled Gateway Restart
|
||||||
|
|||||||
Reference in New Issue
Block a user