@iamoberlin/chorus 1.2.6 → 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/index.ts +24 -8
- package/package.json +1 -1
- package/src/scheduler.ts +67 -6
package/index.ts
CHANGED
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
import * as prayers from "./src/prayers/prayers.js";
|
|
39
39
|
import * as prayerStore from "./src/prayers/store.js";
|
|
40
40
|
|
|
41
|
-
const VERSION = "1.2.
|
|
41
|
+
const VERSION = "1.2.7"; // Fixed JSON parsing: extract JSON from output (plugin logs prefix it)
|
|
42
42
|
|
|
43
43
|
const plugin = {
|
|
44
44
|
id: "chorus",
|
|
@@ -208,7 +208,13 @@ const plugin = {
|
|
|
208
208
|
isolated: true,
|
|
209
209
|
timeoutSeconds: 300,
|
|
210
210
|
});
|
|
211
|
-
|
|
211
|
+
const text = result?.text || result?.payloads?.[0]?.text || '';
|
|
212
|
+
const duration = result?.meta?.durationMs || 0;
|
|
213
|
+
console.log(` ✓ ${choir.name} complete (${(duration/1000).toFixed(1)}s)`);
|
|
214
|
+
if (text) {
|
|
215
|
+
const preview = text.slice(0, 150).replace(/\n/g, ' ');
|
|
216
|
+
console.log(` ${preview}${text.length > 150 ? '...' : ''}`);
|
|
217
|
+
}
|
|
212
218
|
} catch (err) {
|
|
213
219
|
console.error(` ✗ ${choir.name} failed:`, err);
|
|
214
220
|
}
|
|
@@ -228,7 +234,11 @@ const plugin = {
|
|
|
228
234
|
|
|
229
235
|
if (result.status === 0) {
|
|
230
236
|
try {
|
|
231
|
-
|
|
237
|
+
// Extract JSON from output (may have plugin logs before it)
|
|
238
|
+
const stdout = result.stdout || '';
|
|
239
|
+
const jsonStart = stdout.indexOf('{');
|
|
240
|
+
const jsonStr = jsonStart >= 0 ? stdout.slice(jsonStart) : '{}';
|
|
241
|
+
const json = JSON.parse(jsonStr);
|
|
232
242
|
const text = json.result?.payloads?.[0]?.text || '';
|
|
233
243
|
const duration = json.result?.meta?.durationMs || 0;
|
|
234
244
|
console.log(` ✓ ${choir.name} complete (${(duration/1000).toFixed(1)}s)`);
|
|
@@ -236,8 +246,8 @@ const plugin = {
|
|
|
236
246
|
const preview = text.slice(0, 150).replace(/\n/g, ' ');
|
|
237
247
|
console.log(` ${preview}${text.length > 150 ? '...' : ''}`);
|
|
238
248
|
}
|
|
239
|
-
} catch {
|
|
240
|
-
console.log(` ✓ ${choir.name} complete`);
|
|
249
|
+
} catch (parseErr) {
|
|
250
|
+
console.log(` ✓ ${choir.name} complete (parse error: ${parseErr})`);
|
|
241
251
|
}
|
|
242
252
|
} else {
|
|
243
253
|
const errMsg = result.stderr || result.stdout || 'Unknown error';
|
|
@@ -327,7 +337,10 @@ const plugin = {
|
|
|
327
337
|
});
|
|
328
338
|
if (result.status === 0 && result.stdout) {
|
|
329
339
|
try {
|
|
330
|
-
const
|
|
340
|
+
const stdout = result.stdout;
|
|
341
|
+
const jsonStart = stdout.indexOf('{');
|
|
342
|
+
const jsonStr = jsonStart >= 0 ? stdout.slice(jsonStart) : '{}';
|
|
343
|
+
const json = JSON.parse(jsonStr);
|
|
331
344
|
const text = json.result?.payloads?.[0]?.text || '';
|
|
332
345
|
contextStore.set(`${choirId}:d${day}`, text.slice(0, 500));
|
|
333
346
|
console.log(` ✓ (dry)`);
|
|
@@ -360,9 +373,12 @@ const plugin = {
|
|
|
360
373
|
});
|
|
361
374
|
|
|
362
375
|
if (result.status === 0) {
|
|
363
|
-
// Parse the agent response
|
|
376
|
+
// Parse the agent response (extract JSON from output)
|
|
364
377
|
try {
|
|
365
|
-
const
|
|
378
|
+
const stdout = result.stdout || '';
|
|
379
|
+
const jsonStart = stdout.indexOf('{');
|
|
380
|
+
const jsonStr = jsonStart >= 0 ? stdout.slice(jsonStart) : '{}';
|
|
381
|
+
const json = JSON.parse(jsonStr);
|
|
366
382
|
const text = json.result?.payloads?.[0]?.text || '';
|
|
367
383
|
const duration = json.result?.meta?.durationMs || 0;
|
|
368
384
|
contextStore.set(`${choirId}:d${day}`, text.slice(0, 2000)); // Keep 2KB of response
|
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
|
|