Luigit
repositories / will

will

owned by admin

src/shared/wake.ts

Raw
export type WakeKind = 'deploy_failure' | 'ci_failure' | 'recovery' | 'chat' | 'heartbeat'

export interface Wake {
  id: string
  seq: number
  kind: WakeKind
  createdAt: string
  payload: unknown
  /** attempts > 0 means a previous turn for this wake failed on the model path. */
  attempts: number
  /** ISO time; wake is not claimable before this (deferred retry backoff). */
  deferredUntil?: string | undefined
}

/** Priority classes; lower number = handled first (spec: failure, chat FIFO, heartbeat). */
export function wakePriority(kind: WakeKind): number {
  switch (kind) {
    case 'deploy_failure':
    case 'ci_failure':
    case 'recovery':
      return 0
    case 'chat':
      return 1
    case 'heartbeat':
      return 2
  }
}

/** Stable ordering: priority class, then enqueue seq (FIFO inside a class). */
export function orderWakes<
  T extends { kind: WakeKind; seq: number; deferredUntil?: string | undefined },
>(wakes: T[], nowMs: number): T[] {
  return wakes
    .filter((w) => !w.deferredUntil || Date.parse(w.deferredUntil) <= nowMs)
    .sort((a, b) => wakePriority(a.kind) - wakePriority(b.kind) || a.seq - b.seq)
}

/**
 * At most one pending heartbeat wake may exist (coalescing).
 * Call before enqueueing a heartbeat.
 */
export function hasPendingHeartbeat(wakes: readonly { kind: WakeKind }[]): boolean {
  return wakes.some((w) => w.kind === 'heartbeat')
}

export interface HeartbeatState {
  lastClaimedHour?: string
}

/**
 * Claim the current UTC hour; restart may claim the current unhandled hour,
 * older hours are never replayed (spec AC16).
 */
export function claimHeartbeatHour(
  state: HeartbeatState,
  hourKey: string,
): { claimed: boolean; state: HeartbeatState } {
  if (state.lastClaimedHour === hourKey) return { claimed: false, state }
  if (state.lastClaimedHour !== undefined && hourKey < state.lastClaimedHour) {
    return { claimed: false, state }
  }
  return { claimed: true, state: { lastClaimedHour: hourKey } }
}

export interface WakeContext {
  wake: Wake
  facts: string
  prompt: string
}