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.
Files changed (134) hide show
  1. package/README.md +646 -3
  2. package/bin/hyperagent.mjs +419 -0
  3. package/bin/lane-watchdog.js +23 -2
  4. package/bin/mesh-agent.js +439 -28
  5. package/bin/mesh-bridge.js +69 -3
  6. package/bin/mesh-health-publisher.js +41 -1
  7. package/bin/mesh-task-daemon.js +821 -26
  8. package/bin/mesh.js +411 -20
  9. package/config/claude-settings.json +95 -0
  10. package/config/daemon.json.template +2 -1
  11. package/config/git-hooks/pre-commit +13 -0
  12. package/config/git-hooks/pre-push +12 -0
  13. package/config/harness-rules.json +174 -0
  14. package/config/plan-templates/team-bugfix.yaml +52 -0
  15. package/config/plan-templates/team-deploy.yaml +50 -0
  16. package/config/plan-templates/team-feature.yaml +71 -0
  17. package/config/roles/qa-engineer.yaml +36 -0
  18. package/config/roles/solidity-dev.yaml +51 -0
  19. package/config/roles/tech-architect.yaml +36 -0
  20. package/config/rules/framework/solidity.md +22 -0
  21. package/config/rules/framework/typescript.md +21 -0
  22. package/config/rules/framework/unity.md +21 -0
  23. package/config/rules/universal/design-docs.md +18 -0
  24. package/config/rules/universal/git-hygiene.md +18 -0
  25. package/config/rules/universal/security.md +19 -0
  26. package/config/rules/universal/test-standards.md +19 -0
  27. package/identity/DELEGATION.md +6 -6
  28. package/install.sh +296 -10
  29. package/lib/agent-activity.js +2 -2
  30. package/lib/circling-parser.js +119 -0
  31. package/lib/exec-safety.js +105 -0
  32. package/lib/hyperagent-store.mjs +652 -0
  33. package/lib/kanban-io.js +24 -31
  34. package/lib/llm-providers.js +16 -0
  35. package/lib/mcp-knowledge/bench.mjs +118 -0
  36. package/lib/mcp-knowledge/core.mjs +530 -0
  37. package/lib/mcp-knowledge/package.json +25 -0
  38. package/lib/mcp-knowledge/server.mjs +252 -0
  39. package/lib/mcp-knowledge/test.mjs +802 -0
  40. package/lib/memory-budget.mjs +261 -0
  41. package/lib/mesh-collab.js +483 -165
  42. package/lib/mesh-harness.js +427 -0
  43. package/lib/mesh-plans.js +79 -50
  44. package/lib/mesh-tasks.js +132 -49
  45. package/lib/nats-resolve.js +4 -4
  46. package/lib/plan-templates.js +226 -0
  47. package/lib/pre-compression-flush.mjs +322 -0
  48. package/lib/role-loader.js +292 -0
  49. package/lib/rule-loader.js +358 -0
  50. package/lib/session-store.mjs +461 -0
  51. package/lib/transcript-parser.mjs +292 -0
  52. package/mission-control/drizzle/soul_schema_update.sql +29 -0
  53. package/mission-control/drizzle.config.ts +1 -4
  54. package/mission-control/package-lock.json +1571 -83
  55. package/mission-control/package.json +6 -2
  56. package/mission-control/scripts/gen-chronology.js +3 -3
  57. package/mission-control/scripts/import-pipeline-v2.js +0 -16
  58. package/mission-control/scripts/import-pipeline.js +0 -15
  59. package/mission-control/src/app/api/cowork/clusters/[id]/members/route.ts +117 -0
  60. package/mission-control/src/app/api/cowork/clusters/[id]/route.ts +84 -0
  61. package/mission-control/src/app/api/cowork/clusters/route.ts +141 -0
  62. package/mission-control/src/app/api/cowork/dispatch/route.ts +128 -0
  63. package/mission-control/src/app/api/cowork/events/route.ts +65 -0
  64. package/mission-control/src/app/api/cowork/intervene/route.ts +259 -0
  65. package/mission-control/src/app/api/cowork/sessions/[id]/route.ts +37 -0
  66. package/mission-control/src/app/api/cowork/sessions/route.ts +64 -0
  67. package/mission-control/src/app/api/diagnostics/route.ts +97 -0
  68. package/mission-control/src/app/api/diagnostics/test-runner/route.ts +990 -0
  69. package/mission-control/src/app/api/memory/search/route.ts +6 -3
  70. package/mission-control/src/app/api/mesh/events/route.ts +95 -19
  71. package/mission-control/src/app/api/mesh/identity/route.ts +11 -0
  72. package/mission-control/src/app/api/mesh/tasks/[id]/route.ts +92 -0
  73. package/mission-control/src/app/api/mesh/tasks/route.ts +91 -0
  74. package/mission-control/src/app/api/souls/[id]/evolution/route.ts +21 -5
  75. package/mission-control/src/app/api/souls/[id]/prompt/route.ts +7 -1
  76. package/mission-control/src/app/api/souls/[id]/propagate/route.ts +14 -2
  77. package/mission-control/src/app/api/tasks/[id]/handoff/route.ts +8 -2
  78. package/mission-control/src/app/api/tasks/[id]/route.ts +90 -4
  79. package/mission-control/src/app/api/tasks/route.ts +21 -30
  80. package/mission-control/src/app/api/workspace/read/route.ts +11 -0
  81. package/mission-control/src/app/cowork/page.tsx +261 -0
  82. package/mission-control/src/app/diagnostics/page.tsx +385 -0
  83. package/mission-control/src/app/graph/page.tsx +26 -0
  84. package/mission-control/src/app/memory/page.tsx +1 -1
  85. package/mission-control/src/app/obsidian/page.tsx +36 -6
  86. package/mission-control/src/app/roadmap/page.tsx +24 -0
  87. package/mission-control/src/app/souls/page.tsx +2 -2
  88. package/mission-control/src/components/board/execution-config.tsx +431 -0
  89. package/mission-control/src/components/board/kanban-board.tsx +75 -9
  90. package/mission-control/src/components/board/kanban-column.tsx +135 -19
  91. package/mission-control/src/components/board/task-card.tsx +55 -2
  92. package/mission-control/src/components/board/unified-task-dialog.tsx +82 -4
  93. package/mission-control/src/components/cowork/cluster-card.tsx +176 -0
  94. package/mission-control/src/components/cowork/create-cluster-dialog.tsx +251 -0
  95. package/mission-control/src/components/cowork/dispatch-form.tsx +423 -0
  96. package/mission-control/src/components/cowork/role-picker.tsx +102 -0
  97. package/mission-control/src/components/cowork/session-card.tsx +284 -0
  98. package/mission-control/src/components/layout/sidebar.tsx +39 -2
  99. package/mission-control/src/lib/__tests__/daily-log.test.ts +82 -0
  100. package/mission-control/src/lib/__tests__/memory-md.test.ts +87 -0
  101. package/mission-control/src/lib/__tests__/mesh-kv-sync.test.ts +465 -0
  102. package/mission-control/src/lib/__tests__/mocks/mock-kv.ts +131 -0
  103. package/mission-control/src/lib/__tests__/status-kanban.test.ts +46 -0
  104. package/mission-control/src/lib/__tests__/task-markdown.test.ts +188 -0
  105. package/mission-control/src/lib/__tests__/wikilinks.test.ts +175 -0
  106. package/mission-control/src/lib/config.ts +67 -0
  107. package/mission-control/src/lib/db/index.ts +85 -1
  108. package/mission-control/src/lib/db/schema.ts +61 -3
  109. package/mission-control/src/lib/hooks.ts +309 -0
  110. package/mission-control/src/lib/memory/entities.ts +3 -2
  111. package/mission-control/src/lib/memory/extract.ts +2 -1
  112. package/mission-control/src/lib/memory/retrieval.ts +3 -2
  113. package/mission-control/src/lib/nats.ts +66 -1
  114. package/mission-control/src/lib/parsers/task-markdown.ts +52 -2
  115. package/mission-control/src/lib/parsers/transcript.ts +4 -4
  116. package/mission-control/src/lib/scheduler.ts +12 -11
  117. package/mission-control/src/lib/sync/mesh-kv.ts +279 -0
  118. package/mission-control/src/lib/sync/tasks.ts +23 -1
  119. package/mission-control/src/lib/task-id.ts +32 -0
  120. package/mission-control/src/lib/tts/index.ts +33 -9
  121. package/mission-control/src/middleware.ts +82 -0
  122. package/mission-control/tsconfig.json +2 -1
  123. package/mission-control/vitest.config.ts +14 -0
  124. package/package.json +15 -2
  125. package/services/launchd/ai.openclaw.log-rotate.plist +11 -0
  126. package/services/launchd/ai.openclaw.mesh-deploy-listener.plist +4 -0
  127. package/services/launchd/ai.openclaw.mesh-health-publisher.plist +4 -0
  128. package/services/launchd/ai.openclaw.mission-control.plist +1 -1
  129. package/services/service-manifest.json +1 -1
  130. package/skills/cc-godmode/references/agents.md +8 -8
  131. package/uninstall.sh +37 -9
  132. package/workspace-bin/memory-daemon.mjs +199 -5
  133. package/workspace-bin/session-search.mjs +204 -0
  134. 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
+ }
@@ -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) fs.unwatchFile(GATEWAY_LOG);
224
- if (errWatcher) fs.unwatchFile(GATEWAY_ERR_LOG);
244
+ if (logWatcher) logWatcher.close();
245
+ if (errWatcher) errWatcher.close();
225
246
  process.exit(0);
226
247
  });
227
248
  }