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
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; // 5s max wait
33
+ const maxWait = 5000;
34
+ const pollInterval = 50;
34
35
  const start = Date.now();
35
36
 
36
- // Acquire: mkdir is atomic on POSIX
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
- const lockAge = Date.now() - fs.statSync(lockDir).mtimeMs;
46
- if (lockAge > 30000) {
47
- // Stale lock previous holder crashed
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
- // 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 */ }
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
- return fn();
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;
@@ -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);