openclaw-node-harness 2.0.3 → 2.1.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.
Files changed (118) hide show
  1. package/README.md +646 -3
  2. package/bin/hyperagent.mjs +419 -0
  3. package/bin/mesh-agent.js +603 -81
  4. package/bin/mesh-bridge.js +340 -11
  5. package/bin/mesh-deploy-listener.js +119 -97
  6. package/bin/mesh-deploy.js +8 -0
  7. package/bin/mesh-task-daemon.js +1005 -40
  8. package/bin/mesh.js +423 -6
  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 +300 -8
  29. package/lib/circling-parser.js +119 -0
  30. package/lib/hyperagent-store.mjs +652 -0
  31. package/lib/kanban-io.js +59 -10
  32. package/lib/mcp-knowledge/bench.mjs +118 -0
  33. package/lib/mcp-knowledge/core.mjs +528 -0
  34. package/lib/mcp-knowledge/package.json +25 -0
  35. package/lib/mcp-knowledge/server.mjs +245 -0
  36. package/lib/mcp-knowledge/test.mjs +802 -0
  37. package/lib/memory-budget.mjs +261 -0
  38. package/lib/mesh-collab.js +354 -4
  39. package/lib/mesh-harness.js +427 -0
  40. package/lib/mesh-plans.js +13 -5
  41. package/lib/mesh-registry.js +11 -2
  42. package/lib/mesh-tasks.js +67 -0
  43. package/lib/plan-templates.js +226 -0
  44. package/lib/pre-compression-flush.mjs +320 -0
  45. package/lib/role-loader.js +292 -0
  46. package/lib/rule-loader.js +358 -0
  47. package/lib/session-store.mjs +458 -0
  48. package/lib/transcript-parser.mjs +292 -0
  49. package/mission-control/drizzle/soul_schema_update.sql +29 -0
  50. package/mission-control/drizzle.config.ts +1 -4
  51. package/mission-control/package-lock.json +1571 -83
  52. package/mission-control/package.json +6 -2
  53. package/mission-control/scripts/gen-chronology.js +3 -3
  54. package/mission-control/scripts/import-pipeline-v2.js +0 -16
  55. package/mission-control/scripts/import-pipeline.js +0 -15
  56. package/mission-control/src/app/api/cowork/clusters/[id]/members/route.ts +117 -0
  57. package/mission-control/src/app/api/cowork/clusters/[id]/route.ts +84 -0
  58. package/mission-control/src/app/api/cowork/clusters/route.ts +141 -0
  59. package/mission-control/src/app/api/cowork/dispatch/route.ts +128 -0
  60. package/mission-control/src/app/api/cowork/events/route.ts +65 -0
  61. package/mission-control/src/app/api/cowork/intervene/route.ts +259 -0
  62. package/mission-control/src/app/api/cowork/sessions/[id]/route.ts +37 -0
  63. package/mission-control/src/app/api/cowork/sessions/route.ts +64 -0
  64. package/mission-control/src/app/api/diagnostics/route.ts +97 -0
  65. package/mission-control/src/app/api/diagnostics/test-runner/route.ts +990 -0
  66. package/mission-control/src/app/api/mesh/events/route.ts +95 -19
  67. package/mission-control/src/app/api/mesh/identity/route.ts +11 -0
  68. package/mission-control/src/app/api/mesh/tasks/[id]/route.ts +92 -0
  69. package/mission-control/src/app/api/mesh/tasks/route.ts +91 -0
  70. package/mission-control/src/app/api/tasks/[id]/handoff/route.ts +1 -1
  71. package/mission-control/src/app/api/tasks/[id]/route.ts +90 -4
  72. package/mission-control/src/app/api/tasks/route.ts +21 -30
  73. package/mission-control/src/app/cowork/page.tsx +261 -0
  74. package/mission-control/src/app/diagnostics/page.tsx +385 -0
  75. package/mission-control/src/app/graph/page.tsx +26 -0
  76. package/mission-control/src/app/memory/page.tsx +1 -1
  77. package/mission-control/src/app/obsidian/page.tsx +36 -6
  78. package/mission-control/src/app/roadmap/page.tsx +24 -0
  79. package/mission-control/src/app/souls/page.tsx +2 -2
  80. package/mission-control/src/components/board/execution-config.tsx +431 -0
  81. package/mission-control/src/components/board/kanban-board.tsx +75 -9
  82. package/mission-control/src/components/board/kanban-column.tsx +135 -19
  83. package/mission-control/src/components/board/task-card.tsx +55 -2
  84. package/mission-control/src/components/board/unified-task-dialog.tsx +82 -4
  85. package/mission-control/src/components/cowork/cluster-card.tsx +176 -0
  86. package/mission-control/src/components/cowork/create-cluster-dialog.tsx +251 -0
  87. package/mission-control/src/components/cowork/dispatch-form.tsx +423 -0
  88. package/mission-control/src/components/cowork/role-picker.tsx +102 -0
  89. package/mission-control/src/components/cowork/session-card.tsx +284 -0
  90. package/mission-control/src/components/layout/sidebar.tsx +39 -2
  91. package/mission-control/src/lib/__tests__/daily-log.test.ts +82 -0
  92. package/mission-control/src/lib/__tests__/memory-md.test.ts +87 -0
  93. package/mission-control/src/lib/__tests__/mesh-kv-sync.test.ts +465 -0
  94. package/mission-control/src/lib/__tests__/mocks/mock-kv.ts +131 -0
  95. package/mission-control/src/lib/__tests__/status-kanban.test.ts +46 -0
  96. package/mission-control/src/lib/__tests__/task-markdown.test.ts +188 -0
  97. package/mission-control/src/lib/__tests__/wikilinks.test.ts +175 -0
  98. package/mission-control/src/lib/config.ts +58 -0
  99. package/mission-control/src/lib/db/index.ts +69 -0
  100. package/mission-control/src/lib/db/schema.ts +61 -3
  101. package/mission-control/src/lib/hooks.ts +309 -0
  102. package/mission-control/src/lib/memory/entities.ts +3 -2
  103. package/mission-control/src/lib/nats.ts +66 -1
  104. package/mission-control/src/lib/parsers/task-markdown.ts +52 -2
  105. package/mission-control/src/lib/parsers/transcript.ts +4 -4
  106. package/mission-control/src/lib/scheduler.ts +12 -11
  107. package/mission-control/src/lib/sync/mesh-kv.ts +279 -0
  108. package/mission-control/src/lib/sync/tasks.ts +23 -1
  109. package/mission-control/src/lib/task-id.ts +32 -0
  110. package/mission-control/src/lib/tts/index.ts +33 -9
  111. package/mission-control/tsconfig.json +2 -1
  112. package/mission-control/vitest.config.ts +14 -0
  113. package/package.json +15 -2
  114. package/services/service-manifest.json +1 -1
  115. package/skills/cc-godmode/references/agents.md +8 -8
  116. package/workspace-bin/memory-daemon.mjs +199 -5
  117. package/workspace-bin/session-search.mjs +204 -0
  118. package/workspace-bin/web-fetch.mjs +65 -0
package/lib/kanban-io.js CHANGED
@@ -53,8 +53,14 @@ function withMkdirLock(filePath, fn) {
53
53
  if (Date.now() - start > maxWait) {
54
54
  throw new Error(`kanban-io: lock timeout after ${maxWait}ms on ${filePath}`);
55
55
  }
56
- // Non-blocking 10ms pause (busy-wait would peg CPU in Node's single thread)
57
- require('child_process').spawnSync('sleep', ['0.01']);
56
+ // Sleep ~10ms Atomics.wait is precise but throws on main thread
57
+ // in some Node.js builds; fall back to busy-spin (rare contention path)
58
+ try {
59
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10);
60
+ } catch {
61
+ const end = Date.now() + 10;
62
+ while (Date.now() < end) { /* busy-wait fallback */ }
63
+ }
58
64
  }
59
65
  }
60
66
 
@@ -81,9 +87,7 @@ function parseTasks(content) {
81
87
  const liveIdx = content.indexOf('## Live Tasks');
82
88
  if (liveIdx === -1) return tasks;
83
89
 
84
- const afterLive = content.slice(liveIdx);
85
- const nextSectionIdx = afterLive.indexOf('\n## ', 1); // skip the current ## Live Tasks
86
- const liveSection = nextSectionIdx >= 0 ? afterLive.slice(0, nextSectionIdx) : afterLive;
90
+ const liveSection = content.slice(liveIdx);
87
91
  const lines = liveSection.split('\n');
88
92
 
89
93
  let current = null;
@@ -110,6 +114,13 @@ function parseTasks(content) {
110
114
  budget_minutes: current.budget_minutes || 30,
111
115
  scope: current.scope || [],
112
116
  updated_at: current.updated_at || '',
117
+ // Mesh routing
118
+ llm_provider: current.llm_provider || null,
119
+ llm_model: current.llm_model || null,
120
+ preferred_nodes: current.preferred_nodes || [],
121
+ exclude_nodes: current.exclude_nodes || [],
122
+ collaboration: current.collaboration || null,
123
+ collab_result: current.collab_result || null,
113
124
  });
114
125
  }
115
126
  }
@@ -119,7 +130,7 @@ function parseTasks(content) {
119
130
  const taskIdMatch = line.match(/^- task_id:\s*(.+)$/);
120
131
  if (taskIdMatch) {
121
132
  flush();
122
- current = { task_id: taskIdMatch[1].trim(), success_criteria: [], artifacts: [], scope: [] };
133
+ current = { task_id: taskIdMatch[1].trim(), success_criteria: [], artifacts: [], scope: [], preferred_nodes: [], exclude_nodes: [] };
123
134
  currentArrayKey = null;
124
135
  continue;
125
136
  }
@@ -176,6 +187,40 @@ function parseTasks(content) {
176
187
  current.scope = [];
177
188
  currentArrayKey = 'scope';
178
189
  break;
190
+ // Mesh routing fields
191
+ case 'llm_provider':
192
+ case 'provider':
193
+ current.llm_provider = value || null; currentArrayKey = null; break;
194
+ case 'llm_model':
195
+ case 'model':
196
+ current.llm_model = value || null; currentArrayKey = null; break;
197
+ case 'preferred_nodes':
198
+ current.preferred_nodes = [];
199
+ currentArrayKey = 'preferred_nodes';
200
+ break;
201
+ case 'exclude_nodes':
202
+ current.exclude_nodes = [];
203
+ currentArrayKey = 'exclude_nodes';
204
+ break;
205
+ case 'collaboration':
206
+ try { current.collaboration = value ? JSON.parse(value) : null; }
207
+ catch { current.collaboration = null; }
208
+ currentArrayKey = null;
209
+ break;
210
+ case 'collab_result':
211
+ try { current.collab_result = value ? JSON.parse(value) : null; }
212
+ catch { current.collab_result = null; }
213
+ currentArrayKey = null;
214
+ break;
215
+ // Circling Strategy display fields
216
+ case 'circling_phase': current.circling_phase = value || null; currentArrayKey = null; break;
217
+ case 'circling_subround': current.circling_subround = parseInt(value, 10) || 0; currentArrayKey = null; break;
218
+ case 'circling_step': current.circling_step = parseInt(value, 10) || 0; currentArrayKey = null; break;
219
+ case 'circling_nodes':
220
+ try { current.circling_nodes = value ? JSON.parse(value) : null; }
221
+ catch { current.circling_nodes = null; }
222
+ currentArrayKey = null;
223
+ break;
179
224
  default:
180
225
  currentArrayKey = null;
181
226
  break;
@@ -236,7 +281,11 @@ function _updateTaskInPlaceUnsafe(filePath, taskId, fieldUpdates = {}, arrayAppe
236
281
  const blockLines = lines.slice(blockStart, blockEnd);
237
282
 
238
283
  // Update scalar fields
239
- for (const [key, value] of Object.entries(fieldUpdates)) {
284
+ for (const [key, rawValue] of Object.entries(fieldUpdates)) {
285
+ // Serialize objects/arrays as JSON so the parser can read them back
286
+ const value = (rawValue !== null && typeof rawValue === 'object')
287
+ ? JSON.stringify(rawValue)
288
+ : rawValue;
240
289
  const fieldRegex = new RegExp(`^ ${key}:\\s*.*$`);
241
290
  let found = false;
242
291
  for (let i = 1; i < blockLines.length; i++) {
@@ -249,7 +298,7 @@ function _updateTaskInPlaceUnsafe(filePath, taskId, fieldUpdates = {}, arrayAppe
249
298
  if (!found) {
250
299
  // Insert before updated_at if it exists, otherwise at end of block
251
300
  const updatedAtIdx = blockLines.findIndex(l => l.match(/^ updated_at:/));
252
- const insertIdx = updatedAtIdx >= 0 ? updatedAtIdx : blockLines.length;
301
+ const insertIdx = updatedAtIdx > 0 ? updatedAtIdx : blockLines.length;
253
302
  blockLines.splice(insertIdx, 0, ` ${key}: ${value}`);
254
303
  }
255
304
  }
@@ -262,7 +311,7 @@ function _updateTaskInPlaceUnsafe(filePath, taskId, fieldUpdates = {}, arrayAppe
262
311
  if (headerIdx === -1) {
263
312
  // Insert the array before updated_at
264
313
  const updatedAtIdx = blockLines.findIndex(l => l.match(/^ updated_at:/));
265
- const insertIdx = updatedAtIdx >= 0 ? updatedAtIdx : blockLines.length;
314
+ const insertIdx = updatedAtIdx > 0 ? updatedAtIdx : blockLines.length;
266
315
  const newLines = [` ${key}:`];
267
316
  for (const item of items) {
268
317
  newLines.push(` - ${item}`);
@@ -290,7 +339,7 @@ function _updateTaskInPlaceUnsafe(filePath, taskId, fieldUpdates = {}, arrayAppe
290
339
  if (headerIdx === -1) {
291
340
  // Insert the array before updated_at
292
341
  const updatedAtIdx = blockLines.findIndex(l => l.match(/^ updated_at:/));
293
- const insertIdx = updatedAtIdx >= 0 ? updatedAtIdx : blockLines.length;
342
+ const insertIdx = updatedAtIdx > 0 ? updatedAtIdx : blockLines.length;
294
343
  const newLines = [` ${key}:`];
295
344
  for (const item of items) {
296
345
  newLines.push(` - ${item}`);
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Benchmark: index real workspace files and run search queries
4
+ */
5
+ import { pipeline } from '@huggingface/transformers';
6
+ import { statSync, rmSync } from 'node:fs';
7
+
8
+ import {
9
+ scanMarkdownFiles,
10
+ chunkMarkdown,
11
+ hashContent,
12
+ initDatabase,
13
+ EMBEDDING_DIM,
14
+ MAX_CHUNK_CHARS,
15
+ INCLUDE_DIRS,
16
+ EXCLUDE_PATTERNS,
17
+ MODEL_NAME,
18
+ } from './core.mjs';
19
+ import { readFileSync } from 'node:fs';
20
+ import { createHash } from 'node:crypto';
21
+
22
+ const WORKSPACE = process.env.KNOWLEDGE_ROOT || '/Users/moltymac/.openclaw/workspace';
23
+ const DB_PATH = '/tmp/mcp-knowledge-bench.db';
24
+
25
+ // Clean previous run
26
+ try { rmSync(DB_PATH); } catch {}
27
+
28
+ // Init DB
29
+ const db = initDatabase(DB_PATH);
30
+
31
+ const files = scanMarkdownFiles(WORKSPACE, INCLUDE_DIRS, EXCLUDE_PATTERNS);
32
+ console.log(`Scanning ${files.length} files...`);
33
+
34
+ console.log('Loading embedding model...');
35
+ const extractor = await pipeline('feature-extraction', MODEL_NAME, { dtype: 'fp32' });
36
+ console.log('Model loaded.\n');
37
+
38
+ const insertDoc = db.prepare('INSERT INTO documents (path, content_hash, last_indexed, chunk_count) VALUES (?, ?, ?, ?)');
39
+ const insertChunk = db.prepare('INSERT INTO chunks (doc_path, section, text, snippet) VALUES (?, ?, ?, ?)');
40
+
41
+ let totalChunks = 0;
42
+ let totalFiles = 0;
43
+ const start = Date.now();
44
+
45
+ for (const file of files) {
46
+ let content;
47
+ try { content = readFileSync(file.path, 'utf-8'); } catch { continue; }
48
+ if (!content.trim()) continue;
49
+
50
+ const hash = hashContent(content);
51
+ const chunks = chunkMarkdown(content);
52
+ const vectors = [];
53
+ for (const c of chunks) {
54
+ const r = await extractor(c.text, { pooling: 'mean', normalize: true, truncation: true, max_length: 256 });
55
+ vectors.push(new Float32Array(r.data));
56
+ }
57
+
58
+ db.transaction(() => {
59
+ insertDoc.run(file.rel, hash, Date.now(), chunks.length);
60
+ for (let i = 0; i < chunks.length; i++) {
61
+ const snippet = chunks[i].text.slice(0, 250).replace(/\n/g, ' ');
62
+ const info = insertChunk.run(file.rel, chunks[i].section, chunks[i].text, snippet);
63
+ const vecBuf = Buffer.from(vectors[i].buffer);
64
+ db.prepare(`INSERT INTO chunk_vectors VALUES (${info.lastInsertRowid}, ?)`).run(vecBuf);
65
+ }
66
+ })();
67
+ totalChunks += chunks.length;
68
+ totalFiles++;
69
+ if (totalFiles % 50 === 0) console.log(` ${totalFiles}/${files.length} files, ${totalChunks} chunks...`);
70
+ }
71
+
72
+ const elapsed = ((Date.now() - start) / 1000).toFixed(1);
73
+ console.log(`\nIndexed: ${totalFiles} files → ${totalChunks} chunks in ${elapsed}s`);
74
+
75
+ // Search queries
76
+ console.log('\n═══ Semantic Search Tests ═══');
77
+ async function search(query) {
78
+ const t0 = Date.now();
79
+ const r = await extractor(query, { pooling: 'mean', normalize: true, truncation: true, max_length: 256 });
80
+ const buf = Buffer.from(new Float32Array(r.data).buffer);
81
+ const results = db.prepare(`
82
+ SELECT cv.distance, c.doc_path, c.section, c.snippet
83
+ FROM chunk_vectors cv JOIN chunks c ON c.id = cv.rowid
84
+ WHERE embedding MATCH ? AND k = 5 ORDER BY distance
85
+ `).all(buf);
86
+ const ms = Date.now() - t0;
87
+ return { results, ms };
88
+ }
89
+
90
+ const queries = [
91
+ 'oracle threat model GPS spoofing',
92
+ 'faction lore verdant pact nature magic',
93
+ 'smart contract mana well architecture',
94
+ 'Daedalus identity soul companion',
95
+ 'what is the mana harvesting mechanism',
96
+ 'biome oracle location verification',
97
+ ];
98
+
99
+ for (const q of queries) {
100
+ const { results, ms } = await search(q);
101
+ console.log(`\nQuery: "${q}" (${ms}ms)`);
102
+ for (const r of results.slice(0, 3)) {
103
+ const score = (1 - r.distance * r.distance / 2).toFixed(3);
104
+ console.log(` [${score}] ${r.doc_path} → ${r.section.slice(0, 60)}`);
105
+ }
106
+ }
107
+
108
+ // Stats
109
+ const { size } = statSync(DB_PATH);
110
+ console.log(`\n═══ Stats ═══`);
111
+ console.log(`DB size: ${(size / 1024 / 1024).toFixed(1)} MB`);
112
+ console.log(`Files: ${totalFiles}`);
113
+ console.log(`Chunks: ${totalChunks}`);
114
+ console.log(`Index time: ${elapsed}s`);
115
+ console.log(`Avg per file: ${(parseFloat(elapsed) * 1000 / totalFiles).toFixed(0)}ms`);
116
+
117
+ db.close();
118
+ setTimeout(() => process.exit(0), 200);