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,358 @@
1
+ /**
2
+ * rule-loader.js — Load, match, and format path-scoped coding rules.
3
+ *
4
+ * Rules live in .openclaw/rules/ as markdown files with YAML frontmatter.
5
+ * This module is pure Node.js with zero external dependencies — importable
6
+ * by mesh-agent, install scripts, and any LLM adapter.
7
+ *
8
+ * Rule format:
9
+ * ---
10
+ * id: security
11
+ * tier: universal # universal | framework | project
12
+ * paths: ["**\/*"] # glob patterns for file matching
13
+ * detect: null # framework auto-activation signals
14
+ * priority: 100 # higher wins on conflict
15
+ * tags: ["security"]
16
+ * ---
17
+ * # Rule body in markdown
18
+ */
19
+
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+
23
+ // ── Tier Precedence (higher = wins) ────────────────
24
+ const TIER_WEIGHT = {
25
+ universal: 0,
26
+ framework: 10,
27
+ project: 20,
28
+ };
29
+
30
+ // ── Max chars for rule injection into prompts ──────
31
+ const MAX_RULES_CHARS = 4000;
32
+
33
+ // ── Frontmatter Parser (zero-dep) ──────────────────
34
+
35
+ /**
36
+ * Parse YAML frontmatter from a markdown file.
37
+ * Handles: strings, arrays (inline [...] and block - items), numbers, booleans, null.
38
+ */
39
+ function parseFrontmatter(content) {
40
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
41
+ if (!match) return { data: {}, body: content };
42
+
43
+ const yaml = match[1];
44
+ const body = content.slice(match[0].length).trim();
45
+ const data = {};
46
+
47
+ const lines = yaml.split('\n');
48
+ let currentKey = null;
49
+ let currentArray = null;
50
+
51
+ for (const line of lines) {
52
+ const trimmed = line.trim();
53
+ if (!trimmed || trimmed.startsWith('#')) continue;
54
+
55
+ // Block array item: " - value"
56
+ if (trimmed.startsWith('- ') && currentKey && currentArray) {
57
+ currentArray.push(parseValue(trimmed.slice(2).trim()));
58
+ continue;
59
+ }
60
+
61
+ // Key-value pair
62
+ const kvMatch = trimmed.match(/^(\w[\w-]*):\s*(.*)/);
63
+ if (!kvMatch) continue;
64
+
65
+ // Save previous array if pending
66
+ if (currentKey && currentArray) {
67
+ data[currentKey] = currentArray;
68
+ currentArray = null;
69
+ }
70
+
71
+ const [, key, rawVal] = kvMatch;
72
+ currentKey = key;
73
+
74
+ if (rawVal === '' || rawVal === undefined) {
75
+ // Could be start of block array
76
+ currentArray = [];
77
+ continue;
78
+ }
79
+
80
+ // Inline array: ["a", "b"]
81
+ if (rawVal.startsWith('[')) {
82
+ try {
83
+ data[key] = JSON.parse(rawVal);
84
+ } catch {
85
+ data[key] = rawVal.slice(1, -1).split(',').map(s => s.trim().replace(/^["']|["']$/g, ''));
86
+ }
87
+ currentArray = null;
88
+ continue;
89
+ }
90
+
91
+ data[key] = parseValue(rawVal);
92
+ currentArray = null;
93
+ }
94
+
95
+ // Flush last pending array
96
+ if (currentKey && currentArray) {
97
+ data[currentKey] = currentArray;
98
+ }
99
+
100
+ return { data, body };
101
+ }
102
+
103
+ function parseValue(val) {
104
+ if (val === 'null' || val === '~') return null;
105
+ if (val === 'true') return true;
106
+ if (val === 'false') return false;
107
+ if (/^-?\d+$/.test(val)) return parseInt(val, 10);
108
+ if (/^-?\d+\.\d+$/.test(val)) return parseFloat(val);
109
+ return val.replace(/^["']|["']$/g, '');
110
+ }
111
+
112
+ // ── Glob Matching (zero-dep) ───────────────────────
113
+
114
+ /**
115
+ * Match a file path against a glob pattern.
116
+ * Supports: *, **, ?, {a,b}
117
+ */
118
+ function globMatch(pattern, filepath) {
119
+ // Normalize separators
120
+ const p = pattern.replace(/\\/g, '/');
121
+ const f = filepath.replace(/\\/g, '/');
122
+
123
+ // Convert glob to regex
124
+ let regex = '';
125
+ let i = 0;
126
+ while (i < p.length) {
127
+ const ch = p[i];
128
+ if (ch === '*') {
129
+ if (p[i + 1] === '*') {
130
+ // ** matches any number of path segments
131
+ if (p[i + 2] === '/') {
132
+ regex += '(?:.+/)?';
133
+ i += 3;
134
+ } else {
135
+ regex += '.*';
136
+ i += 2;
137
+ }
138
+ } else {
139
+ // * matches anything except /
140
+ regex += '[^/]*';
141
+ i++;
142
+ }
143
+ } else if (ch === '?') {
144
+ regex += '[^/]';
145
+ i++;
146
+ } else if (ch === '{') {
147
+ const end = p.indexOf('}', i);
148
+ if (end === -1) { regex += '\\{'; i++; continue; }
149
+ const alts = p.slice(i + 1, end).split(',').map(s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
150
+ regex += `(?:${alts.join('|')})`;
151
+ i = end + 1;
152
+ } else {
153
+ regex += ch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
154
+ i++;
155
+ }
156
+ }
157
+
158
+ return new RegExp(`^${regex}$`).test(f);
159
+ }
160
+
161
+ // ── Core API ───────────────────────────────────────
162
+
163
+ /**
164
+ * Load all rule files from a directory.
165
+ * Returns array of { id, tier, paths, detect, priority, tags, body, file }.
166
+ */
167
+ function loadAllRules(rulesDir) {
168
+ if (!fs.existsSync(rulesDir)) return [];
169
+
170
+ const files = fs.readdirSync(rulesDir).filter(f => f.endsWith('.md'));
171
+ const rules = [];
172
+
173
+ for (const file of files) {
174
+ try {
175
+ const content = fs.readFileSync(path.join(rulesDir, file), 'utf-8');
176
+ const { data, body } = parseFrontmatter(content);
177
+
178
+ rules.push({
179
+ id: data.id || path.basename(file, '.md'),
180
+ tier: data.tier || 'universal',
181
+ paths: Array.isArray(data.paths) ? data.paths : ['**/*'],
182
+ detect: data.detect || null,
183
+ priority: parseInt(data.priority) || 50,
184
+ tags: Array.isArray(data.tags) ? data.tags : [],
185
+ body: body,
186
+ file: file,
187
+ });
188
+ } catch (err) {
189
+ // Skip malformed rule files silently
190
+ console.error(`[rule-loader] Skipped ${file}: ${err.message}`);
191
+ }
192
+ }
193
+
194
+ return rules;
195
+ }
196
+
197
+ /**
198
+ * Match rules against a set of scope paths.
199
+ * Returns rules sorted by: tier precedence (project > framework > universal),
200
+ * then priority (higher first).
201
+ */
202
+ function matchRules(rules, scopePaths) {
203
+ if (!scopePaths || scopePaths.length === 0) return [];
204
+
205
+ const matched = rules.filter(rule => {
206
+ return rule.paths.some(pattern =>
207
+ scopePaths.some(scopePath => globMatch(pattern, scopePath))
208
+ );
209
+ });
210
+
211
+ // Sort: tier weight desc, then priority desc
212
+ matched.sort((a, b) => {
213
+ const tierDiff = (TIER_WEIGHT[b.tier] || 0) - (TIER_WEIGHT[a.tier] || 0);
214
+ if (tierDiff !== 0) return tierDiff;
215
+ return (b.priority || 0) - (a.priority || 0);
216
+ });
217
+
218
+ return matched;
219
+ }
220
+
221
+ /**
222
+ * Format matched rules into a markdown block for prompt injection.
223
+ * Respects MAX_RULES_CHARS to avoid bloating prompts.
224
+ */
225
+ function formatRulesForPrompt(matchedRules) {
226
+ if (!matchedRules || matchedRules.length === 0) return '';
227
+
228
+ const parts = ['## Coding Standards', ''];
229
+ let totalChars = parts[0].length;
230
+
231
+ for (const rule of matchedRules) {
232
+ const header = `### ${rule.id} (${rule.tier})`;
233
+ const section = `${header}\n${rule.body}`;
234
+
235
+ if (totalChars + section.length > MAX_RULES_CHARS) {
236
+ parts.push(`\n_[${matchedRules.length - parts.length + 2} more rules truncated — scope narrower to see all]_`);
237
+ break;
238
+ }
239
+
240
+ parts.push(section);
241
+ parts.push('');
242
+ totalChars += section.length;
243
+ }
244
+
245
+ return parts.join('\n');
246
+ }
247
+
248
+ /**
249
+ * Detect frameworks present in a project directory.
250
+ * Returns array of framework identifiers (e.g., ['solidity', 'typescript']).
251
+ */
252
+ function detectFrameworks(projectDir) {
253
+ const detected = [];
254
+
255
+ // Check package.json dependencies
256
+ const pkgPath = path.join(projectDir, 'package.json');
257
+ if (fs.existsSync(pkgPath)) {
258
+ try {
259
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
260
+ const allDeps = {
261
+ ...(pkg.dependencies || {}),
262
+ ...(pkg.devDependencies || {}),
263
+ };
264
+
265
+ // Framework detection heuristics
266
+ if (allDeps.hardhat || allDeps['@nomicfoundation/hardhat-toolbox']) detected.push('solidity');
267
+ if (allDeps.react || allDeps['react-dom']) detected.push('react');
268
+ if (allDeps.next) detected.push('nextjs');
269
+ if (allDeps.vue) detected.push('vue');
270
+ if (allDeps.express || allDeps.fastify || allDeps.koa) detected.push('node-server');
271
+ } catch { /* skip malformed package.json */ }
272
+ }
273
+
274
+ // Check config files
275
+ const configChecks = [
276
+ ['tsconfig.json', 'typescript'],
277
+ ['foundry.toml', 'solidity'],
278
+ ['hardhat.config.js', 'solidity'],
279
+ ['hardhat.config.ts', 'solidity'],
280
+ ['Cargo.toml', 'rust'],
281
+ ['go.mod', 'golang'],
282
+ ['pyproject.toml', 'python'],
283
+ ['requirements.txt', 'python'],
284
+ ['Gemfile', 'ruby'],
285
+ ];
286
+
287
+ for (const [file, framework] of configChecks) {
288
+ if (fs.existsSync(path.join(projectDir, file)) && !detected.includes(framework)) {
289
+ detected.push(framework);
290
+ }
291
+ }
292
+
293
+ // Check directories
294
+ const dirChecks = [
295
+ ['ProjectSettings', 'unity'], // Unity project
296
+ ['Assets', 'unity'], // Unity assets
297
+ ['.godot', 'godot'], // Godot
298
+ ['ios', 'ios'], // iOS project
299
+ ['android', 'android'], // Android project
300
+ ];
301
+
302
+ for (const [dir, framework] of dirChecks) {
303
+ if (fs.existsSync(path.join(projectDir, dir)) && !detected.includes(framework)) {
304
+ detected.push(framework);
305
+ }
306
+ }
307
+
308
+ return detected;
309
+ }
310
+
311
+ // Map config file patterns to the framework IDs that detectFrameworks() returns
312
+ const DETECT_SIGNAL_TO_FRAMEWORK = {
313
+ 'hardhat.config.js': 'solidity',
314
+ 'hardhat.config.ts': 'solidity',
315
+ 'foundry.toml': 'solidity',
316
+ 'tsconfig.json': 'typescript',
317
+ 'Cargo.toml': 'rust',
318
+ 'go.mod': 'golang',
319
+ 'pyproject.toml': 'python',
320
+ 'requirements.txt': 'python',
321
+ 'Gemfile': 'ruby',
322
+ 'ProjectSettings/ProjectVersion.txt': 'unity',
323
+ };
324
+
325
+ /**
326
+ * Filter framework-tier rules to only those matching detected frameworks.
327
+ * Rules with detect: null are always included.
328
+ * Rules with detect: ["hardhat.config.js", "foundry.toml"] are included if
329
+ * any detect signal resolves to a detected framework ID.
330
+ */
331
+ function activateFrameworkRules(rules, detectedFrameworks) {
332
+ return rules.filter(rule => {
333
+ if (rule.tier !== 'framework') return true; // non-framework rules pass through
334
+ if (!rule.detect) return true; // no detect = always active
335
+
336
+ const signals = Array.isArray(rule.detect) ? rule.detect : [rule.detect];
337
+ return signals.some(signal => {
338
+ // Direct framework ID match (e.g., detect: ["solidity"])
339
+ if (detectedFrameworks.includes(signal)) return true;
340
+ // Config file → framework ID resolution (e.g., "hardhat.config.js" → "solidity")
341
+ const resolvedFw = DETECT_SIGNAL_TO_FRAMEWORK[signal];
342
+ if (resolvedFw && detectedFrameworks.includes(resolvedFw)) return true;
343
+ return false;
344
+ });
345
+ });
346
+ }
347
+
348
+ module.exports = {
349
+ loadAllRules,
350
+ matchRules,
351
+ formatRulesForPrompt,
352
+ detectFrameworks,
353
+ activateFrameworkRules,
354
+ globMatch,
355
+ parseFrontmatter,
356
+ TIER_WEIGHT,
357
+ MAX_RULES_CHARS,
358
+ };