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.
- package/README.md +646 -3
- package/bin/hyperagent.mjs +419 -0
- package/bin/mesh-agent.js +603 -81
- package/bin/mesh-bridge.js +340 -11
- package/bin/mesh-deploy-listener.js +119 -97
- package/bin/mesh-deploy.js +8 -0
- package/bin/mesh-task-daemon.js +1005 -40
- package/bin/mesh.js +423 -6
- 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 +300 -8
- package/lib/circling-parser.js +119 -0
- package/lib/hyperagent-store.mjs +652 -0
- package/lib/kanban-io.js +59 -10
- package/lib/mcp-knowledge/bench.mjs +118 -0
- package/lib/mcp-knowledge/core.mjs +528 -0
- package/lib/mcp-knowledge/package.json +25 -0
- package/lib/mcp-knowledge/server.mjs +245 -0
- package/lib/mcp-knowledge/test.mjs +802 -0
- package/lib/memory-budget.mjs +261 -0
- package/lib/mesh-collab.js +354 -4
- package/lib/mesh-harness.js +427 -0
- package/lib/mesh-plans.js +13 -5
- package/lib/mesh-registry.js +11 -2
- package/lib/mesh-tasks.js +67 -0
- package/lib/plan-templates.js +226 -0
- package/lib/pre-compression-flush.mjs +320 -0
- package/lib/role-loader.js +292 -0
- package/lib/rule-loader.js +358 -0
- package/lib/session-store.mjs +458 -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/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/tasks/[id]/handoff/route.ts +1 -1
- 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/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 +58 -0
- package/mission-control/src/lib/db/index.ts +69 -0
- 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/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/tsconfig.json +2 -1
- package/mission-control/vitest.config.ts +14 -0
- package/package.json +15 -2
- package/services/service-manifest.json +1 -1
- package/skills/cc-godmode/references/agents.md +8 -8
- package/workspace-bin/memory-daemon.mjs +199 -5
- package/workspace-bin/session-search.mjs +204 -0
- 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
|
-
//
|
|
57
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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);
|