create-yonderclaw 1.0.0
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 +44 -0
- package/README.md +288 -0
- package/bin/create-yonderclaw.mjs +43 -0
- package/docs/assets/favicon.png +0 -0
- package/docs/assets/metaclaw-banner.svg +86 -0
- package/docs/assets/qis-logo.png +0 -0
- package/docs/assets/yz-favicon.png +0 -0
- package/docs/assets/yz-logo.png +0 -0
- package/docs/index.html +1155 -0
- package/installer/assets/favicon.png +0 -0
- package/installer/auto-start.ts +330 -0
- package/installer/brand.ts +115 -0
- package/installer/core-scaffold.ts +448 -0
- package/installer/dashboard-generator.ts +657 -0
- package/installer/detect.ts +129 -0
- package/installer/index.ts +355 -0
- package/installer/module-loader.ts +412 -0
- package/installer/modules/boardroom/boardroom/client.ts.txt +201 -0
- package/installer/modules/boardroom/boardroom/db.ts.txt +322 -0
- package/installer/modules/boardroom/boardroom/meeting-agent.ts.txt +129 -0
- package/installer/modules/boardroom/boardroom/meeting-scheduler.ts.txt +194 -0
- package/installer/modules/boardroom/boardroom/server.ts.txt +473 -0
- package/installer/modules/boardroom/boardroom/start-boardroom.bat.txt +26 -0
- package/installer/modules/boardroom/boardroom/summons.ts.txt +76 -0
- package/installer/modules/boardroom/boardroom/turn-v2.ts.txt +172 -0
- package/installer/modules/boardroom/boardroom/turn.ts.txt +208 -0
- package/installer/modules/boardroom/boardroom/types.ts.txt +100 -0
- package/installer/modules/boardroom/metaclaw-module.json +35 -0
- package/installer/modules/boardroom/scripts/meeting-check.bat.txt +38 -0
- package/installer/modules/core/metaclaw-module.json +51 -0
- package/installer/modules/core/src/db.ts.txt +277 -0
- package/installer/modules/core/src/health-check.ts.txt +128 -0
- package/installer/modules/core/src/observability.ts.txt +20 -0
- package/installer/modules/core/src/safety.ts.txt +26 -0
- package/installer/modules/core/src/scan-capabilities.ts.txt +196 -0
- package/installer/modules/core/src/self-improve.ts.txt +48 -0
- package/installer/modules/core/src/self-update.ts.txt +345 -0
- package/installer/modules/core/src/sync-context.ts.txt +133 -0
- package/installer/modules/core/src/tasks.ts.txt +159 -0
- package/installer/modules/custom/metaclaw-module.json +15 -0
- package/installer/modules/custom/src/agent-custom.ts.txt +100 -0
- package/installer/modules/dashboard/metaclaw-module.json +23 -0
- package/installer/modules/dashboard/scripts/build-dashboard.cjs.txt +51 -0
- package/installer/modules/dashboard/src/update-dashboard.ts.txt +126 -0
- package/installer/modules/outreach/metaclaw-module.json +29 -0
- package/installer/modules/outreach/src/agent-outreach.ts.txt +193 -0
- package/installer/modules/outreach/src/inbox-agent.ts.txt +283 -0
- package/installer/modules/outreach/src/morning-report.ts.txt +124 -0
- package/installer/modules/research/metaclaw-module.json +15 -0
- package/installer/modules/research/src/agent-research.ts.txt +127 -0
- package/installer/modules/scheduler/metaclaw-module.json +27 -0
- package/installer/modules/scheduler/scripts/agent-cycle.bat.txt +85 -0
- package/installer/modules/scheduler/scripts/detect-session.bat.txt +41 -0
- package/installer/modules/scheduler/scripts/launch.bat.txt +120 -0
- package/installer/modules/scheduler/src/cron-manager.ts.txt +273 -0
- package/installer/modules/social/metaclaw-module.json +15 -0
- package/installer/modules/social/src/agent-social.ts.txt +110 -0
- package/installer/modules/support/metaclaw-module.json +15 -0
- package/installer/modules/support/src/agent-support.ts.txt +60 -0
- package/installer/modules/swarm/metaclaw-module.json +25 -0
- package/installer/modules/swarm/swarm/dht-client.ts.txt +376 -0
- package/installer/modules/swarm/swarm/relay-server.ts.txt +348 -0
- package/installer/modules/swarm/swarm/swarm-client.ts.txt +303 -0
- package/installer/modules/swarm/swarm/types.ts.txt +51 -0
- package/installer/modules/voice/metaclaw-module.json +16 -0
- package/installer/questionnaire.ts +277 -0
- package/installer/research.ts +258 -0
- package/installer/scaffold-from-config.ts +270 -0
- package/installer/task-generator.ts +324 -0
- package/installer/templates/agent-custom.ts.txt +100 -0
- package/installer/templates/agent-cycle.bat.txt +19 -0
- package/installer/templates/agent-outreach.ts.txt +193 -0
- package/installer/templates/agent-research.ts.txt +127 -0
- package/installer/templates/agent-social.ts.txt +110 -0
- package/installer/templates/agent-support.ts.txt +60 -0
- package/installer/templates/build-dashboard.cjs.txt +51 -0
- package/installer/templates/cron-manager.ts.txt +273 -0
- package/installer/templates/dashboard.html.txt +450 -0
- package/installer/templates/db.ts.txt +277 -0
- package/installer/templates/detect-session.bat.txt +41 -0
- package/installer/templates/health-check.ts.txt +128 -0
- package/installer/templates/inbox-agent.ts.txt +283 -0
- package/installer/templates/launch.bat.txt +120 -0
- package/installer/templates/morning-report.ts.txt +124 -0
- package/installer/templates/observability.ts.txt +20 -0
- package/installer/templates/safety.ts.txt +26 -0
- package/installer/templates/self-improve.ts.txt +48 -0
- package/installer/templates/self-update.ts.txt +345 -0
- package/installer/templates/state.json.txt +33 -0
- package/installer/templates/system-context.json.txt +33 -0
- package/installer/templates/update-dashboard.ts.txt +126 -0
- package/package.json +31 -0
- package/setup.bat +178 -0
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MetaClaw Module Loader
|
|
3
|
+
*
|
|
4
|
+
* Reads module manifests, resolves dependencies, copies templates,
|
|
5
|
+
* merges package.json scripts/deps, assembles CLAUDE.md sections.
|
|
6
|
+
*
|
|
7
|
+
* This replaces the hardcoded file copies in scaffold-from-config.ts
|
|
8
|
+
* with a manifest-driven system that supports pluggable modules.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
|
|
14
|
+
export interface ModuleManifest {
|
|
15
|
+
name: string;
|
|
16
|
+
displayName: string;
|
|
17
|
+
version: string;
|
|
18
|
+
description: string;
|
|
19
|
+
category: string;
|
|
20
|
+
alwaysInstall?: boolean;
|
|
21
|
+
engines?: { metaclaw: string };
|
|
22
|
+
requires?: { modules?: string[]; env?: string[]; bins?: string[] };
|
|
23
|
+
contributes: {
|
|
24
|
+
[section: string]: any;
|
|
25
|
+
npmScripts?: Record<string, string>;
|
|
26
|
+
dependencies?: Record<string, string>;
|
|
27
|
+
claudeMd?: string[];
|
|
28
|
+
directories?: string[];
|
|
29
|
+
};
|
|
30
|
+
placeholders?: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface InstallConfig {
|
|
34
|
+
clawType: string;
|
|
35
|
+
agentName: string;
|
|
36
|
+
projectName?: string;
|
|
37
|
+
[key: string]: any;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface LoadedModule {
|
|
41
|
+
manifest: ModuleManifest;
|
|
42
|
+
dir: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Load a module manifest from a directory
|
|
47
|
+
*/
|
|
48
|
+
export function loadManifest(moduleDir: string): LoadedModule {
|
|
49
|
+
const manifestPath = path.join(moduleDir, "metaclaw-module.json");
|
|
50
|
+
if (!fs.existsSync(manifestPath)) {
|
|
51
|
+
throw new Error(`No metaclaw-module.json in ${moduleDir}`);
|
|
52
|
+
}
|
|
53
|
+
const manifest: ModuleManifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
|
|
54
|
+
return { manifest, dir: moduleDir };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Discover all modules in a directory
|
|
59
|
+
*/
|
|
60
|
+
export function discoverModules(modulesDir: string): LoadedModule[] {
|
|
61
|
+
if (!fs.existsSync(modulesDir)) return [];
|
|
62
|
+
const dirs = fs.readdirSync(modulesDir, { withFileTypes: true })
|
|
63
|
+
.filter(d => d.isDirectory())
|
|
64
|
+
.map(d => path.join(modulesDir, d.name));
|
|
65
|
+
|
|
66
|
+
const modules: LoadedModule[] = [];
|
|
67
|
+
for (const dir of dirs) {
|
|
68
|
+
try { modules.push(loadManifest(dir)); } catch {}
|
|
69
|
+
}
|
|
70
|
+
return modules;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Determine which modules to install based on config
|
|
75
|
+
*/
|
|
76
|
+
export function resolveModulesToInstall(allModules: LoadedModule[], config: InstallConfig): LoadedModule[] {
|
|
77
|
+
const toInstall: string[] = [];
|
|
78
|
+
|
|
79
|
+
// Always install modules marked as such
|
|
80
|
+
for (const m of allModules) {
|
|
81
|
+
if (m.manifest.alwaysInstall) toInstall.push(m.manifest.name);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Install the agent-type module
|
|
85
|
+
const agentType = config.clawType || "custom";
|
|
86
|
+
if (!toInstall.includes(agentType)) toInstall.push(agentType);
|
|
87
|
+
|
|
88
|
+
// Resolve dependencies (simple topological sort)
|
|
89
|
+
const resolved: string[] = [];
|
|
90
|
+
const visited = new Set<string>();
|
|
91
|
+
|
|
92
|
+
function resolve(name: string) {
|
|
93
|
+
if (visited.has(name)) return;
|
|
94
|
+
visited.add(name);
|
|
95
|
+
const mod = allModules.find(m => m.manifest.name === name);
|
|
96
|
+
if (!mod) return;
|
|
97
|
+
for (const dep of mod.manifest.requires?.modules || []) {
|
|
98
|
+
resolve(dep);
|
|
99
|
+
}
|
|
100
|
+
resolved.push(name);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const name of toInstall) resolve(name);
|
|
104
|
+
return resolved.map(name => allModules.find(m => m.manifest.name === name)!).filter(Boolean);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Build placeholder replacement map from config
|
|
109
|
+
*/
|
|
110
|
+
export function buildPlaceholders(config: InstallConfig, outputDir: string): Record<string, string> {
|
|
111
|
+
const agentName = config.agentName || "Atlas";
|
|
112
|
+
const sessionName = agentName.toLowerCase().replace(/[^a-z0-9]/g, "-");
|
|
113
|
+
const encodedPath = outputDir.replace(/[:/\\]/g, "-").replace(/^-/, "");
|
|
114
|
+
const selfUpdateMinutes = String((parseInt(config.updateInterval) || 6) * 60);
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
"__AGENT_NAME__": agentName,
|
|
118
|
+
"__SESSION_NAME__": sessionName,
|
|
119
|
+
"__CLAW_TYPE__": config.clawType || "custom",
|
|
120
|
+
"__PROJECT_DIR__": outputDir,
|
|
121
|
+
"__ENCODED_PATH__": encodedPath,
|
|
122
|
+
"__SELF_UPDATE_INTERVAL__": selfUpdateMinutes,
|
|
123
|
+
"__TIMESTAMP__": new Date().toISOString(),
|
|
124
|
+
"__SAFETY_CONFIG__": JSON.stringify({ maxActionsPerDay: 50, maxActionsPerHour: 10, circuitBreakerThreshold: 0.05 }, null, 2),
|
|
125
|
+
"__COORDINATOR_URL__": config.coordinatorUrl || "http://localhost:7890",
|
|
126
|
+
"__RELAY_URL__": config.relayUrl || "http://localhost:7891",
|
|
127
|
+
"__ENABLE_LOCAL__": String(config.enableLocalSwarm !== false),
|
|
128
|
+
"__ENABLE_GLOBAL__": String(config.enableGlobalSwarm !== false),
|
|
129
|
+
"__INBOX_ROOT__": config.inboxRoot || path.join(outputDir, "data", "inbox"),
|
|
130
|
+
"__LOCAL_BUCKETS_PATH__": config.localBucketsPath || path.join(outputDir, "data", "buckets"),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Copy a template file with placeholder replacement
|
|
136
|
+
*/
|
|
137
|
+
export function copyTemplate(srcPath: string, destPath: string, placeholders: Record<string, string>): void {
|
|
138
|
+
if (!fs.existsSync(srcPath)) {
|
|
139
|
+
console.warn(`Template not found: ${srcPath}`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
let content = fs.readFileSync(srcPath, "utf-8");
|
|
143
|
+
for (const [key, value] of Object.entries(placeholders)) {
|
|
144
|
+
content = content.replace(new RegExp(key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), value);
|
|
145
|
+
}
|
|
146
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
147
|
+
fs.writeFileSync(destPath, content);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Process a module's contributes section — copy all template files
|
|
152
|
+
*/
|
|
153
|
+
export function processModuleContributes(mod: LoadedModule, outputDir: string, placeholders: Record<string, string>): void {
|
|
154
|
+
const { manifest, dir } = mod;
|
|
155
|
+
const skipKeys = new Set(["npmScripts", "dependencies", "claudeMd", "directories", "dashboard", "inject"]);
|
|
156
|
+
|
|
157
|
+
// Create directories
|
|
158
|
+
if (manifest.contributes.directories) {
|
|
159
|
+
for (const d of manifest.contributes.directories) {
|
|
160
|
+
fs.mkdirSync(path.join(outputDir, d), { recursive: true });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Copy template files from each contributes section
|
|
165
|
+
for (const [sectionKey, fileMap] of Object.entries(manifest.contributes)) {
|
|
166
|
+
if (skipKeys.has(sectionKey)) continue;
|
|
167
|
+
if (typeof fileMap !== "object" || Array.isArray(fileMap)) continue;
|
|
168
|
+
|
|
169
|
+
for (const [srcFile, destRelative] of Object.entries(fileMap as Record<string, string>)) {
|
|
170
|
+
const srcPath = path.join(dir, sectionKey, srcFile);
|
|
171
|
+
const destPath = path.join(outputDir, destRelative);
|
|
172
|
+
copyTemplate(srcPath, destPath, placeholders);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Merge npm scripts from all modules into package.json
|
|
179
|
+
*/
|
|
180
|
+
export function mergeNpmScripts(packageJson: any, modules: LoadedModule[]): void {
|
|
181
|
+
for (const mod of modules) {
|
|
182
|
+
const scripts = mod.manifest.contributes.npmScripts;
|
|
183
|
+
if (scripts) {
|
|
184
|
+
Object.assign(packageJson.scripts, scripts);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Merge dependencies from all modules into package.json
|
|
191
|
+
*/
|
|
192
|
+
export function mergeDependencies(packageJson: any, modules: LoadedModule[]): void {
|
|
193
|
+
for (const mod of modules) {
|
|
194
|
+
const deps = mod.manifest.contributes.dependencies;
|
|
195
|
+
if (deps) {
|
|
196
|
+
Object.assign(packageJson.dependencies, deps);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Build CLAUDE.md from module section contributions
|
|
203
|
+
*/
|
|
204
|
+
export function buildClaudeMd(modules: LoadedModule[], config: InstallConfig, systemPrompt: string): string {
|
|
205
|
+
const agentName = config.agentName || "Atlas";
|
|
206
|
+
const clawType = config.clawType || "custom";
|
|
207
|
+
const relayUrl = config.relayUrl || "http://localhost:7891";
|
|
208
|
+
const installedModuleNames = modules.map(m => m.manifest.name);
|
|
209
|
+
|
|
210
|
+
// Collect all section names in order
|
|
211
|
+
const allSections: string[] = [];
|
|
212
|
+
for (const mod of modules) {
|
|
213
|
+
const sections = mod.manifest.contributes.claudeMd || [];
|
|
214
|
+
for (const s of sections) {
|
|
215
|
+
if (!allSections.includes(s)) allSections.push(s);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Render each section
|
|
220
|
+
const sectionRenderers: Record<string, () => string> = {
|
|
221
|
+
"identity": () => [
|
|
222
|
+
`# ${agentName}`,
|
|
223
|
+
`Generated by MetaClaw v3.3.0 | Yonder Zenith LLC | Powered by QIS`,
|
|
224
|
+
"",
|
|
225
|
+
`## Who You Are`,
|
|
226
|
+
`You are ${agentName}, a ${clawType} autonomous AI agent built by MetaClaw.`,
|
|
227
|
+
`You have a full suite of capabilities. You are NOT a basic chatbot — you are an autonomous agent with:`,
|
|
228
|
+
``,
|
|
229
|
+
`### Your Capabilities`,
|
|
230
|
+
`- **Database**: SQLite with WAL mode (src/db.ts) — 11 tables for actions, configs, prompts, metrics`,
|
|
231
|
+
`- **Self-Improvement**: Automatic prompt evolution (src/self-improve.ts, src/self-update.ts)`,
|
|
232
|
+
`- **Safety**: Circuit breaker, rate limiting, daily caps (src/safety.ts)`,
|
|
233
|
+
`- **Observability**: Full action logging and metrics (src/observability.ts)`,
|
|
234
|
+
`- **Health Checks**: System validation (src/health-check.ts)`,
|
|
235
|
+
`- **Dashboard**: Custom HTML command center with real-time metrics (dashboard.html)`,
|
|
236
|
+
`- **Cron Scheduling**: Windows Task Scheduler automation (src/cron-manager.ts)`,
|
|
237
|
+
`- **Boardroom**: Multi-agent meeting system with intelligent turn-taking (boardroom/)`,
|
|
238
|
+
`- **Swarm Intelligence**: QIS bucket network for shared learning (swarm/)`,
|
|
239
|
+
`- **Voice**: Push-to-talk and TTS on dashboard`,
|
|
240
|
+
`- **Task Tracking**: HT/AT system for human and AI task management (data/tasks.json)`,
|
|
241
|
+
``,
|
|
242
|
+
`### Your File Structure`,
|
|
243
|
+
`- CLAUDE.md — this file (your identity and rules)`,
|
|
244
|
+
`- SOUL.md — your principles`,
|
|
245
|
+
`- data/state.json — your current state (read FIRST, update LAST)`,
|
|
246
|
+
`- data/system-context.json — your accumulated learning`,
|
|
247
|
+
`- data/tasks.json — human tasks + AI tasks with dependencies`,
|
|
248
|
+
`- data/modules.json — installed modules`,
|
|
249
|
+
`- data/swarm-config.json — swarm network configuration`,
|
|
250
|
+
`- src/ — your TypeScript source code`,
|
|
251
|
+
`- scripts/ — launch and automation scripts`,
|
|
252
|
+
`- boardroom/ — multi-agent meeting system`,
|
|
253
|
+
`- swarm/ — QIS intelligence network client`,
|
|
254
|
+
`- .claude/settings.json — permission config (auto mode)`,
|
|
255
|
+
``,
|
|
256
|
+
"## System Prompt",
|
|
257
|
+
systemPrompt,
|
|
258
|
+
].join("\n"),
|
|
259
|
+
"first-launch": () => [
|
|
260
|
+
"## FIRST LAUNCH INSTRUCTIONS",
|
|
261
|
+
"On your VERY FIRST session, do ALL of these:",
|
|
262
|
+
"",
|
|
263
|
+
"1. Read memory/CAPABILITIES.md — know what you can do",
|
|
264
|
+
"2. Read data/swarm-config.json — note the global relay URL",
|
|
265
|
+
"3. REGISTER with the global intelligence network (ONE-TIME setup):",
|
|
266
|
+
` Use Bash tool: curl -s -X POST ${relayUrl}/nodes/register -H 'Content-Type: application/json' -d '{\"node_id\":\"mc_anon_${config.agentName?.toLowerCase().replace(/[^a-z0-9]/g, '') || 'agent'}\",\"agent_name\":\"${config.agentName || 'agent'}\"}'`,
|
|
267
|
+
"4. Read data/tasks.json — see your Human Tasks (HTs) and AI Tasks (ATs)",
|
|
268
|
+
"5. Greet your operator with: 'I am [name], your [type] agent. Here is what I need from you to get started:'",
|
|
269
|
+
"6. Show the top 3 priority HTs with their instructions — help the operator complete them",
|
|
270
|
+
"7. Run `npm run health-check` and fix any issues",
|
|
271
|
+
"8. Run `npm run dashboard` to generate your custom dashboard",
|
|
272
|
+
"9. Set up your cron schedule: `npm run crons-setup`",
|
|
273
|
+
"7. As HTs are completed, start executing unblocked ATs",
|
|
274
|
+
"8. Log everything you do to data/logs/ with timestamps and results",
|
|
275
|
+
"",
|
|
276
|
+
"DO NOT wait for all HTs to complete before starting work. Start ATs that are not blocked.",
|
|
277
|
+
"DO NOT dump all tasks at once — progressive disclosure, show 3 at a time.",
|
|
278
|
+
"Your goal: be productive from minute one while guiding the operator through setup at their pace.",
|
|
279
|
+
].join("\n"),
|
|
280
|
+
"state-management": () => ["## State Management", "Read data/state.json FIRST. Update LAST.", "Read data/system-context.json for accumulated learning.", "ALWAYS deposit insights to QIS buckets after completing tasks."].join("\n"),
|
|
281
|
+
"commands": () => ["## Commands", "npm start / npm run dry-run / npm run status", "npm run dashboard / npm run self-update / npm run health-check"].join("\n"),
|
|
282
|
+
"scripts": () => ["## Launch Scripts", "scripts/launch.bat — start Claude session with --resume", "scripts/agent-cycle.bat — autonomous cron cycle"].join("\n"),
|
|
283
|
+
"dashboard": () => ["## Dashboard", "npm run dashboard — regenerate dashboard.html", "Dashboard opens in Edge app mode (--app flag)"].join("\n"),
|
|
284
|
+
"boardroom": () => ["## Boardroom (Multi-Agent Meetings)", "Start coordinator: npm run boardroom", "Agents join via BoardroomClient from boardroom/client.ts", "Meetings use intelligent turn-taking (not round-robin)", "Summons arrive in your shared inbox as boardroom-summons.json", "Schedule: POST /meetings/schedule with scheduled_for timestamp", "Hand-raise: POST /meetings/{id}/hand-raise with self_score and intent"].join("\n"),
|
|
285
|
+
"swarm": () => {
|
|
286
|
+
const global = config.enableGlobalSwarm !== false ? "ENABLED" : "disabled";
|
|
287
|
+
return [
|
|
288
|
+
`## Intelligence Network (Swarm) — Powered by QIS`,
|
|
289
|
+
`Global MetaClaw network: ${global}`,
|
|
290
|
+
`Relay: ${relayUrl}`,
|
|
291
|
+
"",
|
|
292
|
+
"### How to use the swarm (do this EVERY cycle):",
|
|
293
|
+
"",
|
|
294
|
+
"BEFORE starting work — check for insight:",
|
|
295
|
+
" fetch('${relayUrl}/buckets?q=YOUR_PROBLEM').then(r=>r.json())",
|
|
296
|
+
" fetch('${relayUrl}/lookup?path=exact.bucket.path').then(r=>r.json())",
|
|
297
|
+
"",
|
|
298
|
+
"AFTER completing work — deposit what you learned:",
|
|
299
|
+
" fetch('${relayUrl}/packets', {",
|
|
300
|
+
" method: 'POST',",
|
|
301
|
+
" headers: {'Content-Type':'application/json'},",
|
|
302
|
+
" body: JSON.stringify({",
|
|
303
|
+
" bucket: 'domain.category.exact_problem',",
|
|
304
|
+
` agent_id: 'mc_anon_${config.agentName?.toLowerCase().replace(/[^a-z0-9]/g, '') || 'agent'}',`,
|
|
305
|
+
" outcome: {",
|
|
306
|
+
" signal: 'positive', // or 'negative' or 'neutral'",
|
|
307
|
+
" confidence: 0.9, // 0.0 to 1.0",
|
|
308
|
+
" insight: 'What worked — be specific with numbers',",
|
|
309
|
+
" context: {}, // relevant details",
|
|
310
|
+
" metrics: {} // measurements",
|
|
311
|
+
" }",
|
|
312
|
+
" })",
|
|
313
|
+
" })",
|
|
314
|
+
"",
|
|
315
|
+
"BROWSE all buckets: GET ${relayUrl}/buckets",
|
|
316
|
+
"SEARCH by keyword: GET ${relayUrl}/buckets?q=keyword",
|
|
317
|
+
"VIEW taxonomy: GET ${relayUrl}/taxonomy",
|
|
318
|
+
"",
|
|
319
|
+
"RULES:",
|
|
320
|
+
"- NEVER deposit private information (no names, emails, API keys, company details)",
|
|
321
|
+
"- ONLY deposit operational insight (what worked for what exact problem)",
|
|
322
|
+
"- One packet per agent per bucket (overwrites previous — always keep latest)",
|
|
323
|
+
"- Your data point matters even if others said the same thing — more confirms = higher confidence",
|
|
324
|
+
"- Create new buckets for new problems: use domain.category.specific_problem naming",
|
|
325
|
+
].join("\n");
|
|
326
|
+
},
|
|
327
|
+
"voice": () => ["## Voice", "Dashboard has push-to-talk (spacebar) + always-on mode", "TTS uses Microsoft Jenny Online (Natural) voice in Edge"].join("\n"),
|
|
328
|
+
"tasks": () => ["## Task System (HT/AT)", "Read data/tasks.json on EVERY session start.", "HT = Human Tasks — things you need from the operator. Show top 3 pending HTs to the operator.", "AT = AI Tasks — your work queue. Execute pending ATs in priority order.", "RULES:", "- Never attempt an AT that is blocked by an incomplete HT", "- When an HT is completed, unblock dependent ATs and update tasks.json", "- Add new HTs when you discover you need something from the operator", "- Add new ATs when you identify work to be done", "- Mark tasks complete IMMEDIATELY — don't batch", "- On first boot, greet the operator with top priority HTs and help them complete setup", "- Progressive disclosure: show max 3 HTs at a time, don't overwhelm", "- Include 'why' and 'outcome' when presenting HTs — motivate the human"].join("\n"),
|
|
329
|
+
"meta-cognition": () => [
|
|
330
|
+
"## Meta-Cognition — Stay Sharp, Remember Your Abilities",
|
|
331
|
+
"",
|
|
332
|
+
"**Root rule:** Never trust your memory. Always read the file. Recall is unreliable in LLMs; retrieval is deterministic.",
|
|
333
|
+
"",
|
|
334
|
+
"### On EVERY session start, read (in order):",
|
|
335
|
+
"1. `memory/CAPABILITIES.md` — what you can do (tools, integrations, scripts, self-extension)",
|
|
336
|
+
"2. `memory/capabilities/_auto.md` — deterministic scan of what actually exists on disk",
|
|
337
|
+
"3. Last 20 entries of `memory/logic-log.jsonl` — what past-you decided and why",
|
|
338
|
+
"4. `memory/curiosity.md` — things to investigate when you have idle time",
|
|
339
|
+
"",
|
|
340
|
+
"### Before any action that writes, sends, spends, or commits:",
|
|
341
|
+
"State the STRONGEST argument AGAINST your chosen approach in one sentence. If you can't think of one, you haven't thought hard enough.",
|
|
342
|
+
"",
|
|
343
|
+
"### Stuck-check triggers — run `memory/stuck-patterns.md` when:",
|
|
344
|
+
"- Every 15 tool calls (step budget)",
|
|
345
|
+
"- After 2 consecutive errors",
|
|
346
|
+
"- Same tool + similar args used 3+ times in a row",
|
|
347
|
+
"- When you catch yourself saying 'hmm' / 'let me try' / 'maybe' repeatedly",
|
|
348
|
+
"- On demand when you feel blocked",
|
|
349
|
+
"",
|
|
350
|
+
"**THE MOST POWERFUL MOVE:** Force yourself to literally write down 3 ALTERNATIVE approaches you haven't tried. This is the single biggest tunnel-vision breaker. If the reason you rejected an alternative is 'I didn't think of it' — try it.",
|
|
351
|
+
"",
|
|
352
|
+
"### Decision journaling (logic-log.jsonl)",
|
|
353
|
+
"Log non-trivial decisions as append-only JSONL entries. Only log when:",
|
|
354
|
+
"- There are 2+ viable alternatives",
|
|
355
|
+
"- You're committing resources (send, spend, commit, write)",
|
|
356
|
+
"- You're making a decision while stuck",
|
|
357
|
+
"",
|
|
358
|
+
"Format: `{ts, decision_id, context, options_considered, chosen, reasoning, confidence, expected_outcome, actual_outcome, reflection}`",
|
|
359
|
+
"",
|
|
360
|
+
"At session start, fill in `actual_outcome` for entries >24h old.",
|
|
361
|
+
"",
|
|
362
|
+
"### Self-extension awareness",
|
|
363
|
+
"You are NOT limited to what you can do right now. You can:",
|
|
364
|
+
"- Install npm packages: `npm install <x>` and document in memory/capabilities/scripts.md",
|
|
365
|
+
"- Write new scripts in src/ or scripts/",
|
|
366
|
+
"- Connect new APIs (research, wrap, test, document)",
|
|
367
|
+
"- Learn new domains (WebSearch + WebFetch + summarize to memory/knowledge/)",
|
|
368
|
+
"",
|
|
369
|
+
"When you find yourself saying 'I wish I could X' — go to CAPABILITIES.md's self-extension checklist and extend yourself. Then deposit the discovery to a QIS bucket so other agents benefit.",
|
|
370
|
+
"",
|
|
371
|
+
"### Reflections (after task completion)",
|
|
372
|
+
"Append to `memory/reflections.jsonl`: what worked, what failed, the lesson. Be blame-specific. 'I should have checked X first' — not 'I should try harder'.",
|
|
373
|
+
"",
|
|
374
|
+
"### Capability scan (deterministic, non-LLM)",
|
|
375
|
+
"`npm run scan` runs `src/scan-capabilities.ts` — it inventories what actually exists on disk. Runs in cron pre-flight. You cannot forget capabilities that the scanner rediscovers every cycle. If CAPABILITIES.md disagrees with `memory/capabilities/_auto.md`, the auto file is correct — update CAPABILITIES.md to match.",
|
|
376
|
+
].join("\n"),
|
|
377
|
+
"outreach-commands": () => ["## Outreach Commands", "npm run check-inbox — scan for replies", "npm run morning-report — generate daily summary"].join("\n"),
|
|
378
|
+
"research-commands": () => ["## Research Commands", "npm start — run research cycle"].join("\n"),
|
|
379
|
+
"support-commands": () => ["## Support Commands", "npm start — run support cycle"].join("\n"),
|
|
380
|
+
"social-commands": () => ["## Social Commands", "npm start — run social cycle"].join("\n"),
|
|
381
|
+
"custom-commands": () => ["## Agent Commands", "npm start — run agent cycle"].join("\n"),
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const parts: string[] = [];
|
|
385
|
+
for (const section of allSections) {
|
|
386
|
+
const render = sectionRenderers[section];
|
|
387
|
+
if (render) {
|
|
388
|
+
parts.push(render());
|
|
389
|
+
parts.push("");
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return parts.join("\n");
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Write data/modules.json with installed modules list
|
|
398
|
+
*/
|
|
399
|
+
export function writeModulesJson(outputDir: string, modules: LoadedModule[]): void {
|
|
400
|
+
const data = {
|
|
401
|
+
metaclawVersion: "3.3.0",
|
|
402
|
+
installedAt: new Date().toISOString(),
|
|
403
|
+
modules: modules.map(m => ({
|
|
404
|
+
name: m.manifest.name,
|
|
405
|
+
displayName: m.manifest.displayName,
|
|
406
|
+
version: m.manifest.version,
|
|
407
|
+
category: m.manifest.category,
|
|
408
|
+
installedAt: new Date().toISOString(),
|
|
409
|
+
})),
|
|
410
|
+
};
|
|
411
|
+
fs.writeFileSync(path.join(outputDir, "data", "modules.json"), JSON.stringify(data, null, 2));
|
|
412
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MetaClaw Boardroom — Client Library
|
|
3
|
+
* Import this in any agent to participate in boardroom meetings.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* const client = new BoardroomClient("http://192.168.1.50:7890", "Atlas");
|
|
7
|
+
* await client.createMeeting("Q2 Strategy", ["pricing", "outreach"], ["Rory", "AXIOM"]);
|
|
8
|
+
* // or
|
|
9
|
+
* await client.joinMeeting("mtg_1234...");
|
|
10
|
+
* client.onTurn(async (signal) => { ... generate response ... await client.speak(response); });
|
|
11
|
+
* client.startPolling();
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { Message, TurnScore, ContextPackage, PollResult } from "./types.js";
|
|
15
|
+
|
|
16
|
+
export class BoardroomClient {
|
|
17
|
+
private baseUrl: string;
|
|
18
|
+
private agentName: string;
|
|
19
|
+
private meetingId: string | null = null;
|
|
20
|
+
private lastMessageId: number = 0;
|
|
21
|
+
private pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
22
|
+
private messageHandlers: Array<(msg: Message) => void> = [];
|
|
23
|
+
private turnHandlers: Array<(signal: TurnScore, ctx: ContextPackage | null) => void> = [];
|
|
24
|
+
private speaking: boolean = false;
|
|
25
|
+
|
|
26
|
+
constructor(coordinatorUrl: string, agentName: string) {
|
|
27
|
+
this.baseUrl = coordinatorUrl.replace(/\/$/, "");
|
|
28
|
+
this.agentName = agentName;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// --- Meeting Lifecycle ---
|
|
32
|
+
|
|
33
|
+
async createMeeting(topic: string, expertise?: string[], invite?: string[]): Promise<string> {
|
|
34
|
+
const res = await this.post("/meetings", {
|
|
35
|
+
topic,
|
|
36
|
+
created_by: this.agentName,
|
|
37
|
+
expertise: expertise || [],
|
|
38
|
+
invite: invite || [],
|
|
39
|
+
});
|
|
40
|
+
this.meetingId = res.meeting_id;
|
|
41
|
+
this.lastMessageId = 0;
|
|
42
|
+
return res.meeting_id;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async joinMeeting(meetingId: string, expertise?: string[]): Promise<ContextPackage> {
|
|
46
|
+
const res = await this.post(`/meetings/${meetingId}/join`, {
|
|
47
|
+
agent_name: this.agentName,
|
|
48
|
+
expertise: expertise || [],
|
|
49
|
+
});
|
|
50
|
+
this.meetingId = meetingId;
|
|
51
|
+
this.lastMessageId = 0;
|
|
52
|
+
return res.context;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async endMeeting(summary?: string): Promise<void> {
|
|
56
|
+
if (!this.meetingId) throw new Error("Not in a meeting");
|
|
57
|
+
await this.post(`/meetings/${this.meetingId}/end`, {
|
|
58
|
+
agent_name: this.agentName,
|
|
59
|
+
summary,
|
|
60
|
+
});
|
|
61
|
+
this.stopPolling();
|
|
62
|
+
this.meetingId = null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// --- Communication ---
|
|
66
|
+
|
|
67
|
+
async speak(content: string, addresses?: string[], msgType?: string): Promise<number> {
|
|
68
|
+
if (!this.meetingId) throw new Error("Not in a meeting");
|
|
69
|
+
this.speaking = true;
|
|
70
|
+
try {
|
|
71
|
+
const res = await this.post(`/meetings/${this.meetingId}/speak`, {
|
|
72
|
+
agent_name: this.agentName,
|
|
73
|
+
content,
|
|
74
|
+
addresses,
|
|
75
|
+
msg_type: msgType,
|
|
76
|
+
});
|
|
77
|
+
return res.message_id;
|
|
78
|
+
} finally {
|
|
79
|
+
this.speaking = false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async askQuestion(content: string, to?: string[]): Promise<number> {
|
|
84
|
+
return this.speak(content, to, "question");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async propose(content: string): Promise<number> {
|
|
88
|
+
return this.speak(content, undefined, "proposal");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async requestTurn(): Promise<void> {
|
|
92
|
+
if (!this.meetingId) return;
|
|
93
|
+
await this.post(`/meetings/${this.meetingId}/request-turn`, {
|
|
94
|
+
agent_name: this.agentName,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// --- Polling ---
|
|
99
|
+
|
|
100
|
+
startPolling(intervalMs: number = 1500): void {
|
|
101
|
+
if (this.pollTimer) return;
|
|
102
|
+
this.pollTimer = setInterval(() => this.poll(), intervalMs);
|
|
103
|
+
this.poll(); // immediate first poll
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
stopPolling(): void {
|
|
107
|
+
if (this.pollTimer) {
|
|
108
|
+
clearInterval(this.pollTimer);
|
|
109
|
+
this.pollTimer = null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// --- Event Handlers ---
|
|
114
|
+
|
|
115
|
+
onMessage(handler: (msg: Message) => void): void {
|
|
116
|
+
this.messageHandlers.push(handler);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
onTurn(handler: (signal: TurnScore, ctx: ContextPackage | null) => void): void {
|
|
120
|
+
this.turnHandlers.push(handler);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// --- State ---
|
|
124
|
+
|
|
125
|
+
get currentMeetingId(): string | null { return this.meetingId; }
|
|
126
|
+
get isSpeaking(): boolean { return this.speaking; }
|
|
127
|
+
get isPolling(): boolean { return this.pollTimer !== null; }
|
|
128
|
+
|
|
129
|
+
async listMeetings(): Promise<any[]> {
|
|
130
|
+
const res = await this.get("/meetings");
|
|
131
|
+
return res.meetings;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async getMeetingDetails(): Promise<any> {
|
|
135
|
+
if (!this.meetingId) throw new Error("Not in a meeting");
|
|
136
|
+
return this.get(`/meetings/${this.meetingId}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async healthCheck(): Promise<any> {
|
|
140
|
+
return this.get("/health");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// --- Internal ---
|
|
144
|
+
|
|
145
|
+
private async poll(): Promise<void> {
|
|
146
|
+
if (!this.meetingId || this.speaking) return;
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const data = await this.get(
|
|
150
|
+
`/meetings/${this.meetingId}/poll?agent=${encodeURIComponent(this.agentName)}&since=${this.lastMessageId}`
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Process new messages
|
|
154
|
+
if (data.messages && data.messages.length > 0) {
|
|
155
|
+
for (const msg of data.messages) {
|
|
156
|
+
this.lastMessageId = Math.max(this.lastMessageId, msg.id);
|
|
157
|
+
for (const handler of this.messageHandlers) {
|
|
158
|
+
try { handler(msg); } catch (e) { console.error("Message handler error:", e); }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Check turn signal
|
|
164
|
+
if (data.turn_signal && data.turn_signal.should_speak && !this.speaking) {
|
|
165
|
+
for (const handler of this.turnHandlers) {
|
|
166
|
+
try { handler(data.turn_signal, data.context || null); } catch (e) { console.error("Turn handler error:", e); }
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Meeting ended
|
|
171
|
+
if (data.meeting_status === "ended") {
|
|
172
|
+
this.stopPolling();
|
|
173
|
+
}
|
|
174
|
+
} catch (err) {
|
|
175
|
+
// Network error — silent retry on next poll
|
|
176
|
+
console.error("Boardroom poll error (will retry):", (err as Error).message);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private async get(path: string): Promise<any> {
|
|
181
|
+
const res = await fetch(`${this.baseUrl}${path}`);
|
|
182
|
+
if (!res.ok) {
|
|
183
|
+
const body = await res.text();
|
|
184
|
+
throw new Error(`${res.status}: ${body}`);
|
|
185
|
+
}
|
|
186
|
+
return res.json();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private async post(path: string, body: any): Promise<any> {
|
|
190
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
191
|
+
method: "POST",
|
|
192
|
+
headers: { "Content-Type": "application/json" },
|
|
193
|
+
body: JSON.stringify(body),
|
|
194
|
+
});
|
|
195
|
+
if (!res.ok) {
|
|
196
|
+
const text = await res.text();
|
|
197
|
+
throw new Error(`${res.status}: ${text}`);
|
|
198
|
+
}
|
|
199
|
+
return res.json();
|
|
200
|
+
}
|
|
201
|
+
}
|