@iamoberlin/chorus 1.3.6 → 1.3.7

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 CHANGED
@@ -8,9 +8,6 @@
8
8
 
9
9
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
10
10
  import { spawnSync } from "child_process";
11
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
12
- import { join } from "path";
13
- import { homedir } from "os";
14
11
  import { loadChorusConfig, type ChorusPluginConfig } from "./src/config.js";
15
12
  import { createSecurityHooks } from "./src/security.js";
16
13
  import { createChoirScheduler } from "./src/scheduler.js";
@@ -41,37 +38,7 @@ import {
41
38
  import * as prayers from "./src/prayers/prayers.js";
42
39
  import * as prayerStore from "./src/prayers/store.js";
43
40
 
44
- const VERSION = "1.3.5"; // Vision now updates run-state.json
45
-
46
- // Run state persistence for Vision mode
47
- const CHORUS_DIR = join(homedir(), ".chorus");
48
- const RUN_STATE_PATH = join(CHORUS_DIR, "run-state.json");
49
-
50
- function updateRunState(choirId: string, output: string): void {
51
- try {
52
- if (!existsSync(CHORUS_DIR)) {
53
- mkdirSync(CHORUS_DIR, { recursive: true });
54
- }
55
-
56
- let state: Record<string, any> = {};
57
- if (existsSync(RUN_STATE_PATH)) {
58
- try {
59
- state = JSON.parse(readFileSync(RUN_STATE_PATH, "utf-8"));
60
- } catch { /* start fresh */ }
61
- }
62
-
63
- const existing = state[choirId] || { runCount: 0 };
64
- state[choirId] = {
65
- lastRun: new Date().toISOString(),
66
- lastOutput: output.slice(0, 500),
67
- runCount: (existing.runCount || 0) + 1,
68
- };
69
-
70
- writeFileSync(RUN_STATE_PATH, JSON.stringify(state, null, 2));
71
- } catch (err) {
72
- // Silent fail — don't break Vision for state issues
73
- }
74
- }
41
+ const VERSION = "1.3.4"; // Restore --message flag (required by openclaw agent CLI)
75
42
 
76
43
  const plugin = {
77
44
  id: "chorus",
@@ -415,13 +382,10 @@ const plugin = {
415
382
  const text = json.result?.payloads?.[0]?.text || '';
416
383
  const duration = json.result?.meta?.durationMs || 0;
417
384
  contextStore.set(`${choirId}:d${day}`, text.slice(0, 2000)); // Keep 2KB of response
418
- updateRunState(choirId, text); // Update scheduler state
419
385
  successfulRuns++;
420
386
  console.log(` ✓ (${(duration/1000).toFixed(1)}s)`);
421
387
  } catch {
422
- const output = result.stdout?.slice(-2000) || `[${choir.name} completed]`;
423
- contextStore.set(`${choirId}:d${day}`, output);
424
- updateRunState(choirId, output); // Update scheduler state
388
+ contextStore.set(`${choirId}:d${day}`, result.stdout?.slice(-2000) || `[${choir.name} completed]`);
425
389
  successfulRuns++;
426
390
  console.log(` ✓`);
427
391
  }
@@ -2,7 +2,7 @@
2
2
  "id": "chorus",
3
3
  "name": "CHORUS",
4
4
  "description": "CHORUS: Hierarchy Of Recursive Unified Self-improvement",
5
- "version": "1.1.3",
5
+ "version": "1.3.4",
6
6
  "author": "Oberlin",
7
7
  "homepage": "https://chorus.oberlin.ai",
8
8
  "repository": "https://github.com/iamoberlin/chorus",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iamoberlin/chorus",
3
- "version": "1.3.6",
3
+ "version": "1.3.7",
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",
@@ -11,7 +11,7 @@ import { recordExecution, type ChoirExecution } from "./metrics.js";
11
11
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
12
12
  import { join } from "path";
13
13
  import { homedir } from "os";
14
- import { spawnSync } from "child_process";
14
+ import { spawn } from "child_process";
15
15
 
16
16
  // Workspace path for research output
17
17
  const WORKSPACE_PATH = process.env.OPENCLAW_WORKSPACE || join(homedir(), ".openclaw", "workspace");
@@ -274,17 +274,25 @@ CRITICAL: If sending alerts via iMessage, use PLAIN TEXT ONLY (no markdown).
274
274
  }
275
275
 
276
276
  if (!result) {
277
- // CLI fallback - use stdin to avoid arg length limits
277
+ // CLI fallback - use stdin to avoid arg length limits (async to avoid blocking event loop)
278
278
  log.debug(`[purpose-research] Using CLI fallback for "${purpose.name}"`);
279
- result = spawnSync("openclaw", [
280
- "agent",
281
- "--session-id", `chorus:purpose:${purpose.id}`,
282
- "--json",
283
- ], {
284
- input: prompt,
285
- encoding: "utf-8",
286
- timeout: config.researchTimeoutMs,
287
- maxBuffer: 1024 * 1024, // 1MB
279
+ result = await new Promise<any>((resolve) => {
280
+ const child = spawn("openclaw", [
281
+ "agent",
282
+ "--session-id", `chorus:purpose:${purpose.id}`,
283
+ "--message", prompt,
284
+ "--json",
285
+ ], { stdio: ['pipe', 'pipe', 'pipe'] });
286
+
287
+ let stdout = '';
288
+ let stderr = '';
289
+ const maxBuffer = 1024 * 1024;
290
+ child.stdout.on('data', (d: Buffer) => { if (stdout.length < maxBuffer) stdout += d.toString(); });
291
+ child.stderr.on('data', (d: Buffer) => { if (stderr.length < maxBuffer) stderr += d.toString(); });
292
+
293
+ const timer = setTimeout(() => { child.kill('SIGTERM'); }, config.researchTimeoutMs);
294
+ child.on('close', (code) => { clearTimeout(timer); resolve({ status: code, stdout, stderr }); });
295
+ child.on('error', (err) => { clearTimeout(timer); resolve({ status: 1, stdout: '', stderr: String(err) }); });
288
296
  });
289
297
 
290
298
  if (result.status === 0 && result.stdout) {
package/src/scheduler.ts CHANGED
@@ -12,7 +12,7 @@ import { recordExecution, type ChoirExecution } from "./metrics.js";
12
12
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
13
13
  import { join } from "path";
14
14
  import { homedir } from "os";
15
- import { spawnSync } from "child_process";
15
+ import { spawn } from "child_process";
16
16
 
17
17
  interface ChoirContext {
18
18
  choirId: string;
@@ -128,20 +128,28 @@ export function createChoirScheduler(
128
128
  try {
129
129
  const prompt = buildPrompt(choir);
130
130
 
131
- // Use openclaw agent CLI (same as Vision mode)
132
- const result = spawnSync('openclaw', [
133
- 'agent',
134
- '--session-id', `chorus:${choir.id}`,
135
- '--message', prompt,
136
- '--json',
137
- ], {
138
- encoding: 'utf-8',
139
- timeout: 300000, // 5 min
140
- maxBuffer: 1024 * 1024, // 1MB
131
+ // Use openclaw agent CLI (async to avoid blocking event loop)
132
+ const result = await new Promise<{ status: number | null; stdout: string; stderr: string }>((resolve) => {
133
+ const child = spawn('openclaw', [
134
+ 'agent',
135
+ '--session-id', `chorus:${choir.id}`,
136
+ '--message', prompt,
137
+ '--json',
138
+ ], { stdio: ['pipe', 'pipe', 'pipe'] });
139
+
140
+ let stdout = '';
141
+ let stderr = '';
142
+ const maxBuffer = 1024 * 1024;
143
+ child.stdout.on('data', (d: Buffer) => { if (stdout.length < maxBuffer) stdout += d.toString(); });
144
+ child.stderr.on('data', (d: Buffer) => { if (stderr.length < maxBuffer) stderr += d.toString(); });
145
+
146
+ const timer = setTimeout(() => { child.kill('SIGTERM'); }, 300000); // 5 min
147
+ child.on('close', (code) => { clearTimeout(timer); resolve({ status: code, stdout, stderr }); });
148
+ child.on('error', (err) => { clearTimeout(timer); resolve({ status: 1, stdout: '', stderr: String(err) }); });
141
149
  });
142
150
 
143
151
  let output = "(no response)";
144
-
152
+
145
153
  if (result.status === 0 && result.stdout) {
146
154
  // Extract JSON from output (may have plugin logs before it)
147
155
  const stdout = result.stdout;