openclaw-node-harness 2.0.4 → 2.1.1
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/README.md +646 -3
- package/bin/hyperagent.mjs +419 -0
- package/bin/lane-watchdog.js +23 -2
- package/bin/mesh-agent.js +439 -28
- package/bin/mesh-bridge.js +69 -3
- package/bin/mesh-health-publisher.js +41 -1
- package/bin/mesh-task-daemon.js +821 -26
- package/bin/mesh.js +411 -20
- package/config/claude-settings.json +95 -0
- package/config/daemon.json.template +2 -1
- package/config/git-hooks/pre-commit +13 -0
- package/config/git-hooks/pre-push +12 -0
- package/config/harness-rules.json +174 -0
- package/config/plan-templates/team-bugfix.yaml +52 -0
- package/config/plan-templates/team-deploy.yaml +50 -0
- package/config/plan-templates/team-feature.yaml +71 -0
- package/config/roles/qa-engineer.yaml +36 -0
- package/config/roles/solidity-dev.yaml +51 -0
- package/config/roles/tech-architect.yaml +36 -0
- package/config/rules/framework/solidity.md +22 -0
- package/config/rules/framework/typescript.md +21 -0
- package/config/rules/framework/unity.md +21 -0
- package/config/rules/universal/design-docs.md +18 -0
- package/config/rules/universal/git-hygiene.md +18 -0
- package/config/rules/universal/security.md +19 -0
- package/config/rules/universal/test-standards.md +19 -0
- package/identity/DELEGATION.md +6 -6
- package/install.sh +296 -10
- package/lib/agent-activity.js +2 -2
- package/lib/circling-parser.js +119 -0
- package/lib/exec-safety.js +105 -0
- package/lib/hyperagent-store.mjs +652 -0
- package/lib/kanban-io.js +24 -31
- package/lib/llm-providers.js +16 -0
- package/lib/mcp-knowledge/bench.mjs +118 -0
- package/lib/mcp-knowledge/core.mjs +530 -0
- package/lib/mcp-knowledge/package.json +25 -0
- package/lib/mcp-knowledge/server.mjs +252 -0
- package/lib/mcp-knowledge/test.mjs +802 -0
- package/lib/memory-budget.mjs +261 -0
- package/lib/mesh-collab.js +483 -165
- package/lib/mesh-harness.js +427 -0
- package/lib/mesh-plans.js +79 -50
- package/lib/mesh-tasks.js +132 -49
- package/lib/nats-resolve.js +4 -4
- package/lib/plan-templates.js +226 -0
- package/lib/pre-compression-flush.mjs +322 -0
- package/lib/role-loader.js +292 -0
- package/lib/rule-loader.js +358 -0
- package/lib/session-store.mjs +461 -0
- package/lib/transcript-parser.mjs +292 -0
- package/mission-control/drizzle/soul_schema_update.sql +29 -0
- package/mission-control/drizzle.config.ts +1 -4
- package/mission-control/package-lock.json +1571 -83
- package/mission-control/package.json +6 -2
- package/mission-control/scripts/gen-chronology.js +3 -3
- package/mission-control/scripts/import-pipeline-v2.js +0 -16
- package/mission-control/scripts/import-pipeline.js +0 -15
- package/mission-control/src/app/api/cowork/clusters/[id]/members/route.ts +117 -0
- package/mission-control/src/app/api/cowork/clusters/[id]/route.ts +84 -0
- package/mission-control/src/app/api/cowork/clusters/route.ts +141 -0
- package/mission-control/src/app/api/cowork/dispatch/route.ts +128 -0
- package/mission-control/src/app/api/cowork/events/route.ts +65 -0
- package/mission-control/src/app/api/cowork/intervene/route.ts +259 -0
- package/mission-control/src/app/api/cowork/sessions/[id]/route.ts +37 -0
- package/mission-control/src/app/api/cowork/sessions/route.ts +64 -0
- package/mission-control/src/app/api/diagnostics/route.ts +97 -0
- package/mission-control/src/app/api/diagnostics/test-runner/route.ts +990 -0
- package/mission-control/src/app/api/memory/search/route.ts +6 -3
- package/mission-control/src/app/api/mesh/events/route.ts +95 -19
- package/mission-control/src/app/api/mesh/identity/route.ts +11 -0
- package/mission-control/src/app/api/mesh/tasks/[id]/route.ts +92 -0
- package/mission-control/src/app/api/mesh/tasks/route.ts +91 -0
- package/mission-control/src/app/api/souls/[id]/evolution/route.ts +21 -5
- package/mission-control/src/app/api/souls/[id]/prompt/route.ts +7 -1
- package/mission-control/src/app/api/souls/[id]/propagate/route.ts +14 -2
- package/mission-control/src/app/api/tasks/[id]/handoff/route.ts +8 -2
- package/mission-control/src/app/api/tasks/[id]/route.ts +90 -4
- package/mission-control/src/app/api/tasks/route.ts +21 -30
- package/mission-control/src/app/api/workspace/read/route.ts +11 -0
- package/mission-control/src/app/cowork/page.tsx +261 -0
- package/mission-control/src/app/diagnostics/page.tsx +385 -0
- package/mission-control/src/app/graph/page.tsx +26 -0
- package/mission-control/src/app/memory/page.tsx +1 -1
- package/mission-control/src/app/obsidian/page.tsx +36 -6
- package/mission-control/src/app/roadmap/page.tsx +24 -0
- package/mission-control/src/app/souls/page.tsx +2 -2
- package/mission-control/src/components/board/execution-config.tsx +431 -0
- package/mission-control/src/components/board/kanban-board.tsx +75 -9
- package/mission-control/src/components/board/kanban-column.tsx +135 -19
- package/mission-control/src/components/board/task-card.tsx +55 -2
- package/mission-control/src/components/board/unified-task-dialog.tsx +82 -4
- package/mission-control/src/components/cowork/cluster-card.tsx +176 -0
- package/mission-control/src/components/cowork/create-cluster-dialog.tsx +251 -0
- package/mission-control/src/components/cowork/dispatch-form.tsx +423 -0
- package/mission-control/src/components/cowork/role-picker.tsx +102 -0
- package/mission-control/src/components/cowork/session-card.tsx +284 -0
- package/mission-control/src/components/layout/sidebar.tsx +39 -2
- package/mission-control/src/lib/__tests__/daily-log.test.ts +82 -0
- package/mission-control/src/lib/__tests__/memory-md.test.ts +87 -0
- package/mission-control/src/lib/__tests__/mesh-kv-sync.test.ts +465 -0
- package/mission-control/src/lib/__tests__/mocks/mock-kv.ts +131 -0
- package/mission-control/src/lib/__tests__/status-kanban.test.ts +46 -0
- package/mission-control/src/lib/__tests__/task-markdown.test.ts +188 -0
- package/mission-control/src/lib/__tests__/wikilinks.test.ts +175 -0
- package/mission-control/src/lib/config.ts +67 -0
- package/mission-control/src/lib/db/index.ts +85 -1
- package/mission-control/src/lib/db/schema.ts +61 -3
- package/mission-control/src/lib/hooks.ts +309 -0
- package/mission-control/src/lib/memory/entities.ts +3 -2
- package/mission-control/src/lib/memory/extract.ts +2 -1
- package/mission-control/src/lib/memory/retrieval.ts +3 -2
- package/mission-control/src/lib/nats.ts +66 -1
- package/mission-control/src/lib/parsers/task-markdown.ts +52 -2
- package/mission-control/src/lib/parsers/transcript.ts +4 -4
- package/mission-control/src/lib/scheduler.ts +12 -11
- package/mission-control/src/lib/sync/mesh-kv.ts +279 -0
- package/mission-control/src/lib/sync/tasks.ts +23 -1
- package/mission-control/src/lib/task-id.ts +32 -0
- package/mission-control/src/lib/tts/index.ts +33 -9
- package/mission-control/src/middleware.ts +82 -0
- package/mission-control/tsconfig.json +2 -1
- package/mission-control/vitest.config.ts +14 -0
- package/package.json +15 -2
- package/services/launchd/ai.openclaw.log-rotate.plist +11 -0
- package/services/launchd/ai.openclaw.mesh-deploy-listener.plist +4 -0
- package/services/launchd/ai.openclaw.mesh-health-publisher.plist +4 -0
- package/services/launchd/ai.openclaw.mission-control.plist +1 -1
- package/services/service-manifest.json +1 -1
- package/skills/cc-godmode/references/agents.md +8 -8
- package/uninstall.sh +37 -9
- package/workspace-bin/memory-daemon.mjs +199 -5
- package/workspace-bin/session-search.mjs +204 -0
- package/workspace-bin/web-fetch.mjs +65 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* hyperagent.mjs — CLI for the HyperAgent self-improving agent protocol.
|
|
4
|
+
*
|
|
5
|
+
* Pure data infrastructure — no LLM calls. Manages telemetry, strategies,
|
|
6
|
+
* reflections, and proposals in SQLite. LLM synthesis happens at the agent
|
|
7
|
+
* level via harness rules.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* hyperagent status
|
|
11
|
+
* hyperagent log <json>
|
|
12
|
+
* hyperagent telemetry [--domain X] [--last N]
|
|
13
|
+
* hyperagent strategies [--domain X]
|
|
14
|
+
* hyperagent strategy <id>
|
|
15
|
+
* hyperagent seed-strategy <json>
|
|
16
|
+
* hyperagent reflect [--force]
|
|
17
|
+
* hyperagent reflect --write-synthesis <json>
|
|
18
|
+
* hyperagent proposals
|
|
19
|
+
* hyperagent approve <id>
|
|
20
|
+
* hyperagent reject <id> [reason]
|
|
21
|
+
* hyperagent shadow <id> [--window 60]
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import path from 'path';
|
|
25
|
+
import os from 'os';
|
|
26
|
+
import fs from 'fs';
|
|
27
|
+
import { createHyperAgentStore } from '../lib/hyperagent-store.mjs';
|
|
28
|
+
|
|
29
|
+
const OPENCLAW_HOME = process.env.OPENCLAW_HOME || path.join(os.homedir(), '.openclaw');
|
|
30
|
+
|
|
31
|
+
const NODE_ID = process.env.OPENCLAW_NODE_ID || os.hostname();
|
|
32
|
+
const SOUL_ID = process.env.OPENCLAW_SOUL_ID || 'unknown';
|
|
33
|
+
|
|
34
|
+
// ── Helpers ────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
function parseArg(args, flag, defaultVal = null) {
|
|
37
|
+
const idx = args.indexOf(flag);
|
|
38
|
+
if (idx === -1 || idx + 1 >= args.length) return defaultVal;
|
|
39
|
+
return args[idx + 1];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function hasFlag(args, flag) {
|
|
43
|
+
return args.includes(flag);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function die(msg) {
|
|
47
|
+
console.error(`error: ${msg}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function printJson(obj) {
|
|
52
|
+
console.log(JSON.stringify(obj, null, 2));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function printTable(rows, columns) {
|
|
56
|
+
if (rows.length === 0) { console.log('(none)'); return; }
|
|
57
|
+
const widths = columns.map(c => Math.max(c.label.length, ...rows.map(r => String(c.get(r)).length)));
|
|
58
|
+
const header = columns.map((c, i) => c.label.padEnd(widths[i])).join(' ');
|
|
59
|
+
console.log(header);
|
|
60
|
+
console.log(columns.map((_, i) => '─'.repeat(widths[i])).join(' '));
|
|
61
|
+
for (const row of rows) {
|
|
62
|
+
console.log(columns.map((c, i) => String(c.get(row)).padEnd(widths[i])).join(' '));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Commands ────────────────────────────────────
|
|
67
|
+
|
|
68
|
+
function cmdStatus(store) {
|
|
69
|
+
const stats = store.getStats();
|
|
70
|
+
console.log(`HyperAgent Protocol — ${OPENCLAW_HOME}`);
|
|
71
|
+
console.log(` Telemetry entries: ${stats.telemetry}`);
|
|
72
|
+
console.log(` Active strategies: ${stats.strategies}`);
|
|
73
|
+
console.log(` Reflections: ${stats.reflections}`);
|
|
74
|
+
console.log(` Pending proposals: ${stats.pendingProposals}`);
|
|
75
|
+
console.log(` Unreflected tasks: ${stats.unreflected}`);
|
|
76
|
+
|
|
77
|
+
const pendingSynthesis = store.getPendingSynthesis();
|
|
78
|
+
if (pendingSynthesis) console.log(` Pending synthesis: reflection #${pendingSynthesis.id}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function cmdLog(store, args) {
|
|
82
|
+
const jsonStr = args[0];
|
|
83
|
+
if (!jsonStr) die('usage: hyperagent log <json>');
|
|
84
|
+
|
|
85
|
+
let entry;
|
|
86
|
+
try { entry = JSON.parse(jsonStr); }
|
|
87
|
+
catch { die('invalid JSON'); }
|
|
88
|
+
|
|
89
|
+
if (!entry.domain) die('domain is required');
|
|
90
|
+
if (!entry.outcome) die('outcome is required');
|
|
91
|
+
|
|
92
|
+
const row = store.logTelemetry({
|
|
93
|
+
node_id: NODE_ID,
|
|
94
|
+
soul_id: SOUL_ID,
|
|
95
|
+
task_id: entry.task_id || null,
|
|
96
|
+
domain: entry.domain,
|
|
97
|
+
subdomain: entry.subdomain || null,
|
|
98
|
+
strategy_id: entry.strategy_id || null,
|
|
99
|
+
outcome: entry.outcome,
|
|
100
|
+
iterations: entry.iterations || 1,
|
|
101
|
+
duration_minutes: entry.duration_minutes || null,
|
|
102
|
+
meta_notes: entry.meta_notes || null,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const flags = JSON.parse(row.pattern_flags || '[]');
|
|
106
|
+
console.log(`logged: id=${row.id} domain=${row.domain} outcome=${row.outcome} flags=[${flags.join(',')}]`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function cmdTelemetry(store, args) {
|
|
110
|
+
const domain = parseArg(args, '--domain');
|
|
111
|
+
const last = parseInt(parseArg(args, '--last', '20'));
|
|
112
|
+
const rows = store.getTelemetry({ domain, last });
|
|
113
|
+
|
|
114
|
+
printTable(rows, [
|
|
115
|
+
{ label: 'ID', get: r => r.id },
|
|
116
|
+
{ label: 'Domain', get: r => r.domain },
|
|
117
|
+
{ label: 'Sub', get: r => r.subdomain || '-' },
|
|
118
|
+
{ label: 'Outcome', get: r => r.outcome },
|
|
119
|
+
{ label: 'Iter', get: r => r.iterations },
|
|
120
|
+
{ label: 'Flags', get: r => { const f = JSON.parse(r.pattern_flags || '[]'); return f.length ? f.join(',') : '-'; }},
|
|
121
|
+
{ label: 'Date', get: r => r.created_at?.slice(0, 10) || '-' },
|
|
122
|
+
]);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function cmdStrategies(store, args) {
|
|
126
|
+
const domain = parseArg(args, '--domain');
|
|
127
|
+
const rows = store.listStrategies({ domain });
|
|
128
|
+
|
|
129
|
+
printTable(rows, [
|
|
130
|
+
{ label: 'ID', get: r => r.id },
|
|
131
|
+
{ label: 'Domain', get: r => r.domain },
|
|
132
|
+
{ label: 'Sub', get: r => r.subdomain || '-' },
|
|
133
|
+
{ label: 'Title', get: r => r.title.slice(0, 40) },
|
|
134
|
+
{ label: 'Ver', get: r => r.version },
|
|
135
|
+
{ label: 'Source', get: r => r.source },
|
|
136
|
+
{ label: 'Updated', get: r => r.updated_at?.slice(0, 10) || '-' },
|
|
137
|
+
]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function cmdStrategy(store, args) {
|
|
141
|
+
const id = parseInt(args[0]);
|
|
142
|
+
if (!id) die('usage: hyperagent strategy <id>');
|
|
143
|
+
|
|
144
|
+
const row = store.listStrategies({}).find(r => r.id === id);
|
|
145
|
+
if (!row) die(`strategy ${id} not found`);
|
|
146
|
+
|
|
147
|
+
console.log(`# Strategy: ${row.title}`);
|
|
148
|
+
console.log(`Domain: ${row.domain}${row.subdomain ? '/' + row.subdomain : ''}`);
|
|
149
|
+
console.log(`Version: ${row.version} | Source: ${row.source} | Active: ${row.active ? 'yes' : 'no'}`);
|
|
150
|
+
console.log(`Updated: ${row.updated_at}`);
|
|
151
|
+
console.log('---');
|
|
152
|
+
console.log(row.content);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function cmdSeedStrategy(store, args) {
|
|
156
|
+
const jsonStr = args[0];
|
|
157
|
+
if (!jsonStr) die('usage: hyperagent seed-strategy <json>');
|
|
158
|
+
|
|
159
|
+
let data;
|
|
160
|
+
try { data = JSON.parse(jsonStr); }
|
|
161
|
+
catch { die('invalid JSON'); }
|
|
162
|
+
|
|
163
|
+
if (!data.domain || !data.title || !data.content) {
|
|
164
|
+
die('required: domain, title, content');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const row = store.putStrategy({
|
|
168
|
+
domain: data.domain,
|
|
169
|
+
subdomain: data.subdomain || null,
|
|
170
|
+
title: data.title,
|
|
171
|
+
content: data.content,
|
|
172
|
+
source: data.source || 'manual',
|
|
173
|
+
node_id: data.node_id || null,
|
|
174
|
+
supersedes: data.supersedes || null,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
console.log(`created: id=${row.id} domain=${row.domain} title="${row.title}"`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function cmdReflect(store, args) {
|
|
181
|
+
// --pending: query DB for reflections awaiting synthesis
|
|
182
|
+
if (hasFlag(args, '--pending')) {
|
|
183
|
+
return cmdReflectPending(store);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// --write-synthesis: accept LLM output and write to DB
|
|
187
|
+
if (hasFlag(args, '--write-synthesis')) {
|
|
188
|
+
const jsonStr = args[args.indexOf('--write-synthesis') + 1];
|
|
189
|
+
if (!jsonStr) die('usage: hyperagent reflect --write-synthesis <json>');
|
|
190
|
+
|
|
191
|
+
let data;
|
|
192
|
+
try { data = JSON.parse(jsonStr); }
|
|
193
|
+
catch { die('invalid JSON'); }
|
|
194
|
+
|
|
195
|
+
if (!data.reflection_id) die('reflection_id required');
|
|
196
|
+
|
|
197
|
+
store.writeSynthesis(data.reflection_id, {
|
|
198
|
+
hypotheses: data.hypotheses || [],
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Create proposals if provided
|
|
202
|
+
if (data.proposals && data.proposals.length > 0) {
|
|
203
|
+
const maxProposals = Math.min(data.proposals.length, 2); // cap at 2
|
|
204
|
+
for (let i = 0; i < maxProposals; i++) {
|
|
205
|
+
const p = data.proposals[i];
|
|
206
|
+
const row = store.putProposal({
|
|
207
|
+
reflection_id: data.reflection_id,
|
|
208
|
+
node_id: NODE_ID,
|
|
209
|
+
soul_id: SOUL_ID,
|
|
210
|
+
title: p.title,
|
|
211
|
+
description: p.description,
|
|
212
|
+
proposal_type: p.proposal_type || 'workflow_change',
|
|
213
|
+
target_ref: p.target_ref || null,
|
|
214
|
+
diff_content: p.diff_content ? (typeof p.diff_content === 'string' ? p.diff_content : JSON.stringify(p.diff_content)) : null,
|
|
215
|
+
});
|
|
216
|
+
console.log(`proposal created: id=${row.id} type=${row.proposal_type} "${row.title}"`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
console.log(`synthesis written to reflection ${data.reflection_id}`);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Regular reflect: compute stats from unreflected telemetry
|
|
225
|
+
const force = hasFlag(args, '--force');
|
|
226
|
+
const unreflected = store.getUnreflectedCount();
|
|
227
|
+
|
|
228
|
+
if (unreflected < 5 && !force) {
|
|
229
|
+
console.log(`only ${unreflected} unreflected tasks (need 5). Use --force to override.`);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Get the last reflection's to_id
|
|
234
|
+
const lastReflection = store.getLastReflection();
|
|
235
|
+
const sinceId = lastReflection ? lastReflection.telemetry_to_id : 0;
|
|
236
|
+
|
|
237
|
+
// Compute stats
|
|
238
|
+
const stats = store.computeStats(sinceId);
|
|
239
|
+
if (!stats) {
|
|
240
|
+
console.log('no telemetry to reflect on.');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Write reflection row (raw stats only, no hypotheses yet)
|
|
245
|
+
const reflection = store.putReflection({
|
|
246
|
+
node_id: NODE_ID,
|
|
247
|
+
soul_id: SOUL_ID,
|
|
248
|
+
telemetry_from_id: stats.fromId,
|
|
249
|
+
telemetry_to_id: stats.toId,
|
|
250
|
+
telemetry_count: stats.totalTasks,
|
|
251
|
+
raw_stats: stats,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Get recent telemetry sample for the agent
|
|
255
|
+
const sample = store.getTelemetrySince(sinceId).slice(-5).map(e => ({
|
|
256
|
+
id: e.id,
|
|
257
|
+
domain: e.domain,
|
|
258
|
+
outcome: e.outcome,
|
|
259
|
+
iterations: e.iterations,
|
|
260
|
+
flags: JSON.parse(e.pattern_flags || '[]'),
|
|
261
|
+
meta_notes: e.meta_notes,
|
|
262
|
+
}));
|
|
263
|
+
|
|
264
|
+
// Get previous hypotheses for continuity
|
|
265
|
+
const previousHypotheses = lastReflection && lastReflection.hypotheses
|
|
266
|
+
? JSON.parse(lastReflection.hypotheses)
|
|
267
|
+
: null;
|
|
268
|
+
|
|
269
|
+
// Auto-expire stale pending reflections (>24h without synthesis)
|
|
270
|
+
store.expireStalePending();
|
|
271
|
+
|
|
272
|
+
console.log(`reflection ${reflection.id} created (${stats.totalTasks} tasks)`);
|
|
273
|
+
console.log(` success rate: ${stats.successRate}%`);
|
|
274
|
+
console.log(` avg iterations: ${stats.avgIterations}`);
|
|
275
|
+
console.log(` strategy hit rate: ${stats.strategyHitRate}%`);
|
|
276
|
+
console.log(` pending synthesis — agent will pick up via harness rule`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function cmdReflectPending(store) {
|
|
280
|
+
// Query for reflections awaiting synthesis (hypotheses IS NULL, < 24h old)
|
|
281
|
+
const pending = store.getPendingSynthesis();
|
|
282
|
+
if (!pending) {
|
|
283
|
+
// Silent — no output means nothing to do. Harness rule exits cleanly.
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Get the telemetry in the reflection window
|
|
288
|
+
const entries = store.getTelemetrySince(pending.telemetry_from_id - 1)
|
|
289
|
+
.filter(e => e.id >= pending.telemetry_from_id && e.id <= pending.telemetry_to_id);
|
|
290
|
+
|
|
291
|
+
const sample = entries.slice(-5).map(e => ({
|
|
292
|
+
id: e.id,
|
|
293
|
+
domain: e.domain,
|
|
294
|
+
subdomain: e.subdomain,
|
|
295
|
+
outcome: e.outcome,
|
|
296
|
+
iterations: e.iterations,
|
|
297
|
+
flags: JSON.parse(e.pattern_flags || '[]'),
|
|
298
|
+
meta_notes: e.meta_notes,
|
|
299
|
+
}));
|
|
300
|
+
|
|
301
|
+
// Get previous hypotheses for continuity
|
|
302
|
+
const previous = store.getPreviousReflection(pending.id);
|
|
303
|
+
const previousHypotheses = previous && previous.hypotheses
|
|
304
|
+
? JSON.parse(previous.hypotheses)
|
|
305
|
+
: null;
|
|
306
|
+
|
|
307
|
+
const output = {
|
|
308
|
+
reflection_id: pending.id,
|
|
309
|
+
stats: JSON.parse(pending.raw_stats),
|
|
310
|
+
telemetry_sample: sample,
|
|
311
|
+
previous_hypotheses: previousHypotheses,
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// Output JSON — the agent reads this, synthesizes, and calls --write-synthesis
|
|
315
|
+
printJson(output);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function cmdProposals(store) {
|
|
319
|
+
const rows = store.getProposals();
|
|
320
|
+
|
|
321
|
+
printTable(rows, [
|
|
322
|
+
{ label: 'ID', get: r => r.id },
|
|
323
|
+
{ label: 'Status', get: r => r.status },
|
|
324
|
+
{ label: 'Type', get: r => r.proposal_type },
|
|
325
|
+
{ label: 'Title', get: r => r.title.slice(0, 40) },
|
|
326
|
+
{ label: 'Eval', get: r => r.eval_telemetry_count || '-' },
|
|
327
|
+
{ label: 'Date', get: r => r.created_at?.slice(0, 10) || '-' },
|
|
328
|
+
]);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function cmdApprove(store, args) {
|
|
332
|
+
const id = parseInt(args[0]);
|
|
333
|
+
if (!id) die('usage: hyperagent approve <id>');
|
|
334
|
+
|
|
335
|
+
const result = store.approveProposal(id, 'human');
|
|
336
|
+
if (!result) die(`proposal ${id} not found`);
|
|
337
|
+
|
|
338
|
+
console.log(`approved: id=${result.id} type=${result.proposal_type} "${result.title}"`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function cmdReject(store, args) {
|
|
342
|
+
const id = parseInt(args[0]);
|
|
343
|
+
if (!id) die('usage: hyperagent reject <id> [reason]');
|
|
344
|
+
|
|
345
|
+
const result = store.rejectProposal(id, args.slice(1).join(' ') || 'human');
|
|
346
|
+
if (!result) die(`proposal ${id} not found`);
|
|
347
|
+
|
|
348
|
+
console.log(`rejected: id=${result.id} "${result.title}"`);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function cmdShadow(store, args) {
|
|
352
|
+
const id = parseInt(args[0]);
|
|
353
|
+
if (!id) die('usage: hyperagent shadow <id> [--window 60]');
|
|
354
|
+
|
|
355
|
+
const window = parseInt(parseArg(args, '--window', '60'));
|
|
356
|
+
const result = store.startShadowEval(id, window);
|
|
357
|
+
if (!result) die(`proposal ${id} not found`);
|
|
358
|
+
|
|
359
|
+
console.log(`shadow eval started: id=${result.id} window=${window}min`);
|
|
360
|
+
console.log(` start: ${result.eval_window_start}`);
|
|
361
|
+
console.log(` end: ${result.eval_window_end}`);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ── Main ────────────────────────────────────
|
|
365
|
+
|
|
366
|
+
const args = process.argv.slice(2);
|
|
367
|
+
const command = args[0];
|
|
368
|
+
const commandArgs = args.slice(1);
|
|
369
|
+
|
|
370
|
+
if (!command || command === '--help' || command === '-h') {
|
|
371
|
+
console.log(`hyperagent — self-improving agent protocol CLI
|
|
372
|
+
|
|
373
|
+
commands:
|
|
374
|
+
status overview stats
|
|
375
|
+
log <json> log telemetry entry
|
|
376
|
+
telemetry [--domain X] [--last N] list entries
|
|
377
|
+
strategies [--domain X] list active strategies
|
|
378
|
+
strategy <id> show strategy detail
|
|
379
|
+
seed-strategy <json> import strategy
|
|
380
|
+
reflect [--force] trigger reflection
|
|
381
|
+
reflect --pending get pending reflection for synthesis (JSON)
|
|
382
|
+
reflect --write-synthesis <json> write LLM synthesis
|
|
383
|
+
proposals list proposals
|
|
384
|
+
approve <id> approve proposal
|
|
385
|
+
reject <id> [reason] reject proposal
|
|
386
|
+
shadow <id> [--window 60] start shadow eval
|
|
387
|
+
|
|
388
|
+
env:
|
|
389
|
+
OPENCLAW_HOME base dir (default: ~/.openclaw)
|
|
390
|
+
OPENCLAW_NODE_ID node identifier (default: hostname)
|
|
391
|
+
OPENCLAW_SOUL_ID soul identifier (default: unknown)`);
|
|
392
|
+
process.exit(0);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
let store;
|
|
396
|
+
try {
|
|
397
|
+
store = createHyperAgentStore({ dbPath: path.join(OPENCLAW_HOME, 'state.db') });
|
|
398
|
+
} catch (err) {
|
|
399
|
+
die(`failed to open store: ${err.message}`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
switch (command) {
|
|
404
|
+
case 'status': cmdStatus(store); break;
|
|
405
|
+
case 'log': cmdLog(store, commandArgs); break;
|
|
406
|
+
case 'telemetry': cmdTelemetry(store, commandArgs); break;
|
|
407
|
+
case 'strategies': cmdStrategies(store, commandArgs); break;
|
|
408
|
+
case 'strategy': cmdStrategy(store, commandArgs); break;
|
|
409
|
+
case 'seed-strategy': cmdSeedStrategy(store, commandArgs); break;
|
|
410
|
+
case 'reflect': cmdReflect(store, commandArgs); break;
|
|
411
|
+
case 'proposals': cmdProposals(store); break;
|
|
412
|
+
case 'approve': cmdApprove(store, commandArgs); break;
|
|
413
|
+
case 'reject': cmdReject(store, commandArgs); break;
|
|
414
|
+
case 'shadow': cmdShadow(store, commandArgs); break;
|
|
415
|
+
default: die(`unknown command: ${command}. Run hyperagent --help`);
|
|
416
|
+
}
|
|
417
|
+
} finally {
|
|
418
|
+
store.close();
|
|
419
|
+
}
|
package/bin/lane-watchdog.js
CHANGED
|
@@ -37,6 +37,11 @@ let lastInterventionAt = 0;
|
|
|
37
37
|
let logWatcher = null;
|
|
38
38
|
let errWatcher = null;
|
|
39
39
|
|
|
40
|
+
// Incident log dedup: suppress identical messages within 60s
|
|
41
|
+
let lastIncidentMsg = '';
|
|
42
|
+
let lastIncidentAt = 0;
|
|
43
|
+
let suppressedCount = 0;
|
|
44
|
+
|
|
40
45
|
// Track detected events
|
|
41
46
|
const events = {
|
|
42
47
|
agentTimeout: null, // timestamp of last "embedded run timeout"
|
|
@@ -45,6 +50,22 @@ const events = {
|
|
|
45
50
|
|
|
46
51
|
// --- Helpers ---
|
|
47
52
|
function log(msg) {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
// Dedup: suppress identical messages within 60s
|
|
55
|
+
if (msg === lastIncidentMsg && (now - lastIncidentAt) < 60_000) {
|
|
56
|
+
suppressedCount++;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// If we suppressed duplicates, emit a summary before the new message
|
|
60
|
+
if (suppressedCount > 0) {
|
|
61
|
+
const summaryLine = `${new Date().toISOString()} [lane-watchdog] (suppressed ${suppressedCount} duplicate message(s))`;
|
|
62
|
+
console.log(summaryLine);
|
|
63
|
+
try { fs.appendFileSync(INCIDENT_LOG, summaryLine + '\n'); } catch { /* best effort */ }
|
|
64
|
+
}
|
|
65
|
+
lastIncidentMsg = msg;
|
|
66
|
+
lastIncidentAt = now;
|
|
67
|
+
suppressedCount = 0;
|
|
68
|
+
|
|
48
69
|
const ts = new Date().toISOString();
|
|
49
70
|
const line = `${ts} [lane-watchdog] ${msg}`;
|
|
50
71
|
console.log(line);
|
|
@@ -220,8 +241,8 @@ function main() {
|
|
|
220
241
|
for (const sig of ['SIGTERM', 'SIGINT']) {
|
|
221
242
|
process.on(sig, () => {
|
|
222
243
|
log(`Received ${sig}, shutting down`);
|
|
223
|
-
if (logWatcher)
|
|
224
|
-
if (errWatcher)
|
|
244
|
+
if (logWatcher) logWatcher.close();
|
|
245
|
+
if (errWatcher) errWatcher.close();
|
|
225
246
|
process.exit(0);
|
|
226
247
|
});
|
|
227
248
|
}
|