@iamoberlin/chorus 1.2.7 → 1.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/scheduler.ts +67 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iamoberlin/chorus",
3
- "version": "1.2.7",
3
+ "version": "1.2.8",
4
4
  "description": "CHORUS: Hierarchy Of Recursive Unified Self-improvement — with Prayer Requests social network",
5
5
  "author": "Oberlin <iam@oberlin.ai>",
6
6
  "license": "MIT",
package/src/scheduler.ts CHANGED
@@ -9,6 +9,9 @@ import type { OpenClawPluginService, PluginLogger } from "openclaw/plugin-sdk";
9
9
  import type { ChorusConfig } from "./config.js";
10
10
  import { CHOIRS, shouldRunChoir, CASCADE_ORDER, type Choir } from "./choirs.js";
11
11
  import { recordExecution, type ChoirExecution } from "./metrics.js";
12
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
13
+ import { join } from "path";
14
+ import { homedir } from "os";
12
15
 
13
16
  interface ChoirContext {
14
17
  choirId: string;
@@ -22,6 +25,64 @@ interface ChoirRunState {
22
25
  runCount: number;
23
26
  }
24
27
 
28
+ // State persistence path
29
+ const CHORUS_DIR = join(homedir(), ".chorus");
30
+ const RUN_STATE_PATH = join(CHORUS_DIR, "run-state.json");
31
+
32
+ // Load persisted run state from disk
33
+ function loadRunState(log: PluginLogger): Map<string, ChoirRunState> {
34
+ const state = new Map<string, ChoirRunState>();
35
+
36
+ // Initialize all choirs with default state
37
+ for (const choirId of Object.keys(CHOIRS)) {
38
+ state.set(choirId, { runCount: 0 });
39
+ }
40
+
41
+ // Try to load persisted state
42
+ if (existsSync(RUN_STATE_PATH)) {
43
+ try {
44
+ const data = JSON.parse(readFileSync(RUN_STATE_PATH, "utf-8"));
45
+ for (const [choirId, saved] of Object.entries(data)) {
46
+ const s = saved as any;
47
+ if (state.has(choirId)) {
48
+ state.set(choirId, {
49
+ lastRun: s.lastRun ? new Date(s.lastRun) : undefined,
50
+ lastOutput: s.lastOutput,
51
+ runCount: s.runCount || 0,
52
+ });
53
+ }
54
+ }
55
+ log.info(`[chorus] Loaded run state from disk (${Object.keys(data).length} choirs)`);
56
+ } catch (err) {
57
+ log.warn(`[chorus] Failed to load run state: ${err}`);
58
+ }
59
+ }
60
+
61
+ return state;
62
+ }
63
+
64
+ // Save run state to disk
65
+ function saveRunState(state: Map<string, ChoirRunState>, log: PluginLogger): void {
66
+ try {
67
+ // Ensure directory exists
68
+ if (!existsSync(CHORUS_DIR)) {
69
+ mkdirSync(CHORUS_DIR, { recursive: true });
70
+ }
71
+
72
+ const obj: Record<string, any> = {};
73
+ for (const [choirId, s] of state) {
74
+ obj[choirId] = {
75
+ lastRun: s.lastRun?.toISOString(),
76
+ lastOutput: s.lastOutput,
77
+ runCount: s.runCount,
78
+ };
79
+ }
80
+ writeFileSync(RUN_STATE_PATH, JSON.stringify(obj, null, 2));
81
+ } catch (err) {
82
+ log.error(`[chorus] Failed to save run state: ${err}`);
83
+ }
84
+ }
85
+
25
86
  export function createChoirScheduler(
26
87
  config: ChorusConfig,
27
88
  log: PluginLogger,
@@ -29,12 +90,9 @@ export function createChoirScheduler(
29
90
  ): OpenClawPluginService {
30
91
  let checkInterval: NodeJS.Timeout | null = null;
31
92
  const contextStore: Map<string, ChoirContext> = new Map();
32
- const runState: Map<string, ChoirRunState> = new Map();
33
-
34
- // Initialize run state for all choirs
35
- for (const choirId of Object.keys(CHOIRS)) {
36
- runState.set(choirId, { runCount: 0 });
37
- }
93
+
94
+ // Load persisted state instead of starting fresh
95
+ const runState = loadRunState(log);
38
96
 
39
97
  // Build the prompt with context injected
40
98
  function buildPrompt(choir: Choir): string {
@@ -101,6 +159,9 @@ export function createChoirScheduler(
101
159
  lastOutput: output.slice(0, 500),
102
160
  runCount: state.runCount + 1,
103
161
  });
162
+
163
+ // Persist state to disk after each run
164
+ saveRunState(runState, log);
104
165
 
105
166
  log.info(`[chorus] ${choir.emoji} ${choir.name} completed (${(execution.durationMs/1000).toFixed(1)}s)`);
106
167