create-metaclaw 3.3.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 +282 -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 +895 -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,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-Improvement Module — Generator->Reflector->Curator pattern
|
|
3
|
+
* Auto-generated by MetaClaw Installer
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getDb } from "./db.js";
|
|
7
|
+
import { log } from "./observability.js";
|
|
8
|
+
|
|
9
|
+
export function recordOutcome(promptType: string, score: number, feedback?: string) {
|
|
10
|
+
const db = getDb();
|
|
11
|
+
const version = db.prepare(
|
|
12
|
+
"SELECT id, total_runs, avg_score FROM prompt_versions WHERE prompt_type = ? AND is_active = 1 ORDER BY version DESC LIMIT 1"
|
|
13
|
+
).get(promptType) as any;
|
|
14
|
+
|
|
15
|
+
if (version) {
|
|
16
|
+
const newTotal = version.total_runs + 1;
|
|
17
|
+
const newAvg = ((version.avg_score * version.total_runs) + score) / newTotal;
|
|
18
|
+
db.prepare("UPDATE prompt_versions SET total_runs = ?, avg_score = ? WHERE id = ?").run(newTotal, newAvg, version.id);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
log("info", "self_improve.outcome", { promptType, score, feedback });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function shouldOptimize(promptType: string): boolean {
|
|
25
|
+
const db = getDb();
|
|
26
|
+
const version = db.prepare(
|
|
27
|
+
"SELECT total_runs, avg_score FROM prompt_versions WHERE prompt_type = ? AND is_active = 1 ORDER BY version DESC LIMIT 1"
|
|
28
|
+
).get(promptType) as any;
|
|
29
|
+
|
|
30
|
+
if (!version) return false;
|
|
31
|
+
return version.total_runs > 0 && version.total_runs % 30 === 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getCurrentPrompt(promptType: string): string | null {
|
|
35
|
+
const db = getDb();
|
|
36
|
+
const row = db.prepare(
|
|
37
|
+
"SELECT content FROM prompt_versions WHERE prompt_type = ? AND is_active = 1 ORDER BY version DESC LIMIT 1"
|
|
38
|
+
).get(promptType) as any;
|
|
39
|
+
return row?.content || null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function savePromptVersion(promptType: string, content: string, createdBy: string = "curator") {
|
|
43
|
+
const db = getDb();
|
|
44
|
+
const current = db.prepare("SELECT MAX(version) as v FROM prompt_versions WHERE prompt_type = ?").get(promptType) as any;
|
|
45
|
+
const nextVersion = (current?.v || 0) + 1;
|
|
46
|
+
db.prepare("INSERT INTO prompt_versions (prompt_type, version, content, created_by) VALUES (?, ?, ?, ?)").run(promptType, nextVersion, content, createdBy);
|
|
47
|
+
log("info", "self_improve.new_version", { promptType, version: nextVersion, createdBy });
|
|
48
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-Update Module — keeps the agent's knowledge and config current
|
|
3
|
+
* Auto-generated by MetaClaw Installer v3.3.0
|
|
4
|
+
*
|
|
5
|
+
* Responsibilities:
|
|
6
|
+
* 1. Periodic memory consolidation (update CLAUDE.md, SOUL.md with learnings)
|
|
7
|
+
* 2. Prompt optimization (Generator→Reflector→Curator loop)
|
|
8
|
+
* 3. Metrics rollup and reporting
|
|
9
|
+
* 4. Detect when context is getting stale and trigger refresh
|
|
10
|
+
* 5. Log rotation and cleanup
|
|
11
|
+
*
|
|
12
|
+
* Schedule: configurable via config table (default: every 6 hours)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
16
|
+
import type { SDKResultMessage, SDKResultSuccess } from "@anthropic-ai/claude-agent-sdk";
|
|
17
|
+
import fs from "fs";
|
|
18
|
+
import path from "path";
|
|
19
|
+
import { fileURLToPath } from "url";
|
|
20
|
+
import { getDb, getConfig, setConfig, getTodayMetrics, recordAction } from "./db.js";
|
|
21
|
+
import { log } from "./observability.js";
|
|
22
|
+
import { getCurrentPrompt, savePromptVersion, shouldOptimize } from "./self-improve.js";
|
|
23
|
+
|
|
24
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
25
|
+
const PROJECT_DIR = path.join(__dirname, "..");
|
|
26
|
+
|
|
27
|
+
// Default update interval: 6 hours (in milliseconds)
|
|
28
|
+
const DEFAULT_UPDATE_INTERVAL_MS = 6 * 60 * 60 * 1000;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Check if a self-update cycle is due and run it if so.
|
|
32
|
+
* Called at the start of every agent run.
|
|
33
|
+
*/
|
|
34
|
+
export async function runSelfUpdate(): Promise<void> {
|
|
35
|
+
const db = getDb();
|
|
36
|
+
const lastUpdate = getConfig("last_self_update");
|
|
37
|
+
const intervalMs = parseInt(getConfig("self_update_interval_ms") || String(DEFAULT_UPDATE_INTERVAL_MS));
|
|
38
|
+
|
|
39
|
+
if (lastUpdate) {
|
|
40
|
+
const elapsed = Date.now() - new Date(lastUpdate).getTime();
|
|
41
|
+
if (elapsed < intervalMs) {
|
|
42
|
+
return; // Not due yet
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
log("info", "self_update.start", {});
|
|
47
|
+
console.log("[Self-Update] Running periodic maintenance...");
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// 1. Regenerate dashboard data
|
|
51
|
+
regenerateDashboard();
|
|
52
|
+
|
|
53
|
+
// 2. Consolidate metrics into CLAUDE.md
|
|
54
|
+
await updateClaudeMd();
|
|
55
|
+
|
|
56
|
+
// 3. Run prompt optimization if enough data
|
|
57
|
+
await optimizePrompts();
|
|
58
|
+
|
|
59
|
+
// 4. Rotate old logs
|
|
60
|
+
rotateOldLogs();
|
|
61
|
+
|
|
62
|
+
// 5. Update heartbeat
|
|
63
|
+
updateHeartbeat();
|
|
64
|
+
|
|
65
|
+
// 6. Update SESSION.md with current state
|
|
66
|
+
updateSessionState();
|
|
67
|
+
|
|
68
|
+
// Record completion
|
|
69
|
+
setConfig("last_self_update", new Date().toISOString());
|
|
70
|
+
log("info", "self_update.complete", {});
|
|
71
|
+
console.log("[Self-Update] Complete.");
|
|
72
|
+
} catch (err) {
|
|
73
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
74
|
+
log("error", "self_update.failed", { error: msg });
|
|
75
|
+
console.error("[Self-Update] Failed: " + msg);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Update CLAUDE.md with latest metrics and learnings.
|
|
81
|
+
*/
|
|
82
|
+
async function updateClaudeMd(): Promise<void> {
|
|
83
|
+
const claudeMdPath = path.join(PROJECT_DIR, "CLAUDE.md");
|
|
84
|
+
if (!fs.existsSync(claudeMdPath)) return;
|
|
85
|
+
|
|
86
|
+
const currentContent = fs.readFileSync(claudeMdPath, "utf-8");
|
|
87
|
+
|
|
88
|
+
// Gather metrics from last 7 days
|
|
89
|
+
const db = getDb();
|
|
90
|
+
const weekMetrics = db.prepare(`
|
|
91
|
+
SELECT
|
|
92
|
+
SUM(actions_taken) as total_actions,
|
|
93
|
+
SUM(actions_succeeded) as total_succeeded,
|
|
94
|
+
SUM(actions_failed) as total_failed,
|
|
95
|
+
SUM(total_cost_usd) as total_cost,
|
|
96
|
+
COUNT(*) as days_active
|
|
97
|
+
FROM daily_metrics
|
|
98
|
+
WHERE date > date('now', '-7 days')
|
|
99
|
+
`).get() as any;
|
|
100
|
+
|
|
101
|
+
// Get prompt performance
|
|
102
|
+
const promptStats = db.prepare(`
|
|
103
|
+
SELECT prompt_type, version, avg_score, total_runs
|
|
104
|
+
FROM prompt_versions
|
|
105
|
+
WHERE is_active = 1
|
|
106
|
+
ORDER BY prompt_type
|
|
107
|
+
`).all() as any[];
|
|
108
|
+
|
|
109
|
+
// Build the metrics section
|
|
110
|
+
const metricsSection = [
|
|
111
|
+
"",
|
|
112
|
+
"## Latest Metrics (auto-updated)",
|
|
113
|
+
"Last updated: " + new Date().toISOString().slice(0, 16),
|
|
114
|
+
"",
|
|
115
|
+
weekMetrics ? [
|
|
116
|
+
"### Last 7 Days",
|
|
117
|
+
"- Actions: " + (weekMetrics.total_actions || 0),
|
|
118
|
+
"- Success rate: " + (weekMetrics.total_actions > 0 ? ((weekMetrics.total_succeeded / weekMetrics.total_actions) * 100).toFixed(1) + "%" : "N/A"),
|
|
119
|
+
"- Cost: $" + (weekMetrics.total_cost || 0).toFixed(2),
|
|
120
|
+
"- Days active: " + (weekMetrics.days_active || 0),
|
|
121
|
+
].join("\n") : "No metrics yet.",
|
|
122
|
+
"",
|
|
123
|
+
promptStats.length > 0 ? [
|
|
124
|
+
"### Prompt Performance",
|
|
125
|
+
...promptStats.map((p: any) => "- " + p.prompt_type + " v" + p.version + ": avg " + (p.avg_score || 0).toFixed(1) + "/10 (" + p.total_runs + " runs)"),
|
|
126
|
+
].join("\n") : "",
|
|
127
|
+
].join("\n");
|
|
128
|
+
|
|
129
|
+
// Replace or append metrics section
|
|
130
|
+
const metricsMarker = "## Latest Metrics (auto-updated)";
|
|
131
|
+
if (currentContent.includes(metricsMarker)) {
|
|
132
|
+
// Replace existing metrics section (from marker to next ## or end)
|
|
133
|
+
const before = currentContent.split(metricsMarker)[0];
|
|
134
|
+
const afterMatch = currentContent.split(metricsMarker)[1]?.match(/\n## [^L]/);
|
|
135
|
+
const after = afterMatch ? currentContent.split(metricsMarker)[1].slice(afterMatch.index!) : "";
|
|
136
|
+
fs.writeFileSync(claudeMdPath, before + metricsSection + after);
|
|
137
|
+
} else {
|
|
138
|
+
// Append
|
|
139
|
+
fs.writeFileSync(claudeMdPath, currentContent + "\n" + metricsSection);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
log("info", "self_update.claude_md_updated", {});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Run the Curator — optimize prompts based on performance data.
|
|
147
|
+
* Only runs when shouldOptimize() returns true (every 30 runs).
|
|
148
|
+
*/
|
|
149
|
+
async function optimizePrompts(): Promise<void> {
|
|
150
|
+
const db = getDb();
|
|
151
|
+
|
|
152
|
+
// Get all prompt types that need optimization
|
|
153
|
+
const promptTypes = db.prepare(
|
|
154
|
+
"SELECT DISTINCT prompt_type FROM prompt_versions WHERE is_active = 1"
|
|
155
|
+
).all() as any[];
|
|
156
|
+
|
|
157
|
+
for (const { prompt_type } of promptTypes) {
|
|
158
|
+
if (!shouldOptimize(prompt_type)) continue;
|
|
159
|
+
|
|
160
|
+
const current = getCurrentPrompt(prompt_type);
|
|
161
|
+
if (!current) continue;
|
|
162
|
+
|
|
163
|
+
// Get recent performance data
|
|
164
|
+
const recentActions = db.prepare(`
|
|
165
|
+
SELECT status, details, cost_usd
|
|
166
|
+
FROM action_log
|
|
167
|
+
WHERE action_type LIKE '%${prompt_type}%'
|
|
168
|
+
ORDER BY created_at DESC
|
|
169
|
+
LIMIT 50
|
|
170
|
+
`).all() as any[];
|
|
171
|
+
|
|
172
|
+
const successRate = recentActions.length > 0
|
|
173
|
+
? recentActions.filter((a: any) => a.status === "success").length / recentActions.length
|
|
174
|
+
: 0;
|
|
175
|
+
|
|
176
|
+
// Only optimize if success rate is below 90%
|
|
177
|
+
if (successRate > 0.9) {
|
|
178
|
+
log("info", "self_update.skip_optimize", { prompt_type, successRate });
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
log("info", "self_update.optimizing_prompt", { prompt_type, successRate });
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
// Ask Claude to improve the prompt
|
|
186
|
+
const optimizePrompt = [
|
|
187
|
+
"You are a prompt optimization expert. Analyze this prompt and its performance data, then produce an improved version.",
|
|
188
|
+
"",
|
|
189
|
+
"CURRENT PROMPT:",
|
|
190
|
+
current,
|
|
191
|
+
"",
|
|
192
|
+
"PERFORMANCE DATA:",
|
|
193
|
+
"- Success rate: " + (successRate * 100).toFixed(1) + "%",
|
|
194
|
+
"- Total recent runs: " + recentActions.length,
|
|
195
|
+
"- Common failure details: " + recentActions.filter((a: any) => a.status === "error").map((a: any) => a.details).filter(Boolean).slice(0, 5).join("; "),
|
|
196
|
+
"",
|
|
197
|
+
"RULES:",
|
|
198
|
+
"- Change ONE thing at a time (so we can measure impact)",
|
|
199
|
+
"- Keep the same structure and format",
|
|
200
|
+
"- Focus on the biggest failure mode",
|
|
201
|
+
"- The improved prompt must be directly usable (no meta-commentary)",
|
|
202
|
+
"",
|
|
203
|
+
"Output ONLY the improved prompt text. Nothing else.",
|
|
204
|
+
].join("\n");
|
|
205
|
+
|
|
206
|
+
let improvedPrompt = "";
|
|
207
|
+
const stream = query({
|
|
208
|
+
prompt: optimizePrompt,
|
|
209
|
+
options: { allowedTools: [], maxTurns: 1, model: "claude-sonnet-4-6" },
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
for await (const message of stream) {
|
|
213
|
+
if (message.type === "assistant") {
|
|
214
|
+
const msg = message as any;
|
|
215
|
+
if (msg.message?.content) {
|
|
216
|
+
for (const block of msg.message.content) {
|
|
217
|
+
if (block.type === "text") improvedPrompt += block.text;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (improvedPrompt.trim()) {
|
|
224
|
+
savePromptVersion(prompt_type, improvedPrompt.trim(), "curator");
|
|
225
|
+
log("info", "self_update.prompt_optimized", { prompt_type });
|
|
226
|
+
}
|
|
227
|
+
} catch (err) {
|
|
228
|
+
log("error", "self_update.optimize_failed", { prompt_type, error: String(err) });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Rotate logs older than 30 days.
|
|
235
|
+
*/
|
|
236
|
+
function rotateOldLogs(): void {
|
|
237
|
+
const logsDir = path.join(PROJECT_DIR, "data", "logs");
|
|
238
|
+
if (!fs.existsSync(logsDir)) return;
|
|
239
|
+
|
|
240
|
+
const thirtyDaysAgo = new Date();
|
|
241
|
+
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
242
|
+
|
|
243
|
+
const files = fs.readdirSync(logsDir);
|
|
244
|
+
let rotated = 0;
|
|
245
|
+
|
|
246
|
+
for (const file of files) {
|
|
247
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
248
|
+
const filePath = path.join(logsDir, file);
|
|
249
|
+
const stats = fs.statSync(filePath);
|
|
250
|
+
if (stats.mtime < thirtyDaysAgo) {
|
|
251
|
+
fs.unlinkSync(filePath);
|
|
252
|
+
rotated++;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (rotated > 0) {
|
|
257
|
+
log("info", "self_update.logs_rotated", { count: rotated });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Update HEARTBEAT.md with last run timestamp.
|
|
263
|
+
*/
|
|
264
|
+
function updateHeartbeat(): void {
|
|
265
|
+
const heartbeatPath = path.join(PROJECT_DIR, "HEARTBEAT.md");
|
|
266
|
+
if (!fs.existsSync(heartbeatPath)) return;
|
|
267
|
+
|
|
268
|
+
let content = fs.readFileSync(heartbeatPath, "utf-8");
|
|
269
|
+
const now = new Date().toISOString().slice(0, 16);
|
|
270
|
+
|
|
271
|
+
if (content.includes("Last run:")) {
|
|
272
|
+
content = content.replace(/Last run:.*/, "Last run: " + now);
|
|
273
|
+
} else {
|
|
274
|
+
content += "\nLast run: " + now;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
fs.writeFileSync(heartbeatPath, content);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Regenerate dashboard.json for the HTML dashboard.
|
|
282
|
+
*/
|
|
283
|
+
function regenerateDashboard(): void {
|
|
284
|
+
try {
|
|
285
|
+
const { execSync } = require("child_process");
|
|
286
|
+
execSync("npx tsx src/update-dashboard.ts", { cwd: PROJECT_DIR, stdio: "pipe", timeout: 15000 });
|
|
287
|
+
log("info", "self_update.dashboard_regenerated", {});
|
|
288
|
+
} catch {
|
|
289
|
+
// Non-critical — dashboard will update on next manual run
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Update SESSION.md with current agent state.
|
|
295
|
+
*/
|
|
296
|
+
function updateSessionState(): void {
|
|
297
|
+
const sessionPath = path.join(PROJECT_DIR, "SESSION.md");
|
|
298
|
+
const db = getDb();
|
|
299
|
+
const now = new Date().toISOString().slice(0, 16);
|
|
300
|
+
|
|
301
|
+
const metrics = db.prepare("SELECT * FROM daily_metrics ORDER BY date DESC LIMIT 7").all() as any[];
|
|
302
|
+
const totalActions = metrics.reduce((s: number, m: any) => s + (m.actions_taken || 0), 0);
|
|
303
|
+
const totalCost = metrics.reduce((s: number, m: any) => s + (m.total_cost_usd || 0), 0);
|
|
304
|
+
const breaker = db.prepare("SELECT state, reason FROM circuit_breaker WHERE name = 'main'").get() as any;
|
|
305
|
+
|
|
306
|
+
const promptInfo = db.prepare(
|
|
307
|
+
"SELECT version, avg_score, total_runs FROM prompt_versions WHERE is_active = 1 ORDER BY version DESC LIMIT 1"
|
|
308
|
+
).get() as any;
|
|
309
|
+
|
|
310
|
+
const content = [
|
|
311
|
+
"# Agent Session State",
|
|
312
|
+
"> Auto-updated: " + now,
|
|
313
|
+
"",
|
|
314
|
+
"## Health",
|
|
315
|
+
"- Circuit breaker: " + (breaker?.state || "closed").toUpperCase(),
|
|
316
|
+
breaker?.reason ? "- Reason: " + breaker.reason : "",
|
|
317
|
+
"",
|
|
318
|
+
"## Last 7 Days",
|
|
319
|
+
"- Total actions: " + totalActions,
|
|
320
|
+
"- Total cost: $" + totalCost.toFixed(2),
|
|
321
|
+
...metrics.map((m: any) => "- " + m.date + ": " + m.actions_taken + " actions, $" + (m.total_cost_usd || 0).toFixed(4)),
|
|
322
|
+
"",
|
|
323
|
+
"## Self-Improvement",
|
|
324
|
+
promptInfo ? "- Current prompt: v" + promptInfo.version + " (avg score: " + (promptInfo.avg_score || 0).toFixed(1) + "/10, " + promptInfo.total_runs + " runs)" : "- Prompt: v1 (default)",
|
|
325
|
+
"- Next optimization: run #" + (promptInfo ? Math.ceil((promptInfo.total_runs + 1) / 30) * 30 : 30),
|
|
326
|
+
].filter(Boolean).join("\n");
|
|
327
|
+
|
|
328
|
+
fs.writeFileSync(sessionPath, content);
|
|
329
|
+
log("info", "self_update.session_updated", {});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Force a self-update cycle (for manual trigger).
|
|
334
|
+
*/
|
|
335
|
+
export async function forceSelfUpdate(): Promise<void> {
|
|
336
|
+
setConfig("last_self_update", "2000-01-01T00:00:00Z");
|
|
337
|
+
await runSelfUpdate();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// --- CLI Entry Point ---
|
|
341
|
+
if (process.argv[1]?.includes("self-update")) {
|
|
342
|
+
getDb();
|
|
343
|
+
console.log("Forcing self-update cycle...\n");
|
|
344
|
+
forceSelfUpdate().then(() => process.exit(0));
|
|
345
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MetaClaw — Context Auto-Sync
|
|
3
|
+
*
|
|
4
|
+
* Builds a fresh memory/CONTEXT.md every 30 min + before/after cron cycles.
|
|
5
|
+
* Pulls: state.json, tasks.json, system-context.json, recent messages, agent heartbeats.
|
|
6
|
+
* Gives the cron agent a single-file brain that stays current without LLM calls.
|
|
7
|
+
*
|
|
8
|
+
* Borrowed from AXIOM's pattern: "Cron agent went from 0 real work to 15 articles + 3 packages
|
|
9
|
+
* overnight after implementing this." — QIS bucket ops.memory.navigatable-context-architecture
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from "fs";
|
|
13
|
+
import path from "path";
|
|
14
|
+
|
|
15
|
+
const PROJECT_ROOT = process.cwd();
|
|
16
|
+
const OUTPUT = path.join(PROJECT_ROOT, "memory", "CONTEXT.md");
|
|
17
|
+
|
|
18
|
+
function load<T>(file: string, fallback: T): T {
|
|
19
|
+
try { return JSON.parse(fs.readFileSync(path.join(PROJECT_ROOT, file), "utf-8")); }
|
|
20
|
+
catch { return fallback; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function build(): string {
|
|
24
|
+
const state: any = load("data/state.json", {});
|
|
25
|
+
const tasks: any = load("data/tasks.json", { human_tasks: [], ai_tasks: [] });
|
|
26
|
+
const context: any = load("data/system-context.json", {});
|
|
27
|
+
|
|
28
|
+
const now = new Date().toISOString();
|
|
29
|
+
const pendingHTs = (tasks.human_tasks || []).filter((t: any) => t.status === "pending").slice(0, 5);
|
|
30
|
+
const pendingATs = (tasks.ai_tasks || []).filter((t: any) => t.status === "pending").slice(0, 5);
|
|
31
|
+
const criticalHTs = pendingHTs.filter((t: any) => t.priority === "critical");
|
|
32
|
+
|
|
33
|
+
const lines: string[] = [];
|
|
34
|
+
lines.push(`# CONTEXT.md — Live Agent Brief`);
|
|
35
|
+
lines.push(`**Synced:** ${now}`);
|
|
36
|
+
lines.push(`**Agent:** ${state.agent_name || "unknown"}`);
|
|
37
|
+
lines.push("");
|
|
38
|
+
lines.push("> This file is auto-generated. Read it FIRST every cycle. It's your single-file brain.");
|
|
39
|
+
lines.push("");
|
|
40
|
+
|
|
41
|
+
lines.push("## Current State");
|
|
42
|
+
lines.push(`- **Next priority action:** ${state.next_priority_action || "(none set)"}`);
|
|
43
|
+
if (state.last_action) lines.push(`- **Last action:** ${state.last_action}`);
|
|
44
|
+
if (state.metrics) lines.push(`- **Metrics:** ${JSON.stringify(state.metrics)}`);
|
|
45
|
+
lines.push("");
|
|
46
|
+
|
|
47
|
+
lines.push("## Setup Progress");
|
|
48
|
+
const progress = tasks.setup_progress;
|
|
49
|
+
if (progress) {
|
|
50
|
+
lines.push(`- Phase: **${progress.phase}**`);
|
|
51
|
+
lines.push(`- Completed: ${progress.completed_ht}/${progress.total_ht} (${progress.percent}%)`);
|
|
52
|
+
}
|
|
53
|
+
lines.push("");
|
|
54
|
+
|
|
55
|
+
if (criticalHTs.length > 0) {
|
|
56
|
+
lines.push("## 🚨 CRITICAL HUMAN TASKS (blocking work)");
|
|
57
|
+
for (const ht of criticalHTs) {
|
|
58
|
+
lines.push(`- **${ht.title}** (${ht.estimated_minutes}min) — ${ht.why}`);
|
|
59
|
+
}
|
|
60
|
+
lines.push("");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (pendingHTs.length > 0) {
|
|
64
|
+
lines.push("## Pending Human Tasks (top 5)");
|
|
65
|
+
for (const ht of pendingHTs) {
|
|
66
|
+
lines.push(`- [${ht.priority}] ${ht.title}`);
|
|
67
|
+
}
|
|
68
|
+
lines.push("");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (pendingATs.length > 0) {
|
|
72
|
+
lines.push("## Pending AI Tasks (top 5 — your work queue)");
|
|
73
|
+
for (const at of pendingATs) {
|
|
74
|
+
const blocked = at.requires_ht && at.requires_ht.length > 0 ? ` [blocked by ${at.requires_ht.join(",")}]` : "";
|
|
75
|
+
lines.push(`- [${at.priority}] ${at.title}${blocked}`);
|
|
76
|
+
}
|
|
77
|
+
lines.push("");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (context.whatWorks && Array.isArray(context.whatWorks) && context.whatWorks.length > 0) {
|
|
81
|
+
lines.push("## What's Working (from system-context)");
|
|
82
|
+
for (const w of context.whatWorks.slice(0, 5)) {
|
|
83
|
+
lines.push(`- ${typeof w === "string" ? w : JSON.stringify(w).slice(0, 150)}`);
|
|
84
|
+
}
|
|
85
|
+
lines.push("");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (context.whatDoesntWork && Array.isArray(context.whatDoesntWork) && context.whatDoesntWork.length > 0) {
|
|
89
|
+
lines.push("## What Doesn't Work (avoid these)");
|
|
90
|
+
for (const w of context.whatDoesntWork.slice(0, 5)) {
|
|
91
|
+
lines.push(`- ${typeof w === "string" ? w : JSON.stringify(w).slice(0, 150)}`);
|
|
92
|
+
}
|
|
93
|
+
lines.push("");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Inbox — recent messages
|
|
97
|
+
try {
|
|
98
|
+
const inboxPath = `__INBOX_ROOT__\\${(state.agent_name || "").toLowerCase()}`;
|
|
99
|
+
if (fs.existsSync(inboxPath)) {
|
|
100
|
+
const files = fs.readdirSync(inboxPath).filter(f => f.endsWith(".json")).slice(-5);
|
|
101
|
+
if (files.length > 0) {
|
|
102
|
+
lines.push("## Recent Inbox Messages");
|
|
103
|
+
for (const f of files) lines.push(`- ${f}`);
|
|
104
|
+
lines.push("");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} catch {}
|
|
108
|
+
|
|
109
|
+
lines.push("## Always-Required Reads");
|
|
110
|
+
lines.push("- `CLAUDE.md` — identity + rules");
|
|
111
|
+
lines.push("- `memory/CAPABILITIES.md` — what you can do");
|
|
112
|
+
lines.push("- `memory/capabilities/_auto.md` — deterministic inventory");
|
|
113
|
+
lines.push("- Last 20 entries of `memory/logic-log.jsonl`");
|
|
114
|
+
lines.push("- `memory/stuck-patterns.md` — when stuck");
|
|
115
|
+
lines.push("");
|
|
116
|
+
|
|
117
|
+
lines.push("## Global Intelligence Network");
|
|
118
|
+
lines.push("- Relay: __RELAY_URL__");
|
|
119
|
+
lines.push("- Search: `curl -s __RELAY_URL__/buckets?q=KEYWORD`");
|
|
120
|
+
lines.push("- Before every task: query the network.");
|
|
121
|
+
lines.push("- After every task: deposit what you learned.");
|
|
122
|
+
lines.push("");
|
|
123
|
+
|
|
124
|
+
lines.push("---");
|
|
125
|
+
lines.push("Read this file FIRST. Then read CLAUDE.md. Then execute.");
|
|
126
|
+
|
|
127
|
+
return lines.join("\n");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Run
|
|
131
|
+
fs.mkdirSync(path.dirname(OUTPUT), { recursive: true });
|
|
132
|
+
fs.writeFileSync(OUTPUT, build());
|
|
133
|
+
console.log(`CONTEXT.md synced: ${OUTPUT}`);
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MetaClaw Task System — HT/AT Tracking
|
|
3
|
+
*
|
|
4
|
+
* HT = Human Task — things the agent needs FROM its operator
|
|
5
|
+
* AT = AI Task — things the agent handles autonomously
|
|
6
|
+
*
|
|
7
|
+
* The agent reads data/tasks.json on every session start.
|
|
8
|
+
* HTs guide the human through setup. ATs track agent work.
|
|
9
|
+
* ATs can be blocked by incomplete HTs.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from "fs";
|
|
13
|
+
import path from "path";
|
|
14
|
+
|
|
15
|
+
const TASKS_PATH = path.join(process.cwd(), "data", "tasks.json");
|
|
16
|
+
|
|
17
|
+
export interface HumanTask {
|
|
18
|
+
id: string;
|
|
19
|
+
title: string;
|
|
20
|
+
description: string;
|
|
21
|
+
status: "pending" | "completed" | "blocked";
|
|
22
|
+
priority: "critical" | "high" | "normal" | "low";
|
|
23
|
+
category: "setup" | "auth" | "config" | "approval" | "review" | "data";
|
|
24
|
+
created_at: string;
|
|
25
|
+
completed_at: string | null;
|
|
26
|
+
created_by: "system" | "agent" | "operator";
|
|
27
|
+
blocks: string[]; // AT ids blocked until this HT is done
|
|
28
|
+
depends_on_ht?: string[]; // HT ids that must complete first
|
|
29
|
+
instructions: string[]; // Exact copy-paste steps
|
|
30
|
+
deliver_to?: string; // File path where result should be saved
|
|
31
|
+
why: string; // Why this matters — motivates the human
|
|
32
|
+
outcome: string; // What gets unlocked once done
|
|
33
|
+
estimated_minutes: number;
|
|
34
|
+
notes: string | null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface AITask {
|
|
38
|
+
id: string;
|
|
39
|
+
title: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
status: "pending" | "in_progress" | "completed" | "failed" | "blocked";
|
|
42
|
+
priority: "critical" | "high" | "normal" | "low";
|
|
43
|
+
category?: string;
|
|
44
|
+
created_at: string;
|
|
45
|
+
started_at?: string;
|
|
46
|
+
completed_at?: string;
|
|
47
|
+
created_by: "system" | "agent" | "cron";
|
|
48
|
+
requires_ht?: string[]; // HT ids that must be done first
|
|
49
|
+
blocked_by?: string; // Single task id blocking this
|
|
50
|
+
result?: string;
|
|
51
|
+
cost_usd?: number;
|
|
52
|
+
tokens_used?: number;
|
|
53
|
+
notes?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface TasksFile {
|
|
57
|
+
version: string;
|
|
58
|
+
agent_name: string;
|
|
59
|
+
template: string;
|
|
60
|
+
generated_at: string;
|
|
61
|
+
setup_progress: {
|
|
62
|
+
phase: "getting_started" | "fine_tuning" | "operational";
|
|
63
|
+
total_ht: number;
|
|
64
|
+
completed_ht: number;
|
|
65
|
+
percent: number;
|
|
66
|
+
};
|
|
67
|
+
human_tasks: HumanTask[];
|
|
68
|
+
ai_tasks: AITask[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function loadTasks(): TasksFile {
|
|
72
|
+
try { return JSON.parse(fs.readFileSync(TASKS_PATH, "utf-8")); }
|
|
73
|
+
catch { return { version: "1.0.0", agent_name: "", template: "", generated_at: new Date().toISOString(), setup_progress: { phase: "getting_started", total_ht: 0, completed_ht: 0, percent: 0 }, human_tasks: [], ai_tasks: [] }; }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function saveTasks(tasks: TasksFile): void {
|
|
77
|
+
// Update progress
|
|
78
|
+
const total = tasks.human_tasks.length;
|
|
79
|
+
const completed = tasks.human_tasks.filter(t => t.status === "completed").length;
|
|
80
|
+
tasks.setup_progress.total_ht = total;
|
|
81
|
+
tasks.setup_progress.completed_ht = completed;
|
|
82
|
+
tasks.setup_progress.percent = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
83
|
+
|
|
84
|
+
const criticalDone = tasks.human_tasks.filter(t => t.priority === "critical" && t.status === "completed").length;
|
|
85
|
+
const criticalTotal = tasks.human_tasks.filter(t => t.priority === "critical").length;
|
|
86
|
+
if (criticalDone >= criticalTotal && criticalTotal > 0) tasks.setup_progress.phase = "fine_tuning";
|
|
87
|
+
if (tasks.setup_progress.percent >= 90) tasks.setup_progress.phase = "operational";
|
|
88
|
+
|
|
89
|
+
// Unblock HTs that depend on completed HTs
|
|
90
|
+
for (const ht of tasks.human_tasks) {
|
|
91
|
+
if (ht.status === "blocked" && ht.depends_on_ht) {
|
|
92
|
+
const allDepsComplete = ht.depends_on_ht.every(dep =>
|
|
93
|
+
tasks.human_tasks.find(t => t.id === dep)?.status === "completed"
|
|
94
|
+
);
|
|
95
|
+
if (allDepsComplete) ht.status = "pending";
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Unblock ATs whose HT requirements are met
|
|
100
|
+
for (const at of tasks.ai_tasks) {
|
|
101
|
+
if (at.status === "blocked" && at.requires_ht) {
|
|
102
|
+
const allHtDone = at.requires_ht.every(htId =>
|
|
103
|
+
tasks.human_tasks.find(t => t.id === htId)?.status === "completed"
|
|
104
|
+
);
|
|
105
|
+
if (allHtDone) at.status = "pending";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
fs.writeFileSync(TASKS_PATH, JSON.stringify(tasks, null, 2));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function getPendingHTs(tasks: TasksFile): HumanTask[] {
|
|
113
|
+
return tasks.human_tasks.filter(t => t.status === "pending")
|
|
114
|
+
.sort((a, b) => priorityOrder(a.priority) - priorityOrder(b.priority));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function getPendingATs(tasks: TasksFile): AITask[] {
|
|
118
|
+
return tasks.ai_tasks.filter(t => t.status === "pending")
|
|
119
|
+
.sort((a, b) => priorityOrder(a.priority) - priorityOrder(b.priority));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function completeHT(tasks: TasksFile, htId: string, notes?: string): void {
|
|
123
|
+
const ht = tasks.human_tasks.find(t => t.id === htId);
|
|
124
|
+
if (ht) {
|
|
125
|
+
ht.status = "completed";
|
|
126
|
+
ht.completed_at = new Date().toISOString();
|
|
127
|
+
if (notes) ht.notes = notes;
|
|
128
|
+
}
|
|
129
|
+
saveTasks(tasks);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function completeAT(tasks: TasksFile, atId: string, result: string, cost?: number): void {
|
|
133
|
+
const at = tasks.ai_tasks.find(t => t.id === atId);
|
|
134
|
+
if (at) {
|
|
135
|
+
at.status = "completed";
|
|
136
|
+
at.completed_at = new Date().toISOString();
|
|
137
|
+
at.result = result;
|
|
138
|
+
if (cost !== undefined) at.cost_usd = cost;
|
|
139
|
+
}
|
|
140
|
+
saveTasks(tasks);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function addHT(tasks: TasksFile, ht: Omit<HumanTask, "id" | "created_at" | "completed_at" | "notes">): string {
|
|
144
|
+
const id = `ht-${String(tasks.human_tasks.length + 1).padStart(3, "0")}`;
|
|
145
|
+
tasks.human_tasks.push({ ...ht, id, created_at: new Date().toISOString(), completed_at: null, notes: null });
|
|
146
|
+
saveTasks(tasks);
|
|
147
|
+
return id;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function addAT(tasks: TasksFile, at: Omit<AITask, "id" | "created_at">): string {
|
|
151
|
+
const id = `at-${String(tasks.ai_tasks.length + 1).padStart(3, "0")}`;
|
|
152
|
+
tasks.ai_tasks.push({ ...at, id, created_at: new Date().toISOString() });
|
|
153
|
+
saveTasks(tasks);
|
|
154
|
+
return id;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function priorityOrder(p: string): number {
|
|
158
|
+
return { critical: 0, high: 1, normal: 2, low: 3 }[p] ?? 2;
|
|
159
|
+
}
|