@iamoberlin/chorus 1.1.4
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/LICENSE +21 -0
- package/README.md +191 -0
- package/index.ts +724 -0
- package/logo.png +0 -0
- package/openclaw.plugin.json +117 -0
- package/package.json +41 -0
- package/src/choirs.ts +375 -0
- package/src/config.ts +105 -0
- package/src/daemon.ts +287 -0
- package/src/metrics.ts +241 -0
- package/src/purpose-research.ts +392 -0
- package/src/purposes.ts +178 -0
- package/src/salience.ts +160 -0
- package/src/scheduler.ts +241 -0
- package/src/security.ts +26 -0
- package/src/senses.ts +259 -0
package/index.ts
ADDED
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CHORUS Extension
|
|
3
|
+
*
|
|
4
|
+
* CHORUS: Hierarchy Of Recursive Unified Self-improvement
|
|
5
|
+
* Recursive illumination through the Nine Choirs.
|
|
6
|
+
* Config via openclaw.yaml: plugins.entries.chorus.config
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
10
|
+
import { spawnSync } from "child_process";
|
|
11
|
+
import { loadChorusConfig, type ChorusPluginConfig } from "./src/config.js";
|
|
12
|
+
import { createSecurityHooks } from "./src/security.js";
|
|
13
|
+
import { createChoirScheduler } from "./src/scheduler.js";
|
|
14
|
+
import { CHOIRS, formatFrequency } from "./src/choirs.js";
|
|
15
|
+
import {
|
|
16
|
+
getTodayMetrics,
|
|
17
|
+
getMetricsForDate,
|
|
18
|
+
getRecentMetrics,
|
|
19
|
+
getTotals,
|
|
20
|
+
setQualityScore,
|
|
21
|
+
formatMetricsSummary,
|
|
22
|
+
formatWeeklySummary,
|
|
23
|
+
} from "./src/metrics.js";
|
|
24
|
+
import { createDaemon, DEFAULT_DAEMON_CONFIG, type DaemonConfig } from "./src/daemon.js";
|
|
25
|
+
import { getInboxPath } from "./src/senses.js";
|
|
26
|
+
import {
|
|
27
|
+
loadPurposes,
|
|
28
|
+
addPurpose,
|
|
29
|
+
updatePurpose,
|
|
30
|
+
removePurpose,
|
|
31
|
+
formatPurposesList,
|
|
32
|
+
} from "./src/purposes.js";
|
|
33
|
+
import {
|
|
34
|
+
createPurposeResearchScheduler,
|
|
35
|
+
DEFAULT_PURPOSE_RESEARCH_CONFIG,
|
|
36
|
+
type PurposeResearchConfig,
|
|
37
|
+
} from "./src/purpose-research.js";
|
|
38
|
+
|
|
39
|
+
const VERSION = "1.1.3";
|
|
40
|
+
|
|
41
|
+
const plugin = {
|
|
42
|
+
id: "chorus",
|
|
43
|
+
name: "CHORUS",
|
|
44
|
+
description: "CHORUS: Hierarchy Of Recursive Unified Self-improvement",
|
|
45
|
+
|
|
46
|
+
register(api: OpenClawPluginApi) {
|
|
47
|
+
// Standard OpenClaw config: plugins.entries.chorus.config
|
|
48
|
+
const pluginConfig = api.config.plugins?.entries?.chorus?.config as ChorusPluginConfig | undefined;
|
|
49
|
+
const config = loadChorusConfig(pluginConfig);
|
|
50
|
+
|
|
51
|
+
api.logger.info(`[chorus] šµ CHORUS v${VERSION}`);
|
|
52
|
+
|
|
53
|
+
// Register security hooks (Powers choir handles security)
|
|
54
|
+
createSecurityHooks(api, config);
|
|
55
|
+
|
|
56
|
+
// Register choir scheduler service
|
|
57
|
+
if (config.choirs.enabled) {
|
|
58
|
+
api.registerService(createChoirScheduler(config, api.logger, api));
|
|
59
|
+
api.logger.info("[chorus] Choirs enabled ā scheduler registered");
|
|
60
|
+
} else {
|
|
61
|
+
api.logger.info("[chorus] Choirs disabled ā set enabled: true in openclaw.yaml");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Register daemon service
|
|
65
|
+
const daemonConfig: DaemonConfig = {
|
|
66
|
+
...DEFAULT_DAEMON_CONFIG,
|
|
67
|
+
enabled: (pluginConfig as any)?.daemon?.enabled ?? true,
|
|
68
|
+
...(pluginConfig as any)?.daemon,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
let daemon: ReturnType<typeof createDaemon> | null = null;
|
|
72
|
+
if (daemonConfig.enabled) {
|
|
73
|
+
daemon = createDaemon(daemonConfig, api.logger, api);
|
|
74
|
+
api.registerService(daemon);
|
|
75
|
+
api.logger.info("[chorus] Daemon enabled ā autonomous attention active");
|
|
76
|
+
} else {
|
|
77
|
+
api.logger.info("[chorus] Daemon disabled");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Register purpose research service
|
|
81
|
+
const purposeResearchConfig: PurposeResearchConfig = {
|
|
82
|
+
...DEFAULT_PURPOSE_RESEARCH_CONFIG,
|
|
83
|
+
enabled: config.purposeResearch.enabled,
|
|
84
|
+
dailyRunCap: config.purposeResearch.dailyRunCap,
|
|
85
|
+
defaultFrequency: config.purposeResearch.defaultFrequency,
|
|
86
|
+
defaultMaxFrequency: config.purposeResearch.defaultMaxFrequency,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
let purposeResearch: ReturnType<typeof createPurposeResearchScheduler> | null = null;
|
|
90
|
+
if (purposeResearchConfig.enabled) {
|
|
91
|
+
purposeResearch = createPurposeResearchScheduler(purposeResearchConfig, api.logger, api);
|
|
92
|
+
api.registerService(purposeResearch);
|
|
93
|
+
api.logger.info("[chorus] Purpose research enabled ā adaptive frequency active");
|
|
94
|
+
} else {
|
|
95
|
+
api.logger.info("[chorus] Purpose research disabled");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Register CLI
|
|
99
|
+
api.registerCli((ctx) => {
|
|
100
|
+
const program = ctx.program.command("chorus").description("CHORUS Nine Choirs management");
|
|
101
|
+
|
|
102
|
+
// Status command
|
|
103
|
+
program.command("status").description("Show CHORUS status").action(async () => {
|
|
104
|
+
const purposes = await loadPurposes();
|
|
105
|
+
const activePurposes = purposes.filter(p => p.progress < 100);
|
|
106
|
+
const researchPurposes = purposes.filter(p =>
|
|
107
|
+
p.progress < 100 &&
|
|
108
|
+
p.research?.enabled !== false &&
|
|
109
|
+
(p.criteria?.length || p.research?.domains?.length)
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
console.log("");
|
|
113
|
+
console.log("šµ CHORUS ā Hierarchy Of Recursive Unified Self-improvement");
|
|
114
|
+
console.log("ā".repeat(55));
|
|
115
|
+
console.log("");
|
|
116
|
+
console.log(` Version: ${VERSION}`);
|
|
117
|
+
console.log(` Choirs: ${config.choirs.enabled ? "ā
enabled" : "ā disabled"}`);
|
|
118
|
+
console.log(` Daemon: ${daemonConfig.enabled ? "ā
enabled" : "ā disabled"}`);
|
|
119
|
+
console.log(` Purpose Research: ${purposeResearchConfig.enabled ? "ā
enabled" : "ā disabled"}`);
|
|
120
|
+
console.log(` Active Purposes: ${activePurposes.length}`);
|
|
121
|
+
console.log(` Research Purposes: ${researchPurposes.length}`);
|
|
122
|
+
if (daemon) {
|
|
123
|
+
console.log(` Attention Queue: ${daemon.getQueueSize()} items`);
|
|
124
|
+
}
|
|
125
|
+
if (purposeResearch) {
|
|
126
|
+
console.log(` Research Runs: ${purposeResearch.getDailyRunCount()}/${purposeResearch.getDailyCap()} today`);
|
|
127
|
+
}
|
|
128
|
+
console.log(` Timezone: ${config.choirs.timezone}`);
|
|
129
|
+
console.log("");
|
|
130
|
+
|
|
131
|
+
if (!config.choirs.enabled && !daemonConfig.enabled && !purposeResearchConfig.enabled) {
|
|
132
|
+
console.log(" š” Enable choirs, daemon, or purposeResearch in openclaw.yaml");
|
|
133
|
+
console.log("");
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// List choirs command
|
|
138
|
+
program.command("list").description("List all choirs and their schedules").action(() => {
|
|
139
|
+
console.log("");
|
|
140
|
+
console.log("šµ Nine Choirs");
|
|
141
|
+
console.log("ā".repeat(50));
|
|
142
|
+
console.log("");
|
|
143
|
+
console.log("FIRST TRIAD ā Contemplation");
|
|
144
|
+
console.log("ā".repeat(50));
|
|
145
|
+
printChoir("seraphim", config);
|
|
146
|
+
printChoir("cherubim", config);
|
|
147
|
+
printChoir("thrones", config);
|
|
148
|
+
console.log("");
|
|
149
|
+
console.log("SECOND TRIAD ā Governance");
|
|
150
|
+
console.log("ā".repeat(50));
|
|
151
|
+
printChoir("dominions", config);
|
|
152
|
+
printChoir("virtues", config);
|
|
153
|
+
printChoir("powers", config);
|
|
154
|
+
console.log("");
|
|
155
|
+
console.log("THIRD TRIAD ā Action");
|
|
156
|
+
console.log("ā".repeat(50));
|
|
157
|
+
printChoir("principalities", config);
|
|
158
|
+
printChoir("archangels", config);
|
|
159
|
+
printChoir("angels", config);
|
|
160
|
+
console.log("");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Run a specific choir manually (or all if none specified)
|
|
164
|
+
program
|
|
165
|
+
.command("run [choir]")
|
|
166
|
+
.description("Manually trigger a choir (or all choirs if none specified)")
|
|
167
|
+
.option("--preview", "Preview prompt without running")
|
|
168
|
+
.action(async (choirId?: string, options?: { preview?: boolean }) => {
|
|
169
|
+
const choirsToRun = choirId
|
|
170
|
+
? [choirId]
|
|
171
|
+
: ["seraphim", "cherubim", "thrones", "dominions", "virtues", "powers", "principalities", "archangels", "angels"];
|
|
172
|
+
|
|
173
|
+
if (choirId) {
|
|
174
|
+
const choir = CHOIRS[choirId];
|
|
175
|
+
if (!choir) {
|
|
176
|
+
console.error(`Unknown choir: ${choirId}`);
|
|
177
|
+
console.log("Available: seraphim, cherubim, thrones, dominions, virtues, powers, principalities, archangels, angels");
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log("");
|
|
183
|
+
if (!choirId) {
|
|
184
|
+
console.log("šµ Running all Nine Choirs in cascade order...");
|
|
185
|
+
console.log("");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
for (const id of choirsToRun) {
|
|
189
|
+
const choir = CHOIRS[id];
|
|
190
|
+
if (!choir) continue;
|
|
191
|
+
|
|
192
|
+
console.log(`Running ${choir.name}...`);
|
|
193
|
+
|
|
194
|
+
// Preview mode - just show the prompt
|
|
195
|
+
if (options?.preview) {
|
|
196
|
+
console.log(` Prompt: ${choir.prompt.slice(0, 100)}...`);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Try gateway-connected runAgentTurn first (available when loaded as plugin)
|
|
201
|
+
if (typeof api.runAgentTurn === 'function') {
|
|
202
|
+
try {
|
|
203
|
+
const result = await api.runAgentTurn({
|
|
204
|
+
sessionLabel: `chorus:${id}`,
|
|
205
|
+
message: choir.prompt,
|
|
206
|
+
isolated: true,
|
|
207
|
+
timeoutSeconds: 300,
|
|
208
|
+
});
|
|
209
|
+
console.log(` ā ${choir.name} complete`);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
console.error(` ā ${choir.name} failed:`, err);
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
// CLI context: use openclaw agent for direct execution via gateway
|
|
215
|
+
try {
|
|
216
|
+
const result = spawnSync('openclaw', [
|
|
217
|
+
'agent',
|
|
218
|
+
'--session-id', `chorus:${id}`,
|
|
219
|
+
'--message', choir.prompt,
|
|
220
|
+
'--json',
|
|
221
|
+
], {
|
|
222
|
+
encoding: 'utf-8',
|
|
223
|
+
timeout: 300000, // 5 min
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
if (result.status === 0) {
|
|
227
|
+
try {
|
|
228
|
+
const json = JSON.parse(result.stdout || '{}');
|
|
229
|
+
const text = json.result?.payloads?.[0]?.text || '';
|
|
230
|
+
const duration = json.result?.meta?.durationMs || 0;
|
|
231
|
+
console.log(` ā ${choir.name} complete (${(duration/1000).toFixed(1)}s)`);
|
|
232
|
+
if (text) {
|
|
233
|
+
const preview = text.slice(0, 150).replace(/\n/g, ' ');
|
|
234
|
+
console.log(` ${preview}${text.length > 150 ? '...' : ''}`);
|
|
235
|
+
}
|
|
236
|
+
} catch {
|
|
237
|
+
console.log(` ā ${choir.name} complete`);
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
const errMsg = result.stderr || result.stdout || 'Unknown error';
|
|
241
|
+
if (errMsg.includes('ECONNREFUSED') || errMsg.includes('connect')) {
|
|
242
|
+
console.log(` ā Gateway not running. Start with: openclaw gateway start`);
|
|
243
|
+
} else {
|
|
244
|
+
console.error(` ā ${choir.name} failed:`, errMsg.trim().slice(0, 200));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} catch (err: any) {
|
|
248
|
+
console.error(` ā ${choir.name} failed:`, err.message || err);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
console.log("");
|
|
254
|
+
if (!choirId) {
|
|
255
|
+
console.log("šµ All choirs scheduled.");
|
|
256
|
+
}
|
|
257
|
+
console.log("");
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Metrics command
|
|
261
|
+
const metricsCmd = program.command("metrics").description("View CHORUS execution metrics");
|
|
262
|
+
|
|
263
|
+
metricsCmd
|
|
264
|
+
.command("today")
|
|
265
|
+
.description("Show today's metrics")
|
|
266
|
+
.action(() => {
|
|
267
|
+
const metrics = getTodayMetrics();
|
|
268
|
+
if (!metrics) {
|
|
269
|
+
console.log("\nNo metrics recorded for today yet.\n");
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
console.log("");
|
|
273
|
+
console.log(formatMetricsSummary(metrics));
|
|
274
|
+
console.log("");
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
metricsCmd
|
|
278
|
+
.command("week")
|
|
279
|
+
.description("Show weekly summary")
|
|
280
|
+
.action(() => {
|
|
281
|
+
console.log("");
|
|
282
|
+
console.log(formatWeeklySummary());
|
|
283
|
+
console.log("");
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
metricsCmd
|
|
287
|
+
.command("date <date>")
|
|
288
|
+
.description("Show metrics for a specific date (YYYY-MM-DD)")
|
|
289
|
+
.action((date: string) => {
|
|
290
|
+
const metrics = getMetricsForDate(date);
|
|
291
|
+
if (!metrics) {
|
|
292
|
+
console.log(`\nNo metrics recorded for ${date}.\n`);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
console.log("");
|
|
296
|
+
console.log(formatMetricsSummary(metrics));
|
|
297
|
+
console.log("");
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
metricsCmd
|
|
301
|
+
.command("rate <date> <score>")
|
|
302
|
+
.description("Set quality score (1-5) for a date")
|
|
303
|
+
.option("-n, --notes <notes>", "Add notes")
|
|
304
|
+
.action((date: string, score: string, options: { notes?: string }) => {
|
|
305
|
+
const scoreNum = parseInt(score, 10);
|
|
306
|
+
if (isNaN(scoreNum) || scoreNum < 1 || scoreNum > 5) {
|
|
307
|
+
console.error("Score must be 1-5");
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
setQualityScore(date, scoreNum, options.notes);
|
|
311
|
+
console.log(`\nā Quality score for ${date} set to ${scoreNum}/5\n`);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
metricsCmd
|
|
315
|
+
.command("totals")
|
|
316
|
+
.description("Show all-time totals")
|
|
317
|
+
.action(() => {
|
|
318
|
+
const totals = getTotals();
|
|
319
|
+
console.log("");
|
|
320
|
+
console.log("š CHORUS All-Time Totals");
|
|
321
|
+
console.log("ā".repeat(40));
|
|
322
|
+
console.log(` Total Runs: ${totals.allTimeRuns.toLocaleString()}`);
|
|
323
|
+
console.log(` Successes: ${totals.allTimeSuccesses.toLocaleString()} (${totals.allTimeRuns > 0 ? ((totals.allTimeSuccesses / totals.allTimeRuns) * 100).toFixed(1) : 0}%)`);
|
|
324
|
+
console.log(` Findings: ${totals.allTimeFindings}`);
|
|
325
|
+
console.log(` Alerts: ${totals.allTimeAlerts}`);
|
|
326
|
+
console.log(` Improvements: ${totals.allTimeImprovements}`);
|
|
327
|
+
console.log("");
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
metricsCmd
|
|
331
|
+
.command("purposes")
|
|
332
|
+
.description("Show metrics for purpose-derived research")
|
|
333
|
+
.action(() => {
|
|
334
|
+
const todayMetrics = getTodayMetrics();
|
|
335
|
+
if (!todayMetrics) {
|
|
336
|
+
console.log("\nNo metrics recorded for today yet.\n");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Filter executions for purpose-derived research
|
|
341
|
+
const purposeExecs = todayMetrics.executions.filter(e => e.choirId.startsWith("purpose:"));
|
|
342
|
+
|
|
343
|
+
console.log("");
|
|
344
|
+
console.log("š Purpose Research Metrics ā Today");
|
|
345
|
+
console.log("ā".repeat(40));
|
|
346
|
+
console.log(` Total runs: ${purposeExecs.length}`);
|
|
347
|
+
console.log(` Successful: ${purposeExecs.filter(e => e.success).length}`);
|
|
348
|
+
console.log(` Findings: ${purposeExecs.reduce((sum, e) => sum + (e.findings || 0), 0)}`);
|
|
349
|
+
console.log(` Alerts: ${purposeExecs.reduce((sum, e) => sum + (e.alerts || 0), 0)}`);
|
|
350
|
+
console.log("");
|
|
351
|
+
|
|
352
|
+
if (purposeExecs.length > 0) {
|
|
353
|
+
console.log("By purpose:");
|
|
354
|
+
console.log("ā".repeat(40));
|
|
355
|
+
const byPurpose = new Map<string, typeof purposeExecs>();
|
|
356
|
+
for (const exec of purposeExecs) {
|
|
357
|
+
const purposeId = exec.choirId.replace("purpose:", "");
|
|
358
|
+
if (!byPurpose.has(purposeId)) byPurpose.set(purposeId, []);
|
|
359
|
+
byPurpose.get(purposeId)!.push(exec);
|
|
360
|
+
}
|
|
361
|
+
for (const [purposeId, execs] of byPurpose) {
|
|
362
|
+
const findings = execs.reduce((sum, e) => sum + (e.findings || 0), 0);
|
|
363
|
+
const avgDuration = execs.reduce((sum, e) => sum + e.durationMs, 0) / execs.length;
|
|
364
|
+
console.log(` ${purposeId}: ${execs.length} runs, ${findings} findings, ${(avgDuration/1000).toFixed(1)}s avg`);
|
|
365
|
+
}
|
|
366
|
+
console.log("");
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// Daemon commands
|
|
371
|
+
const daemonCmd = program.command("daemon").description("Autonomous attention daemon");
|
|
372
|
+
|
|
373
|
+
daemonCmd
|
|
374
|
+
.command("status")
|
|
375
|
+
.description("Show daemon status")
|
|
376
|
+
.action(() => {
|
|
377
|
+
console.log("");
|
|
378
|
+
console.log("šļø CHORUS Daemon");
|
|
379
|
+
console.log("ā".repeat(40));
|
|
380
|
+
console.log(` Enabled: ${daemonConfig.enabled ? "ā
yes" : "ā no"}`);
|
|
381
|
+
console.log(` Threshold: ${daemonConfig.thinkThreshold}`);
|
|
382
|
+
console.log(` Poll interval: ${daemonConfig.pollIntervalMs / 1000}s`);
|
|
383
|
+
console.log(` Quiet hours: ${daemonConfig.quietHoursStart}:00 - ${daemonConfig.quietHoursEnd}:00`);
|
|
384
|
+
console.log(` Inbox: ${getInboxPath()}`);
|
|
385
|
+
if (daemon) {
|
|
386
|
+
console.log(` Queue size: ${daemon.getQueueSize()}`);
|
|
387
|
+
}
|
|
388
|
+
console.log("");
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
daemonCmd
|
|
392
|
+
.command("queue")
|
|
393
|
+
.description("Show current attention queue")
|
|
394
|
+
.action(() => {
|
|
395
|
+
if (!daemon) {
|
|
396
|
+
console.log("\nDaemon not running.\n");
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
const queue = daemon.getQueue();
|
|
400
|
+
console.log("");
|
|
401
|
+
console.log("šļø Attention Queue");
|
|
402
|
+
console.log("ā".repeat(50));
|
|
403
|
+
if (queue.length === 0) {
|
|
404
|
+
console.log(" (empty)");
|
|
405
|
+
} else {
|
|
406
|
+
for (const item of queue) {
|
|
407
|
+
console.log(` [${item.salienceScore}] ${item.source}: ${item.content.slice(0, 60)}...`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
console.log("");
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
daemonCmd
|
|
414
|
+
.command("poll")
|
|
415
|
+
.description("Force poll all senses now")
|
|
416
|
+
.action(async () => {
|
|
417
|
+
if (!daemon) {
|
|
418
|
+
console.log("\nDaemon not running.\n");
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
console.log("\nPolling senses...");
|
|
422
|
+
await daemon.forcePoll();
|
|
423
|
+
console.log(`Queue size: ${daemon.getQueueSize()}\n`);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
daemonCmd
|
|
427
|
+
.command("process")
|
|
428
|
+
.description("Process highest priority item now")
|
|
429
|
+
.action(async () => {
|
|
430
|
+
if (!daemon) {
|
|
431
|
+
console.log("\nDaemon not running.\n");
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
const size = daemon.getQueueSize();
|
|
435
|
+
if (size === 0) {
|
|
436
|
+
console.log("\nQueue empty.\n");
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
console.log("\nProcessing top item...");
|
|
440
|
+
await daemon.forceProcess();
|
|
441
|
+
console.log("Done.\n");
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Research commands
|
|
445
|
+
const researchCmd = program.command("research").description("Purpose-derived research");
|
|
446
|
+
|
|
447
|
+
researchCmd
|
|
448
|
+
.command("status")
|
|
449
|
+
.description("Show research scheduler status")
|
|
450
|
+
.action(async () => {
|
|
451
|
+
const purposes = await loadPurposes();
|
|
452
|
+
const researchPurposes = purposes.filter(p =>
|
|
453
|
+
p.progress < 100 &&
|
|
454
|
+
p.research?.enabled !== false &&
|
|
455
|
+
(p.criteria?.length || p.research?.domains?.length)
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
console.log("");
|
|
459
|
+
console.log("š¬ Purpose Research Status");
|
|
460
|
+
console.log("ā".repeat(50));
|
|
461
|
+
console.log(` Enabled: ${purposeResearchConfig.enabled ? "ā
yes" : "ā no"}`);
|
|
462
|
+
console.log(` Daily cap: ${purposeResearchConfig.dailyRunCap}`);
|
|
463
|
+
console.log(` Default freq: ${purposeResearchConfig.defaultFrequency}/day`);
|
|
464
|
+
if (purposeResearch) {
|
|
465
|
+
console.log(` Today's runs: ${purposeResearch.getDailyRunCount()}/${purposeResearch.getDailyCap()}`);
|
|
466
|
+
}
|
|
467
|
+
console.log(` Active purposes: ${researchPurposes.length}`);
|
|
468
|
+
console.log("");
|
|
469
|
+
|
|
470
|
+
if (researchPurposes.length > 0) {
|
|
471
|
+
console.log("Research-enabled purposes:");
|
|
472
|
+
console.log("ā".repeat(50));
|
|
473
|
+
for (const purpose of researchPurposes) {
|
|
474
|
+
const freq = purpose.research?.frequency ?? purposeResearchConfig.defaultFrequency;
|
|
475
|
+
const lastRun = purpose.research?.lastRun
|
|
476
|
+
? new Date(purpose.research.lastRun).toLocaleString()
|
|
477
|
+
: "never";
|
|
478
|
+
const runCount = purpose.research?.runCount ?? 0;
|
|
479
|
+
console.log(` ${purpose.name}`);
|
|
480
|
+
console.log(` Frequency: ${freq}/day | Last: ${lastRun} | Runs: ${runCount}`);
|
|
481
|
+
}
|
|
482
|
+
console.log("");
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
researchCmd
|
|
487
|
+
.command("run <purposeId>")
|
|
488
|
+
.description("Manually trigger research for a purpose")
|
|
489
|
+
.action(async (purposeId: string) => {
|
|
490
|
+
if (!purposeResearch) {
|
|
491
|
+
console.log("\nPurpose research not enabled.\n");
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
console.log(`\nRunning research for "${purposeId}"...`);
|
|
495
|
+
try {
|
|
496
|
+
await purposeResearch.forceRun(purposeId);
|
|
497
|
+
console.log("Done.\n");
|
|
498
|
+
} catch (err: any) {
|
|
499
|
+
console.error(`\nā ${err.message}\n`);
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
researchCmd
|
|
504
|
+
.command("list")
|
|
505
|
+
.description("List purposes with research enabled")
|
|
506
|
+
.action(async () => {
|
|
507
|
+
const purposes = await loadPurposes();
|
|
508
|
+
const researchPurposes = purposes.filter(p =>
|
|
509
|
+
p.research?.enabled !== false &&
|
|
510
|
+
(p.criteria?.length || p.research?.domains?.length)
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
console.log("");
|
|
514
|
+
console.log("š¬ Research-Enabled Purposes");
|
|
515
|
+
console.log("ā".repeat(50));
|
|
516
|
+
|
|
517
|
+
if (researchPurposes.length === 0) {
|
|
518
|
+
console.log(" No purposes with research enabled.");
|
|
519
|
+
console.log(" Add criteria to a purpose to enable research.");
|
|
520
|
+
} else {
|
|
521
|
+
for (const purpose of researchPurposes) {
|
|
522
|
+
const status = purpose.progress >= 100 ? "ā" : "ā";
|
|
523
|
+
const freq = purpose.research?.frequency ?? purposeResearchConfig.defaultFrequency;
|
|
524
|
+
console.log(` ${status} ${purpose.name} (${freq}/day)`);
|
|
525
|
+
if (purpose.criteria?.length) {
|
|
526
|
+
for (const c of purpose.criteria.slice(0, 3)) {
|
|
527
|
+
console.log(` ⢠${c}`);
|
|
528
|
+
}
|
|
529
|
+
if (purpose.criteria.length > 3) {
|
|
530
|
+
console.log(` ... +${purpose.criteria.length - 3} more`);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
console.log("");
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// Purpose commands
|
|
539
|
+
const purposeCmd = program.command("purpose").description("Manage autonomous purposes");
|
|
540
|
+
|
|
541
|
+
purposeCmd
|
|
542
|
+
.command("list")
|
|
543
|
+
.description("List all purposes")
|
|
544
|
+
.action(async () => {
|
|
545
|
+
const purposes = await loadPurposes();
|
|
546
|
+
console.log("");
|
|
547
|
+
console.log(formatPurposesList(purposes));
|
|
548
|
+
console.log("");
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
purposeCmd
|
|
552
|
+
.command("add <id> <name>")
|
|
553
|
+
.description("Add a new purpose")
|
|
554
|
+
.option("-d, --deadline <date>", "Deadline (YYYY-MM-DD or ISO)")
|
|
555
|
+
.option("-c, --criteria <items>", "Success criteria (comma-separated)")
|
|
556
|
+
.option("--domains <items>", "Research domains (comma-separated)")
|
|
557
|
+
.option("--frequency <n>", "Research runs per day")
|
|
558
|
+
.option("--no-research", "Disable auto-research for this purpose")
|
|
559
|
+
.option("--curiosity <n>", "Curiosity score 0-100 (for exploration purposes)")
|
|
560
|
+
.action(async (id: string, name: string, options: any) => {
|
|
561
|
+
try {
|
|
562
|
+
const criteria = options.criteria
|
|
563
|
+
? options.criteria.split(",").map((s: string) => s.trim())
|
|
564
|
+
: undefined;
|
|
565
|
+
const domains = options.domains
|
|
566
|
+
? options.domains.split(",").map((s: string) => s.trim())
|
|
567
|
+
: undefined;
|
|
568
|
+
|
|
569
|
+
// Build research config if criteria or domains provided
|
|
570
|
+
let research = undefined;
|
|
571
|
+
if (options.research === false) {
|
|
572
|
+
research = { enabled: false };
|
|
573
|
+
} else if (criteria?.length || domains?.length) {
|
|
574
|
+
research = {
|
|
575
|
+
enabled: true,
|
|
576
|
+
domains,
|
|
577
|
+
frequency: options.frequency ? parseInt(options.frequency) : undefined,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
const purpose = await addPurpose({
|
|
582
|
+
id,
|
|
583
|
+
name,
|
|
584
|
+
deadline: options.deadline ? Date.parse(options.deadline) : undefined,
|
|
585
|
+
criteria,
|
|
586
|
+
curiosity: options.curiosity ? parseInt(options.curiosity) : undefined,
|
|
587
|
+
research,
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
console.log(`\nā Purpose added: ${purpose.name}`);
|
|
591
|
+
if (purpose.research?.enabled) {
|
|
592
|
+
const freq = purpose.research.frequency ?? purposeResearchConfig.defaultFrequency;
|
|
593
|
+
console.log(` Research: ${freq}/day`);
|
|
594
|
+
if (purpose.research.domains?.length) {
|
|
595
|
+
console.log(` Domains: ${purpose.research.domains.join(", ")}`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
console.log("");
|
|
599
|
+
} catch (err: any) {
|
|
600
|
+
console.error(`\nā ${err.message}\n`);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
purposeCmd
|
|
605
|
+
.command("progress <id> <percent>")
|
|
606
|
+
.description("Update purpose progress (0-100)")
|
|
607
|
+
.action(async (id: string, percent: string) => {
|
|
608
|
+
const progress = parseInt(percent);
|
|
609
|
+
if (isNaN(progress) || progress < 0 || progress > 100) {
|
|
610
|
+
console.error("\nProgress must be 0-100\n");
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const purpose = await updatePurpose(id, { progress });
|
|
614
|
+
if (purpose) {
|
|
615
|
+
console.log(`\nā ${purpose.name}: ${progress}%\n`);
|
|
616
|
+
} else {
|
|
617
|
+
console.error(`\nā Purpose "${id}" not found\n`);
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
purposeCmd
|
|
622
|
+
.command("done <id>")
|
|
623
|
+
.description("Mark purpose as complete (100%)")
|
|
624
|
+
.action(async (id: string) => {
|
|
625
|
+
const purpose = await updatePurpose(id, { progress: 100 });
|
|
626
|
+
if (purpose) {
|
|
627
|
+
console.log(`\nā ${purpose.name}: Complete!\n`);
|
|
628
|
+
} else {
|
|
629
|
+
console.error(`\nā Purpose "${id}" not found\n`);
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
purposeCmd
|
|
634
|
+
.command("remove <id>")
|
|
635
|
+
.description("Remove a purpose")
|
|
636
|
+
.action(async (id: string) => {
|
|
637
|
+
const removed = await removePurpose(id);
|
|
638
|
+
if (removed) {
|
|
639
|
+
console.log(`\nā Purpose "${id}" removed\n`);
|
|
640
|
+
} else {
|
|
641
|
+
console.error(`\nā Purpose "${id}" not found\n`);
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
purposeCmd
|
|
646
|
+
.command("research <id>")
|
|
647
|
+
.description("Configure research for a purpose")
|
|
648
|
+
.option("--enable", "Enable research")
|
|
649
|
+
.option("--disable", "Disable research")
|
|
650
|
+
.option("--domains <items>", "Set research domains (comma-separated)")
|
|
651
|
+
.option("--frequency <n>", "Set research frequency (runs/day)")
|
|
652
|
+
.option("--criteria <items>", "Set success criteria (comma-separated)")
|
|
653
|
+
.action(async (id: string, options: any) => {
|
|
654
|
+
const purposes = await loadPurposes();
|
|
655
|
+
const purpose = purposes.find(p => p.id === id);
|
|
656
|
+
if (!purpose) {
|
|
657
|
+
console.error(`\nā Purpose "${id}" not found\n`);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const updates: any = {};
|
|
662
|
+
|
|
663
|
+
if (options.criteria) {
|
|
664
|
+
updates.criteria = options.criteria.split(",").map((s: string) => s.trim());
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const researchUpdates: any = { ...purpose.research };
|
|
668
|
+
|
|
669
|
+
if (options.enable) {
|
|
670
|
+
researchUpdates.enabled = true;
|
|
671
|
+
} else if (options.disable) {
|
|
672
|
+
researchUpdates.enabled = false;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (options.domains) {
|
|
676
|
+
researchUpdates.domains = options.domains.split(",").map((s: string) => s.trim());
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (options.frequency) {
|
|
680
|
+
researchUpdates.frequency = parseInt(options.frequency);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
updates.research = researchUpdates;
|
|
684
|
+
|
|
685
|
+
const updated = await updatePurpose(id, updates);
|
|
686
|
+
if (updated) {
|
|
687
|
+
console.log(`\nā ${updated.name} research config updated`);
|
|
688
|
+
if (updated.research?.enabled === false) {
|
|
689
|
+
console.log(" Research: disabled");
|
|
690
|
+
} else {
|
|
691
|
+
const freq = updated.research?.frequency ?? purposeResearchConfig.defaultFrequency;
|
|
692
|
+
console.log(` Research: ${freq}/day`);
|
|
693
|
+
if (updated.research?.domains?.length) {
|
|
694
|
+
console.log(` Domains: ${updated.research.domains.join(", ")}`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
console.log("");
|
|
698
|
+
}
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
// Inbox command (shortcut)
|
|
702
|
+
program
|
|
703
|
+
.command("inbox")
|
|
704
|
+
.description("Show inbox path for daemon signals")
|
|
705
|
+
.action(() => {
|
|
706
|
+
console.log(`\nDrop files here to trigger daemon attention:\n ${getInboxPath()}\n`);
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
}, { commands: ["chorus"] });
|
|
710
|
+
|
|
711
|
+
api.logger.info("[chorus] šµ Registered");
|
|
712
|
+
},
|
|
713
|
+
};
|
|
714
|
+
|
|
715
|
+
function printChoir(id: string, config: any) {
|
|
716
|
+
const choir = CHOIRS[id];
|
|
717
|
+
if (!choir) return;
|
|
718
|
+
const enabled = config.choirs.overrides[id] !== false;
|
|
719
|
+
const status = enabled ? "ā
" : "ā";
|
|
720
|
+
const freq = formatFrequency(choir).padEnd(8);
|
|
721
|
+
console.log(` ${status} ${choir.name.padEnd(16)} ${freq} ${choir.function}`);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
export default plugin;
|