@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.
- package/package.json +1 -1
- package/src/scheduler.ts +67 -6
package/package.json
CHANGED
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
|