repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | /** |
| #2 | * Heartbeat Configuration |
| #3 | * |
| #4 | * Parses and manages heartbeat.yml configuration. |
| #5 | */ |
| #6 | |
| #7 | import fs from "fs"; |
| #8 | import path from "path"; |
| #9 | import YAML from "yaml"; |
| #10 | import type { HeartbeatEntry, HeartbeatConfig, AutomatonDatabase } from "../types.js"; |
| #11 | import { getAutomatonDir } from "../identity/wallet.js"; |
| #12 | |
| #13 | const USDC_TOPUP_ENTRY_NAME = "check_usdc_balance"; |
| #14 | const USDC_TOPUP_FAST_SCHEDULE = "*/5 * * * *"; |
| #15 | const USDC_TOPUP_OLD_SCHEDULE = "0 */12 * * *"; |
| #16 | |
| #17 | const DEFAULT_HEARTBEAT_CONFIG: HeartbeatConfig = { |
| #18 | entries: [ |
| #19 | { |
| #20 | name: "heartbeat_ping", |
| #21 | schedule: "*/15 * * * *", |
| #22 | task: "heartbeat_ping", |
| #23 | enabled: true, |
| #24 | }, |
| #25 | { |
| #26 | name: "check_credits", |
| #27 | schedule: "0 */6 * * *", |
| #28 | task: "check_credits", |
| #29 | enabled: true, |
| #30 | }, |
| #31 | { |
| #32 | name: "check_usdc_balance", |
| #33 | schedule: USDC_TOPUP_FAST_SCHEDULE, |
| #34 | task: "check_usdc_balance", |
| #35 | enabled: true, |
| #36 | }, |
| #37 | { |
| #38 | name: "check_for_updates", |
| #39 | schedule: "0 */4 * * *", |
| #40 | task: "check_for_updates", |
| #41 | enabled: true, |
| #42 | }, |
| #43 | { |
| #44 | name: "health_check", |
| #45 | schedule: "*/30 * * * *", |
| #46 | task: "health_check", |
| #47 | enabled: true, |
| #48 | }, |
| #49 | { |
| #50 | name: "convex_heartbeat", |
| #51 | schedule: "*/2 * * * *", |
| #52 | task: "convex_heartbeat", |
| #53 | enabled: true, |
| #54 | }, |
| #55 | { |
| #56 | name: "check_social_inbox", |
| #57 | schedule: "*/2 * * * *", |
| #58 | task: "check_social_inbox", |
| #59 | enabled: true, |
| #60 | }, |
| #61 | { |
| #62 | name: "backroom_conversation", |
| #63 | schedule: "0 */2 * * *", |
| #64 | task: "backroom_conversation", |
| #65 | enabled: true, |
| #66 | }, |
| #67 | ], |
| #68 | defaultIntervalMs: 60_000, |
| #69 | lowComputeMultiplier: 4, |
| #70 | }; |
| #71 | |
| #72 | /** |
| #73 | * Load heartbeat config from YAML file, falling back to defaults. |
| #74 | */ |
| #75 | export function loadHeartbeatConfig(configPath?: string): HeartbeatConfig { |
| #76 | const filePath = |
| #77 | configPath || path.join(getAutomatonDir(), "heartbeat.yml"); |
| #78 | |
| #79 | if (!fs.existsSync(filePath)) { |
| #80 | return DEFAULT_HEARTBEAT_CONFIG; |
| #81 | } |
| #82 | |
| #83 | try { |
| #84 | const raw = fs.readFileSync(filePath, "utf-8"); |
| #85 | const parsed = YAML.parse(raw) || {}; |
| #86 | |
| #87 | const parsedEntries = (parsed.entries || []).map((e: any) => ({ |
| #88 | name: e.name, |
| #89 | schedule: e.schedule, |
| #90 | task: e.task, |
| #91 | enabled: e.enabled !== false, |
| #92 | params: e.params, |
| #93 | })) as HeartbeatEntry[]; |
| #94 | |
| #95 | const entries = mergeWithDefaults(parsedEntries); |
| #96 | |
| #97 | return { |
| #98 | entries, |
| #99 | defaultIntervalMs: |
| #100 | parsed.defaultIntervalMs || DEFAULT_HEARTBEAT_CONFIG.defaultIntervalMs, |
| #101 | lowComputeMultiplier: |
| #102 | parsed.lowComputeMultiplier || |
| #103 | DEFAULT_HEARTBEAT_CONFIG.lowComputeMultiplier, |
| #104 | }; |
| #105 | } catch { |
| #106 | return DEFAULT_HEARTBEAT_CONFIG; |
| #107 | } |
| #108 | } |
| #109 | |
| #110 | /** |
| #111 | * Save heartbeat config to YAML file. |
| #112 | */ |
| #113 | export function saveHeartbeatConfig( |
| #114 | config: HeartbeatConfig, |
| #115 | configPath?: string, |
| #116 | ): void { |
| #117 | const filePath = |
| #118 | configPath || path.join(getAutomatonDir(), "heartbeat.yml"); |
| #119 | const dir = path.dirname(filePath); |
| #120 | if (!fs.existsSync(dir)) { |
| #121 | fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); |
| #122 | } |
| #123 | |
| #124 | fs.writeFileSync(filePath, YAML.stringify(config), { mode: 0o600 }); |
| #125 | } |
| #126 | |
| #127 | /** |
| #128 | * Write the default heartbeat.yml file. |
| #129 | */ |
| #130 | export function writeDefaultHeartbeatConfig(configPath?: string): void { |
| #131 | saveHeartbeatConfig(DEFAULT_HEARTBEAT_CONFIG, configPath); |
| #132 | } |
| #133 | |
| #134 | /** |
| #135 | * Sync heartbeat entries from YAML config into the database. |
| #136 | */ |
| #137 | export function syncHeartbeatToDb( |
| #138 | config: HeartbeatConfig, |
| #139 | db: AutomatonDatabase, |
| #140 | ): void { |
| #141 | for (const entry of config.entries) { |
| #142 | db.upsertHeartbeatEntry(entry); |
| #143 | } |
| #144 | } |
| #145 | |
| #146 | function mergeWithDefaults(entries: HeartbeatEntry[]): HeartbeatEntry[] { |
| #147 | const defaults = DEFAULT_HEARTBEAT_CONFIG.entries.map((entry) => ({ ...entry })); |
| #148 | const defaultsByName = new Map(defaults.map((entry) => [entry.name, entry])); |
| #149 | const mergedByName = new Map(defaultsByName); |
| #150 | |
| #151 | for (const entry of entries) { |
| #152 | if (!entry?.name) continue; |
| #153 | const fallback = defaultsByName.get(entry.name); |
| #154 | mergedByName.set(entry.name, { |
| #155 | ...(fallback || {}), |
| #156 | ...entry, |
| #157 | enabled: entry.enabled !== false, |
| #158 | task: entry.task || fallback?.task || "", |
| #159 | schedule: entry.schedule || fallback?.schedule || "", |
| #160 | }); |
| #161 | } |
| #162 | |
| #163 | const fallbackTopup = defaultsByName.get(USDC_TOPUP_ENTRY_NAME); |
| #164 | if (fallbackTopup) { |
| #165 | const current = mergedByName.get(USDC_TOPUP_ENTRY_NAME) || fallbackTopup; |
| #166 | const migratedSchedule = current.schedule?.trim() === USDC_TOPUP_OLD_SCHEDULE |
| #167 | ? USDC_TOPUP_FAST_SCHEDULE |
| #168 | : current.schedule || fallbackTopup.schedule; |
| #169 | |
| #170 | mergedByName.set(USDC_TOPUP_ENTRY_NAME, { |
| #171 | ...fallbackTopup, |
| #172 | ...current, |
| #173 | task: current.task || fallbackTopup.task, |
| #174 | schedule: migratedSchedule, |
| #175 | }); |
| #176 | } |
| #177 | |
| #178 | const orderedDefaultEntries = defaults.map( |
| #179 | (defaultEntry) => mergedByName.get(defaultEntry.name) || defaultEntry, |
| #180 | ); |
| #181 | const knownNames = new Set(defaults.map((entry) => entry.name)); |
| #182 | const customEntries = [...mergedByName.values()].filter( |
| #183 | (entry) => !knownNames.has(entry.name), |
| #184 | ); |
| #185 | |
| #186 | return [...orderedDefaultEntries, ...customEntries]; |
| #187 | } |
| #188 |