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
package/lib/kanban-io.js
CHANGED
|
@@ -28,47 +28,31 @@ const ACTIVE_TASKS_PATH = path.join(
|
|
|
28
28
|
// Prevents lost updates when mesh-bridge and memory-daemon write concurrently.
|
|
29
29
|
// See architecture note above for why this is local-only.
|
|
30
30
|
|
|
31
|
-
function withMkdirLock(filePath, fn) {
|
|
31
|
+
async function withMkdirLock(filePath, fn) {
|
|
32
32
|
const lockDir = filePath + '.lk';
|
|
33
|
-
const maxWait = 5000;
|
|
33
|
+
const maxWait = 5000;
|
|
34
|
+
const pollInterval = 50;
|
|
34
35
|
const start = Date.now();
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
while (true) {
|
|
37
|
+
while (Date.now() - start < maxWait) {
|
|
38
38
|
try {
|
|
39
39
|
fs.mkdirSync(lockDir);
|
|
40
|
-
break; // got the lock
|
|
41
|
-
} catch (err) {
|
|
42
|
-
if (err.code !== 'EEXIST') throw err;
|
|
43
|
-
// Lock held by another process — check for stale lock (>30s)
|
|
44
40
|
try {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
fs.rmdirSync(lockDir);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
} catch { /* stat failed, lock was just released */ continue; }
|
|
52
|
-
|
|
53
|
-
if (Date.now() - start > maxWait) {
|
|
54
|
-
throw new Error(`kanban-io: lock timeout after ${maxWait}ms on ${filePath}`);
|
|
41
|
+
return await fn();
|
|
42
|
+
} finally {
|
|
43
|
+
try { fs.rmdirSync(lockDir); } catch {}
|
|
55
44
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
} catch {
|
|
61
|
-
const end = Date.now() + 10;
|
|
62
|
-
while (Date.now() < end) { /* busy-wait fallback */ }
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (err.code === 'EEXIST') {
|
|
47
|
+
await new Promise(r => setTimeout(r, pollInterval));
|
|
48
|
+
continue;
|
|
63
49
|
}
|
|
50
|
+
throw err;
|
|
64
51
|
}
|
|
65
52
|
}
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
|
|
69
|
-
} finally {
|
|
70
|
-
try { fs.rmdirSync(lockDir); } catch { /* already released */ }
|
|
71
|
-
}
|
|
53
|
+
// Timeout — force acquire (stale lock)
|
|
54
|
+
try { fs.rmdirSync(lockDir); } catch {}
|
|
55
|
+
return fn();
|
|
72
56
|
}
|
|
73
57
|
|
|
74
58
|
// ── Parser ──────────────────────────────────────────
|
|
@@ -212,6 +196,15 @@ function parseTasks(content) {
|
|
|
212
196
|
catch { current.collab_result = null; }
|
|
213
197
|
currentArrayKey = null;
|
|
214
198
|
break;
|
|
199
|
+
// Circling Strategy display fields
|
|
200
|
+
case 'circling_phase': current.circling_phase = value || null; currentArrayKey = null; break;
|
|
201
|
+
case 'circling_subround': current.circling_subround = parseInt(value, 10) || 0; currentArrayKey = null; break;
|
|
202
|
+
case 'circling_step': current.circling_step = parseInt(value, 10) || 0; currentArrayKey = null; break;
|
|
203
|
+
case 'circling_nodes':
|
|
204
|
+
try { current.circling_nodes = value ? JSON.parse(value) : null; }
|
|
205
|
+
catch { current.circling_nodes = null; }
|
|
206
|
+
currentArrayKey = null;
|
|
207
|
+
break;
|
|
215
208
|
default:
|
|
216
209
|
currentArrayKey = null;
|
|
217
210
|
break;
|
package/lib/llm-providers.js
CHANGED
|
@@ -21,6 +21,19 @@ const path = require('path');
|
|
|
21
21
|
const fs = require('fs');
|
|
22
22
|
const os = require('os');
|
|
23
23
|
|
|
24
|
+
// ── Shell Command Security ─────────────────────────
|
|
25
|
+
const SHELL_PROVIDER_ALLOWED_PREFIXES = [
|
|
26
|
+
'npm test', 'npm run', 'node ', 'python ', 'pytest', 'cargo test',
|
|
27
|
+
'go test', 'make', 'jest', 'vitest', 'mocha', 'bash ', 'sh ',
|
|
28
|
+
'cat ', 'echo ', 'ls ', 'grep ', 'find ', 'git '
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
function validateShellCommand(cmd) {
|
|
32
|
+
const trimmed = (cmd || '').trim();
|
|
33
|
+
if (!trimmed) return false;
|
|
34
|
+
return SHELL_PROVIDER_ALLOWED_PREFIXES.some(p => trimmed.startsWith(p));
|
|
35
|
+
}
|
|
36
|
+
|
|
24
37
|
// ── Generic Provider Factory ────────────────────────
|
|
25
38
|
// Most agentic coding CLIs follow a similar pattern:
|
|
26
39
|
// binary [prompt-flag] "prompt" [model-flag] model [cwd-flag] dir
|
|
@@ -167,6 +180,9 @@ const PROVIDERS = {
|
|
|
167
180
|
buildArgs(prompt, model, task) {
|
|
168
181
|
// Use task.description (the raw command) if available, fall back to prompt
|
|
169
182
|
const cmd = (task && task.description) ? task.description : prompt;
|
|
183
|
+
if (!validateShellCommand(cmd)) {
|
|
184
|
+
throw new Error(`Shell provider: command blocked by security filter: ${cmd.slice(0, 80)}`);
|
|
185
|
+
}
|
|
170
186
|
return ['-c', cmd];
|
|
171
187
|
},
|
|
172
188
|
cleanEnv(env) {
|
|
@@ -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);
|