{"version":3,"file":"plan-scheduler.mjs","names":[],"sources":["../../../src/services/plan-scheduler.ts"],"sourcesContent":["/**\n * Plan Scheduler — persistent job store with timer loop and condition polling.\n *\n * Responsibilities:\n * 1. Persist plans to disk (Fly volume) so they survive restarts.\n * 2. Run a tick loop that checks time-based and condition-based triggers.\n * 3. When a trigger fires, emit an event — the executor (LLM) handles execution.\n * 4. Track execution state per plan.\n *\n * The scheduler NEVER executes tools directly. It checks conditions and fires events.\n * This keeps money-touching logic in the executor where the LLM can reason about it.\n *\n * ─── Persistence Format ─────────────────────────────────────────────────\n *\n * Plans are stored as individual JSON files in the state directory:\n *   /workspace/.openclaw-state/plans/{planId}.json\n *   /workspace/.openclaw-state/plans/executions/{planId}/{executionId}.json\n *\n * On startup, the scheduler reads all plan files and rebuilds its in-memory state.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type {\n  Plan,\n  PlanStore,\n  PlanExecution,\n  PlanTemplate,\n  DeadLetterEntry,\n  ExecutionCheckpoint,\n  Trigger,\n  Condition,\n  CompareCondition,\n  ValueRef,\n  RuntimeFn,\n  PlanStatus,\n} from './plan-types.js';\n\n// ─── Event Types ────────────────────────────────────────────────────────\n\nexport type SchedulerEvent =\n  | { type: 'trigger_fired'; plan: Plan; executionId: string }\n  | { type: 'plan_expired'; plan: Plan; reason: string }\n  | { type: 'condition_check_error'; planId: string; error: string }\n  | { type: 'plan_added'; plan: Plan }\n  | { type: 'plan_cancelled'; planId: string };\n\nexport type SchedulerEventHandler = (event: SchedulerEvent) => void | Promise<void>;\n\n// ─── Runtime Value Resolver ─────────────────────────────────────────────\n// Pluggable: the scheduler doesn't know how to fetch prices or balances.\n// The plugin wires this up with real service calls.\n\nexport interface RuntimeResolver {\n  price(token: string): Promise<number>;\n  balance(token: string, chainId?: number): Promise<number>;\n  gasPrice(chainId?: number): Promise<number>;\n  timestamp(): number;\n  blockNumber(chainId?: number): Promise<number>;\n}\n\n/** Default no-op resolver for testing. */\nexport const NULL_RESOLVER: RuntimeResolver = {\n  price: async () => 0,\n  balance: async () => 0,\n  gasPrice: async () => 0,\n  timestamp: () => Math.floor(Date.now() / 1000),\n  blockNumber: async () => 0,\n};\n\n// ─── File-based Plan Store ──────────────────────────────────────────────\n\nfunction getPlansDir(): string {\n  const base = process.env.OPENCLAWNCH_TX_DIR\n    ? join(process.env.OPENCLAWNCH_TX_DIR, '..', 'plans')\n    : join(process.env.HOME ?? '/tmp', '.openclawnch', 'plans');\n  return base;\n}\n\nfunction getExecutionsDir(planId: string): string {\n  return join(getPlansDir(), 'executions', sanitizeId(planId));\n}\n\nfunction sanitizeId(id: string): string {\n  return id.replace(/[^a-zA-Z0-9_\\-]/g, '_').slice(0, 64);\n}\n\n/** Shape of the scheduler runtime state that survives restarts. */\ninterface SchedulerState {\n  intervalRunCounts: Record<string, number>;\n  lastConditionCheck: Record<string, number>;\n  /** Cron: number of times each cron plan has fired. */\n  cronRunCounts?: Record<string, number>;\n  /** Cron: timestamp (floored to minute) of the last fire for each plan. */\n  lastCronFire?: Record<string, number>;\n}\n\nexport class FilePlanStore implements PlanStore {\n  private dir: string;\n\n  constructor(dir?: string) {\n    this.dir = dir ?? getPlansDir();\n    this.ensureDir(this.dir);\n  }\n\n  save(plan: Plan): void {\n    this.ensureDir(this.dir);\n    const path = join(this.dir, `${sanitizeId(plan.id)}.json`);\n    writeFileSync(path, JSON.stringify(plan, null, 2), 'utf8');\n  }\n\n  load(planId: string): Plan | null {\n    const path = join(this.dir, `${sanitizeId(planId)}.json`);\n    try {\n      if (!existsSync(path)) return null;\n      return JSON.parse(readFileSync(path, 'utf8')) as Plan;\n    } catch {\n      return null;\n    }\n  }\n\n  loadAll(userId?: string): Plan[] {\n    try {\n      if (!existsSync(this.dir)) return [];\n      const files = readdirSync(this.dir).filter(f => f.endsWith('.json') && !f.startsWith('_'));\n      const plans: Plan[] = [];\n      for (const f of files) {\n        try {\n          const plan = JSON.parse(readFileSync(join(this.dir, f), 'utf8')) as Plan;\n          if (!userId || plan.userId === userId) {\n            plans.push(plan);\n          }\n        } catch { /* skip corrupt files */ }\n      }\n      return plans;\n    } catch {\n      return [];\n    }\n  }\n\n  delete(planId: string): boolean {\n    const path = join(this.dir, `${sanitizeId(planId)}.json`);\n    try {\n      if (existsSync(path)) {\n        rmSync(path);\n        return true;\n      }\n    } catch { /* ignore */ }\n    return false;\n  }\n\n  saveExecution(exec: PlanExecution): void {\n    const dir = getExecutionsDir(exec.planId);\n    this.ensureDir(dir);\n    const path = join(dir, `${sanitizeId(exec.executionId)}.json`);\n    writeFileSync(path, JSON.stringify(exec, null, 2), 'utf8');\n  }\n\n  loadExecutions(planId: string): PlanExecution[] {\n    const dir = getExecutionsDir(planId);\n    try {\n      if (!existsSync(dir)) return [];\n      return readdirSync(dir)\n        .filter(f => f.endsWith('.json'))\n        .map(f => {\n          try {\n            return JSON.parse(readFileSync(join(dir, f), 'utf8')) as PlanExecution;\n          } catch { return null; }\n        })\n        .filter((e): e is PlanExecution => e !== null);\n    } catch {\n      return [];\n    }\n  }\n\n  /** Persist scheduler runtime state (interval counts, last check times). */\n  saveState(state: SchedulerState): void {\n    this.ensureDir(this.dir);\n    const path = join(this.dir, '_scheduler-state.json');\n    writeFileSync(path, JSON.stringify(state), 'utf8');\n  }\n\n  /** Restore scheduler runtime state. Returns null if no state file. */\n  loadState(): SchedulerState | null {\n    const path = join(this.dir, '_scheduler-state.json');\n    try {\n      if (!existsSync(path)) return null;\n      return JSON.parse(readFileSync(path, 'utf8')) as SchedulerState;\n    } catch {\n      return null;\n    }\n  }\n\n  // ── Template Storage ──────────────────────────────────────────────────\n\n  private get templatesDir(): string {\n    return join(this.dir, 'templates');\n  }\n\n  saveTemplate(template: PlanTemplate): void {\n    this.ensureDir(this.templatesDir);\n    const path = join(this.templatesDir, `${sanitizeId(template.id)}.json`);\n    writeFileSync(path, JSON.stringify(template, null, 2), 'utf8');\n  }\n\n  loadTemplate(templateId: string): PlanTemplate | null {\n    const path = join(this.templatesDir, `${sanitizeId(templateId)}.json`);\n    try {\n      if (!existsSync(path)) return null;\n      return JSON.parse(readFileSync(path, 'utf8')) as PlanTemplate;\n    } catch {\n      return null;\n    }\n  }\n\n  listTemplates(userId?: string): PlanTemplate[] {\n    try {\n      if (!existsSync(this.templatesDir)) return [];\n      return readdirSync(this.templatesDir)\n        .filter(f => f.endsWith('.json'))\n        .map(f => {\n          try {\n            return JSON.parse(readFileSync(join(this.templatesDir, f), 'utf8')) as PlanTemplate;\n          } catch { return null; }\n        })\n        .filter((t): t is PlanTemplate => t !== null)\n        .filter(t => !userId || t.createdBy === userId);\n    } catch {\n      return [];\n    }\n  }\n\n  deleteTemplate(templateId: string): boolean {\n    const path = join(this.templatesDir, `${sanitizeId(templateId)}.json`);\n    try {\n      if (existsSync(path)) {\n        rmSync(path);\n        return true;\n      }\n    } catch { /* ignore */ }\n    return false;\n  }\n\n  // ── Dead-Letter Storage ────────────────────────────────────────────────\n\n  private get deadLetterDir(): string {\n    return join(this.dir, 'dead-letter');\n  }\n\n  saveDeadLetter(entry: DeadLetterEntry): void {\n    this.ensureDir(this.deadLetterDir);\n    const filename = `${sanitizeId(entry.planId)}_${sanitizeId(entry.nodeId)}_${entry.timestamp}.json`;\n    const path = join(this.deadLetterDir, filename);\n    writeFileSync(path, JSON.stringify(entry, null, 2), 'utf8');\n  }\n\n  loadDeadLetters(planId?: string): DeadLetterEntry[] {\n    try {\n      if (!existsSync(this.deadLetterDir)) return [];\n      return readdirSync(this.deadLetterDir)\n        .filter(f => f.endsWith('.json'))\n        .map(f => {\n          try {\n            return JSON.parse(readFileSync(join(this.deadLetterDir, f), 'utf8')) as DeadLetterEntry;\n          } catch { return null; }\n        })\n        .filter((e): e is DeadLetterEntry => e !== null)\n        .filter(e => !planId || e.planId === planId)\n        .sort((a, b) => b.timestamp - a.timestamp);\n    } catch {\n      return [];\n    }\n  }\n\n  clearDeadLetters(planId?: string): number {\n    try {\n      if (!existsSync(this.deadLetterDir)) return 0;\n      const files = readdirSync(this.deadLetterDir).filter(f => f.endsWith('.json'));\n      let removed = 0;\n      for (const f of files) {\n        if (planId) {\n          try {\n            const entry = JSON.parse(readFileSync(join(this.deadLetterDir, f), 'utf8')) as DeadLetterEntry;\n            if (entry.planId !== planId) continue;\n          } catch { continue; }\n        }\n        try { rmSync(join(this.deadLetterDir, f)); removed++; } catch { /* ignore */ }\n      }\n      return removed;\n    } catch {\n      return 0;\n    }\n  }\n\n  // ── Execution Checkpoint Storage ──────────────────────────────────────\n\n  private get checkpointsDir(): string {\n    return join(this.dir, 'checkpoints');\n  }\n\n  saveCheckpoint(cp: ExecutionCheckpoint): void {\n    this.ensureDir(this.checkpointsDir);\n    const path = join(this.checkpointsDir, `${sanitizeId(cp.executionId)}.json`);\n    writeFileSync(path, JSON.stringify(cp, null, 2), 'utf8');\n  }\n\n  loadCheckpoint(executionId: string): ExecutionCheckpoint | null {\n    const path = join(this.checkpointsDir, `${sanitizeId(executionId)}.json`);\n    try {\n      if (!existsSync(path)) return null;\n      return JSON.parse(readFileSync(path, 'utf8')) as ExecutionCheckpoint;\n    } catch {\n      return null;\n    }\n  }\n\n  loadAllCheckpoints(): ExecutionCheckpoint[] {\n    try {\n      if (!existsSync(this.checkpointsDir)) return [];\n      return readdirSync(this.checkpointsDir)\n        .filter(f => f.endsWith('.json'))\n        .map(f => {\n          try {\n            return JSON.parse(readFileSync(join(this.checkpointsDir, f), 'utf8')) as ExecutionCheckpoint;\n          } catch { return null; }\n        })\n        .filter((cp): cp is ExecutionCheckpoint => cp !== null);\n    } catch {\n      return [];\n    }\n  }\n\n  deleteCheckpoint(executionId: string): boolean {\n    const path = join(this.checkpointsDir, `${sanitizeId(executionId)}.json`);\n    try {\n      if (existsSync(path)) {\n        rmSync(path);\n        return true;\n      }\n    } catch { /* ignore */ }\n    return false;\n  }\n\n  private ensureDir(dir: string): void {\n    if (!existsSync(dir)) {\n      mkdirSync(dir, { recursive: true });\n    }\n  }\n}\n\n// ─── Scheduler ──────────────────────────────────────────────────────────\n\nexport class PlanScheduler {\n  private store: PlanStore;\n  private resolver: RuntimeResolver;\n  private handlers: SchedulerEventHandler[] = [];\n  private tickInterval: ReturnType<typeof setInterval> | null = null;\n  private plans = new Map<string, Plan>();\n  private lastConditionCheck = new Map<string, number>(); // planId → last check time\n  private intervalRunCounts = new Map<string, number>();   // planId → executions so far\n  private cronRunCounts = new Map<string, number>();       // planId → cron executions so far\n  private lastCronFire = new Map<string, number>();        // planId → last fire timestamp (minute-floored)\n  private tickMs: number;\n  private running = false;\n  private stateDirty = false;                             // debounce state persistence\n  private stateFlushTimer: ReturnType<typeof setTimeout> | null = null;\n\n  constructor(opts?: {\n    store?: PlanStore;\n    resolver?: RuntimeResolver;\n    tickMs?: number;\n  }) {\n    this.store = opts?.store ?? new FilePlanStore();\n    this.resolver = opts?.resolver ?? NULL_RESOLVER;\n    this.tickMs = opts?.tickMs ?? 15_000; // Check every 15s\n  }\n\n  /** Register an event handler. Returns unsubscribe function. */\n  on(handler: SchedulerEventHandler): () => void {\n    this.handlers.push(handler);\n    return () => {\n      this.handlers = this.handlers.filter(h => h !== handler);\n    };\n  }\n\n  /** Load persisted plans and start the tick loop. */\n  start(): void {\n    if (this.running) return;\n    this.running = true;\n\n    // Restore from disk\n    const persisted = this.store.loadAll();\n    for (const plan of persisted) {\n      if (plan.status === 'scheduled' || plan.status === 'running' || plan.status === 'paused') {\n        this.plans.set(plan.id, plan);\n      }\n    }\n\n    // Restore scheduler runtime state (interval counts, last check times)\n    this.restoreState();\n\n    // Start tick loop\n    this.tickInterval = setInterval(() => this.tick(), this.tickMs);\n    // Run an immediate first tick\n    this.tick();\n  }\n\n  /** Stop the tick loop. Flush pending state. Plans remain persisted. */\n  stop(): void {\n    this.running = false;\n    if (this.tickInterval) {\n      clearInterval(this.tickInterval);\n      this.tickInterval = null;\n    }\n    // Flush any pending state writes\n    this.flushState();\n    if (this.stateFlushTimer) {\n      clearTimeout(this.stateFlushTimer);\n      this.stateFlushTimer = null;\n    }\n  }\n\n  /** Add or update a plan. Persists immediately. */\n  addPlan(plan: Plan): void {\n    this.store.save(plan);\n    if (plan.status === 'scheduled' || plan.status === 'running') {\n      this.plans.set(plan.id, plan);\n    }\n    this.emit({ type: 'plan_added', plan }).catch(() => {});\n  }\n\n  /** Cancel a plan. */\n  cancelPlan(planId: string): boolean {\n    const plan = this.plans.get(planId) ?? this.store.load(planId);\n    if (!plan) return false;\n    plan.status = 'cancelled';\n    this.store.save(plan);\n    this.plans.delete(planId);\n    this.lastConditionCheck.delete(planId);\n    this.intervalRunCounts.delete(planId);\n    this.cronRunCounts.delete(planId);\n    this.lastCronFire.delete(planId);\n    this.persistState();\n    this.emit({ type: 'plan_cancelled', planId }).catch(() => {});\n    return true;\n  }\n\n  /** Pause a plan (stops trigger checking, can be resumed). */\n  pausePlan(planId: string): boolean {\n    const plan = this.plans.get(planId);\n    if (!plan || plan.status !== 'scheduled') return false;\n    plan.status = 'paused';\n    this.store.save(plan);\n    return true;\n  }\n\n  /** Resume a paused plan. */\n  resumePlan(planId: string): boolean {\n    const plan = this.plans.get(planId) ?? this.store.load(planId);\n    if (!plan || plan.status !== 'paused') return false;\n    plan.status = 'scheduled';\n    this.store.save(plan);\n    this.plans.set(plan.id, plan);\n    return true;\n  }\n\n  /** Get a plan by ID. */\n  getPlan(planId: string): Plan | null {\n    return this.plans.get(planId) ?? this.store.load(planId);\n  }\n\n  /** List all plans for a user. */\n  listPlans(userId?: string): Plan[] {\n    return this.store.loadAll(userId);\n  }\n\n  /** Get execution history for a plan. */\n  getExecutions(planId: string): PlanExecution[] {\n    return this.store.loadExecutions(planId);\n  }\n\n  // ── Template Proxy Methods ────────────────────────────────────────────\n\n  saveTemplate(template: PlanTemplate): void {\n    if (typeof (this.store as FilePlanStore).saveTemplate === 'function') {\n      (this.store as FilePlanStore).saveTemplate(template);\n    }\n  }\n\n  loadTemplate(templateId: string): PlanTemplate | null {\n    if (typeof (this.store as FilePlanStore).loadTemplate === 'function') {\n      return (this.store as FilePlanStore).loadTemplate(templateId);\n    }\n    return null;\n  }\n\n  listTemplates(userId?: string): PlanTemplate[] {\n    if (typeof (this.store as FilePlanStore).listTemplates === 'function') {\n      return (this.store as FilePlanStore).listTemplates(userId);\n    }\n    return [];\n  }\n\n  deleteTemplate(templateId: string): boolean {\n    if (typeof (this.store as FilePlanStore).deleteTemplate === 'function') {\n      return (this.store as FilePlanStore).deleteTemplate(templateId);\n    }\n    return false;\n  }\n\n  // ── Dead-Letter Proxy Methods ────────────────────────────────────────\n\n  saveDeadLetter(entry: DeadLetterEntry): void {\n    if (typeof (this.store as FilePlanStore).saveDeadLetter === 'function') {\n      (this.store as FilePlanStore).saveDeadLetter(entry);\n    }\n  }\n\n  loadDeadLetters(planId?: string): DeadLetterEntry[] {\n    if (typeof (this.store as FilePlanStore).loadDeadLetters === 'function') {\n      return (this.store as FilePlanStore).loadDeadLetters(planId);\n    }\n    return [];\n  }\n\n  clearDeadLetters(planId?: string): number {\n    if (typeof (this.store as FilePlanStore).clearDeadLetters === 'function') {\n      return (this.store as FilePlanStore).clearDeadLetters(planId);\n    }\n    return 0;\n  }\n\n  // ── Checkpoint Proxy Methods ─────────────────────────────────────────\n\n  saveCheckpoint(cp: ExecutionCheckpoint): void {\n    if (typeof (this.store as FilePlanStore).saveCheckpoint === 'function') {\n      (this.store as FilePlanStore).saveCheckpoint(cp);\n    }\n  }\n\n  loadCheckpoint(executionId: string): ExecutionCheckpoint | null {\n    if (typeof (this.store as FilePlanStore).loadCheckpoint === 'function') {\n      return (this.store as FilePlanStore).loadCheckpoint(executionId);\n    }\n    return null;\n  }\n\n  loadAllCheckpoints(): ExecutionCheckpoint[] {\n    if (typeof (this.store as FilePlanStore).loadAllCheckpoints === 'function') {\n      return (this.store as FilePlanStore).loadAllCheckpoints();\n    }\n    return [];\n  }\n\n  deleteCheckpoint(executionId: string): boolean {\n    if (typeof (this.store as FilePlanStore).deleteCheckpoint === 'function') {\n      return (this.store as FilePlanStore).deleteCheckpoint(executionId);\n    }\n    return false;\n  }\n\n  /** Mark a plan execution as complete (called by executor). */\n  markCompleted(planId: string, execution: PlanExecution): void {\n    const plan = this.plans.get(planId);\n    if (!plan) return;\n\n    this.store.saveExecution(execution);\n\n    // For non-recurring triggers, mark plan as completed\n    const trigger = plan.trigger;\n    if (!trigger || trigger.type === 'immediate' || trigger.type === 'time') {\n      plan.status = 'completed';\n      this.store.save(plan);\n      this.plans.delete(planId);\n    } else if (trigger.type === 'condition' && !trigger.recurring) {\n      plan.status = 'completed';\n      this.store.save(plan);\n      this.plans.delete(planId);\n    } else if (trigger.type === 'price' && !trigger.recurring) {\n      plan.status = 'completed';\n      this.store.save(plan);\n      this.plans.delete(planId);\n    }\n    // Interval, cron, recurring conditions, and recurring price triggers stay scheduled\n  }\n\n  /** Mark a plan execution as failed (called by executor). */\n  markFailed(planId: string, execution: PlanExecution): void {\n    const plan = this.plans.get(planId);\n    if (!plan) return;\n\n    this.store.saveExecution(execution);\n\n    // Non-recurring plans go to failed status\n    const trigger = plan.trigger;\n    if (!trigger || trigger.type === 'immediate' || trigger.type === 'time') {\n      plan.status = 'failed';\n      this.store.save(plan);\n      this.plans.delete(planId);\n    } else if (trigger.type === 'price' && !trigger.recurring) {\n      plan.status = 'failed';\n      this.store.save(plan);\n      this.plans.delete(planId);\n    }\n    // Recurring plans stay scheduled (they'll retry on next trigger)\n  }\n\n  // ─── State Persistence ─────────────────────────────────────────────────\n\n  /** Restore intervalRunCounts and lastConditionCheck from disk. */\n  private restoreState(): void {\n    if (typeof (this.store as FilePlanStore).loadState !== 'function') return;\n    const state = (this.store as FilePlanStore).loadState();\n    if (!state) return;\n\n    if (state.intervalRunCounts) {\n      for (const [k, v] of Object.entries(state.intervalRunCounts)) {\n        // Only restore for plans that are still active\n        if (this.plans.has(k)) {\n          this.intervalRunCounts.set(k, v);\n        }\n      }\n    }\n    if (state.lastConditionCheck) {\n      for (const [k, v] of Object.entries(state.lastConditionCheck)) {\n        if (this.plans.has(k)) {\n          this.lastConditionCheck.set(k, v);\n        }\n      }\n    }\n    if (state.cronRunCounts) {\n      for (const [k, v] of Object.entries(state.cronRunCounts)) {\n        if (this.plans.has(k)) {\n          this.cronRunCounts.set(k, v);\n        }\n      }\n    }\n    if (state.lastCronFire) {\n      for (const [k, v] of Object.entries(state.lastCronFire)) {\n        if (this.plans.has(k)) {\n          this.lastCronFire.set(k, v);\n        }\n      }\n    }\n  }\n\n  /** Mark state as dirty; it will be flushed within 5s or at next tick boundary. */\n  private persistState(): void {\n    this.stateDirty = true;\n    // Debounce: flush after 5s of inactivity to batch rapid updates\n    if (!this.stateFlushTimer) {\n      this.stateFlushTimer = setTimeout(() => {\n        this.stateFlushTimer = null;\n        this.flushState();\n      }, 5_000);\n    }\n  }\n\n  /** Write state to disk immediately if dirty. */\n  private flushState(): void {\n    if (!this.stateDirty) return;\n    if (typeof (this.store as FilePlanStore).saveState !== 'function') return;\n    this.stateDirty = false;\n\n    const state: SchedulerState = {\n      intervalRunCounts: Object.fromEntries(this.intervalRunCounts),\n      lastConditionCheck: Object.fromEntries(this.lastConditionCheck),\n      cronRunCounts: Object.fromEntries(this.cronRunCounts),\n      lastCronFire: Object.fromEntries(this.lastCronFire),\n    };\n    (this.store as FilePlanStore).saveState(state);\n  }\n\n  /** How many plans are actively being watched. */\n  get activeCount(): number {\n    return this.plans.size;\n  }\n\n  /** Get all active in-memory plans. */\n  getActivePlans(): Plan[] {\n    return Array.from(this.plans.values());\n  }\n\n  /** Is the scheduler running. */\n  get isRunning(): boolean {\n    return this.running;\n  }\n\n  // ─── Tick Loop ────────────────────────────────────────────────────────\n\n  private ticking = false;\n\n  private async tick(): Promise<void> {\n    if (this.ticking) return; // Reentrancy guard — skip if previous tick still running\n    this.ticking = true;\n    try {\n      const now = Date.now();\n\n      for (const [planId, plan] of this.plans) {\n        if (plan.status === 'paused' || plan.status === 'running') continue;\n\n        try {\n          const shouldFire = await this.shouldTriggerFire(plan, now);\n          if (shouldFire) {\n            await this.fireTrigger(plan);\n          }\n        } catch (err: any) {\n          await this.emit({ type: 'condition_check_error', planId, error: err.message ?? String(err) });\n        }\n      }\n    } finally {\n      this.ticking = false;\n    }\n  }\n\n  private async shouldTriggerFire(plan: Plan, now: number): Promise<boolean> {\n    const trigger = plan.trigger;\n    if (!trigger || trigger.type === 'immediate') return true;\n\n    switch (trigger.type) {\n      case 'time': {\n        const at = new Date(trigger.at).getTime();\n        return !isNaN(at) && now >= at;\n      }\n\n      case 'interval': {\n        const startAt = trigger.startAt ? new Date(trigger.startAt).getTime() : plan.createdAt;\n        if (now < startAt) return false;\n\n        const endAt = trigger.endAt ? new Date(trigger.endAt).getTime() : Infinity;\n        if (now > endAt) {\n          await this.emit({ type: 'plan_expired', plan, reason: 'Past end time' });\n          plan.status = 'completed';\n          this.store.save(plan);\n          this.plans.delete(plan.id);\n          return false;\n        }\n\n        const runCount = this.intervalRunCounts.get(plan.id) ?? 0;\n        if (trigger.maxRuns && runCount >= trigger.maxRuns) {\n          await this.emit({ type: 'plan_expired', plan, reason: `Reached ${trigger.maxRuns} executions` });\n          plan.status = 'completed';\n          this.store.save(plan);\n          this.plans.delete(plan.id);\n          return false;\n        }\n\n        // Check if enough time has passed since creation/last run\n        const elapsed = now - startAt;\n        const expectedRuns = Math.floor(elapsed / trigger.everyMs);\n        return expectedRuns > runCount;\n      }\n\n      case 'condition': {\n        // Respect poll interval\n        const lastCheck = this.lastConditionCheck.get(plan.id) ?? 0;\n        const pollMs = trigger.pollIntervalMs ?? 60_000;\n        if (now - lastCheck < pollMs) return false;\n\n        this.lastConditionCheck.set(plan.id, now);\n        this.persistState();\n\n        // Check expiry\n        if (trigger.expiresAfterMs && now - plan.createdAt > trigger.expiresAfterMs) {\n          await this.emit({ type: 'plan_expired', plan, reason: 'Condition watch expired' });\n          plan.status = 'completed';\n          this.store.save(plan);\n          this.plans.delete(plan.id);\n          return false;\n        }\n\n        return await this.evaluateCondition(trigger.when);\n      }\n\n      case 'cron':\n        // Cron evaluation handled in its own tick path (see evaluateCronTrigger)\n        return this.evaluateCronTrigger(plan, trigger, now);\n\n      case 'price':\n        // Price triggers are handled by the PriceWatcher service via events.\n        // The scheduler does not poll prices directly — the watcher emits\n        // 'price_crossed' events which fire the plan through firePriceTrigger().\n        return false;\n\n      case 'onchain_event':\n        // On-chain event triggers are handled by the OnChainEventListener service.\n        // The listener polls getLogs and emits 'onchain_event' events on the bus.\n        return false;\n\n      case 'balance':\n        // Balance triggers are handled by the BalanceWatcher service.\n        // The watcher polls balances and emits 'balance_changed' events on the bus.\n        return false;\n    }\n\n    return false;\n  }\n\n  private async fireTrigger(plan: Plan): Promise<void> {\n    const executionId = `exec_${plan.id}_${Date.now()}`;\n\n    // Track interval runs\n    const trigger = plan.trigger;\n    if (trigger?.type === 'interval') {\n      const count = (this.intervalRunCounts.get(plan.id) ?? 0) + 1;\n      this.intervalRunCounts.set(plan.id, count);\n      this.persistState();\n    }\n\n    // For one-shot triggers, mark as running so we don't fire again\n    if (!trigger || trigger.type === 'immediate' || trigger.type === 'time') {\n      plan.status = 'running';\n      this.store.save(plan);\n    }\n    if (trigger?.type === 'condition' && !trigger.recurring) {\n      plan.status = 'running';\n      this.store.save(plan);\n    }\n\n    await this.emit({ type: 'trigger_fired', plan, executionId });\n  }\n\n  // ─── Cron Trigger Evaluation ─────────────────────────────────────────\n\n  private evaluateCronTrigger(\n    plan: Plan,\n    trigger: { type: 'cron'; expression: string; timezone?: string; maxRuns?: number },\n    now: number,\n  ): boolean {\n    // Check max runs\n    const runCount = this.cronRunCounts.get(plan.id) ?? 0;\n    if (trigger.maxRuns && runCount >= trigger.maxRuns) {\n      plan.status = 'completed';\n      this.store.save(plan);\n      this.plans.delete(plan.id);\n      return false;\n    }\n\n    // Floor current time to minute boundary\n    const minuteFloor = Math.floor(now / 60_000) * 60_000;\n\n    // Don't fire twice in the same minute\n    const lastFire = this.lastCronFire.get(plan.id) ?? 0;\n    if (minuteFloor <= lastFire) return false;\n\n    // Parse and evaluate the cron expression against current time\n    const date = trigger.timezone\n      ? dateInTimezone(now, trigger.timezone)\n      : new Date(now);\n\n    if (!matchesCron(trigger.expression, date)) return false;\n\n    // Record the fire\n    this.cronRunCounts.set(plan.id, runCount + 1);\n    this.lastCronFire.set(plan.id, minuteFloor);\n    this.persistState();\n    return true;\n  }\n\n  // ─── Price Trigger (Event-Driven) ──────────────────────────────────────\n  // Called by the event bus listener when PriceWatcher detects a threshold cross.\n\n  async firePriceTrigger(planId: string): Promise<void> {\n    const plan = this.plans.get(planId);\n    if (!plan) return;\n    if (plan.status === 'paused' || plan.status === 'running') return;\n\n    const trigger = plan.trigger;\n    if (!trigger || trigger.type !== 'price') return;\n\n    // One-shot: mark as running\n    if (!trigger.recurring) {\n      plan.status = 'running';\n      this.store.save(plan);\n    }\n\n    const executionId = `exec_${plan.id}_${Date.now()}`;\n    await this.emit({ type: 'trigger_fired', plan, executionId });\n  }\n\n  // ─── On-Chain Event Trigger (Event-Driven) ─────────────────────────────\n  // Called by the event bus listener when OnChainEventListener detects a matching log.\n\n  async fireOnChainTrigger(planId: string): Promise<void> {\n    const plan = this.plans.get(planId);\n    if (!plan) return;\n    if (plan.status === 'paused' || plan.status === 'running') return;\n\n    const trigger = plan.trigger;\n    if (!trigger || trigger.type !== 'onchain_event') return;\n\n    if (!trigger.recurring) {\n      plan.status = 'running';\n      this.store.save(plan);\n    }\n\n    const executionId = `exec_${plan.id}_${Date.now()}`;\n    await this.emit({ type: 'trigger_fired', plan, executionId });\n  }\n\n  // ─── Balance Trigger (Event-Driven) ────────────────────────────────────\n  // Called by the event bus listener when BalanceWatcher detects a threshold cross.\n\n  async fireBalanceTrigger(planId: string): Promise<void> {\n    const plan = this.plans.get(planId);\n    if (!plan) return;\n    if (plan.status === 'paused' || plan.status === 'running') return;\n\n    const trigger = plan.trigger;\n    if (!trigger || trigger.type !== 'balance') return;\n\n    if (!trigger.recurring) {\n      plan.status = 'running';\n      this.store.save(plan);\n    }\n\n    const executionId = `exec_${plan.id}_${Date.now()}`;\n    await this.emit({ type: 'trigger_fired', plan, executionId });\n  }\n\n  // ─── Condition Evaluation ─────────────────────────────────────────────\n  // Evaluates conditions using the runtime resolver. Used by both the scheduler\n  // (for condition triggers) and the executor (for if/wait nodes).\n\n  async evaluateCondition(cond: Condition): Promise<boolean> {\n    if (cond.type === 'compare') {\n      const left = await this.resolveValue(cond.left);\n      const right = await this.resolveValue(cond.right);\n      return this.compare(left, cond.op, right);\n    } else if (cond.type === 'logic') {\n      switch (cond.op) {\n        case 'and': {\n          for (const sub of cond.conditions) {\n            if (!await this.evaluateCondition(sub)) return false;\n          }\n          return true;\n        }\n        case 'or': {\n          for (const sub of cond.conditions) {\n            if (await this.evaluateCondition(sub)) return true;\n          }\n          return false;\n        }\n        case 'not': {\n          return !await this.evaluateCondition(cond.conditions[0]!);\n        }\n      }\n    }\n    return false;\n  }\n\n  async resolveValue(ref: ValueRef): Promise<number> {\n    switch (ref.type) {\n      case 'literal':\n        return typeof ref.value === 'number' ? ref.value : parseFloat(String(ref.value)) || 0;\n      case 'runtime':\n        return this.resolveRuntimeFn(ref.fn, ref.args);\n      case 'env': {\n        const v = process.env[ref.key];\n        return v ? parseFloat(v) || 0 : 0;\n      }\n      case 'step_output':\n        // Step outputs are resolved by the executor, not the scheduler.\n        // During trigger evaluation, step_output refs return 0.\n        return 0;\n    }\n  }\n\n  private async resolveRuntimeFn(fn: RuntimeFn, args: ValueRef[]): Promise<number> {\n    const resolvedArgs = await Promise.all(args.map(a => this.resolveValue(a)));\n    switch (fn) {\n      case 'price':\n        // First arg is a literal token symbol\n        const token = args[0]?.type === 'literal' ? String(args[0].value) : 'ETH';\n        return this.resolver.price(token);\n      case 'balance':\n        const balToken = args[0]?.type === 'literal' ? String(args[0].value) : 'ETH';\n        const balChain = resolvedArgs[1] ?? undefined;\n        return this.resolver.balance(balToken, balChain);\n      case 'gas_price':\n        return this.resolver.gasPrice(resolvedArgs[0] ?? undefined);\n      case 'timestamp':\n        return this.resolver.timestamp();\n      case 'block_number':\n        return this.resolver.blockNumber(resolvedArgs[0] ?? undefined);\n    }\n  }\n\n  private compare(left: number, op: string, right: number): boolean {\n    switch (op) {\n      case 'gt': return left > right;\n      case 'gte': return left >= right;\n      case 'lt': return left < right;\n      case 'lte': return left <= right;\n      case 'eq': return Math.abs(left - right) < Number.EPSILON;\n      case 'neq': return Math.abs(left - right) >= Number.EPSILON;\n      default: return false;\n    }\n  }\n\n  private async emit(event: SchedulerEvent): Promise<void> {\n    for (const handler of this.handlers) {\n      try {\n        await handler(event);\n      } catch (err) {\n        // Log but don't let handler errors break the scheduler.\n        // Before this fix, async handler rejections were silently dropped\n        // (unhandled promise rejection → potential Node.js crash).\n        console.error(`[plan-scheduler] handler error on ${event.type}:`, err);\n      }\n    }\n  }\n}\n\n// ─── Cron Helpers ───────────────────────────────────────────────────────\n// Minimal 5-field cron parser: minute hour day-of-month month day-of-week.\n// Supports: *, ranges (1-5), lists (1,3,5), steps (*/5, 1-10/2).\n// No named days/months to keep it small.\n\n/** Convert a Date to the equivalent time in a different timezone. */\nfunction dateInTimezone(epochMs: number, tz: string): Date {\n  try {\n    const formatter = new Intl.DateTimeFormat('en-US', {\n      timeZone: tz,\n      year: 'numeric', month: '2-digit', day: '2-digit',\n      hour: '2-digit', minute: '2-digit', second: '2-digit',\n      hour12: false,\n    });\n    const parts = formatter.formatToParts(new Date(epochMs));\n    const get = (type: string) => parseInt(parts.find(p => p.type === type)?.value ?? '0', 10);\n    return new Date(get('year'), get('month') - 1, get('day'), get('hour'), get('minute'), get('second'));\n  } catch {\n    // Invalid timezone — fall back to UTC\n    return new Date(epochMs);\n  }\n}\n\n/** Check if a date matches a 5-field cron expression. */\nexport function matchesCron(expression: string, date: Date): boolean {\n  const fields = expression.trim().split(/\\s+/);\n  if (fields.length !== 5) return false;\n\n  const minute = date.getMinutes();\n  const hour = date.getHours();\n  const dayOfMonth = date.getDate();\n  const month = date.getMonth() + 1; // 1-12\n  const dayOfWeek = date.getDay();    // 0=Sun, 6=Sat\n\n  return (\n    matchesField(fields[0]!, minute, 0, 59) &&\n    matchesField(fields[1]!, hour, 0, 23) &&\n    matchesField(fields[2]!, dayOfMonth, 1, 31) &&\n    matchesField(fields[3]!, month, 1, 12) &&\n    matchesField(fields[4]!, dayOfWeek, 0, 7) // 0 and 7 both = Sunday\n  );\n}\n\n/** Check if a single cron field matches a value. */\nfunction matchesField(field: string, value: number, min: number, max: number): boolean {\n  // Handle Sunday normalization: 7 → 0\n  if (max === 7 && value === 7) value = 0;\n\n  for (const part of field.split(',')) {\n    if (matchesPart(part.trim(), value, min, max)) return true;\n  }\n  return false;\n}\n\n// Check if a single cron part (e.g., star-slash-5, 1-10/2, star, 5) matches.\nfunction matchesPart(part: string, value: number, min: number, _max: number): boolean {\n  const slashIdx = part.indexOf('/');\n  const rangePart = slashIdx >= 0 ? part.slice(0, slashIdx) : part;\n  const step = slashIdx >= 0 ? parseInt(part.slice(slashIdx + 1), 10) : 1;\n  if (isNaN(step) || step < 1) return false;\n\n  if (rangePart === '*') {\n    return (value - min) % step === 0;\n  }\n\n  // Handle range: 1-5\n  const dashIdx = rangePart.indexOf('-');\n  if (dashIdx >= 0) {\n    const start = parseInt(rangePart.slice(0, dashIdx), 10);\n    const end = parseInt(rangePart.slice(dashIdx + 1), 10);\n    if (isNaN(start) || isNaN(end)) return false;\n    if (value < start || value > end) return false;\n    return (value - start) % step === 0;\n  }\n\n  // Single value\n  const num = parseInt(rangePart, 10);\n  if (isNaN(num)) return false;\n  return value === num;\n}\n\n// ─── Singleton ──────────────────────────────────────────────────────────\n\nlet _scheduler: PlanScheduler | null = null;\n\nexport function getScheduler(opts?: ConstructorParameters<typeof PlanScheduler>[0]): PlanScheduler {\n  if (!_scheduler) {\n    _scheduler = new PlanScheduler(opts);\n  } else if (opts) {\n    console.warn(\n      '[plan-scheduler] getScheduler() called with config but scheduler already exists. ' +\n      'Config ignored. Call resetScheduler() first to reconfigure.',\n    );\n  }\n  return _scheduler;\n}\n\nexport function resetScheduler(): void {\n  if (_scheduler) {\n    _scheduler.stop();\n    _scheduler = null;\n  }\n}\n"],"mappings":"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"}