openclaw-node-harness 2.1.0 → 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 (36) hide show
  1. package/bin/lane-watchdog.js +23 -2
  2. package/bin/mesh-agent.js +38 -16
  3. package/bin/mesh-bridge.js +3 -2
  4. package/bin/mesh-health-publisher.js +41 -1
  5. package/bin/mesh-task-daemon.js +5 -0
  6. package/bin/mesh.js +8 -19
  7. package/install.sh +3 -2
  8. package/lib/agent-activity.js +2 -2
  9. package/lib/exec-safety.js +105 -0
  10. package/lib/kanban-io.js +15 -31
  11. package/lib/llm-providers.js +16 -0
  12. package/lib/mcp-knowledge/core.mjs +7 -5
  13. package/lib/mcp-knowledge/server.mjs +8 -1
  14. package/lib/mesh-collab.js +268 -250
  15. package/lib/mesh-plans.js +66 -45
  16. package/lib/mesh-tasks.js +89 -73
  17. package/lib/nats-resolve.js +4 -4
  18. package/lib/pre-compression-flush.mjs +2 -0
  19. package/lib/session-store.mjs +6 -3
  20. package/mission-control/src/app/api/memory/search/route.ts +6 -3
  21. package/mission-control/src/app/api/souls/[id]/evolution/route.ts +21 -5
  22. package/mission-control/src/app/api/souls/[id]/prompt/route.ts +7 -1
  23. package/mission-control/src/app/api/souls/[id]/propagate/route.ts +14 -2
  24. package/mission-control/src/app/api/tasks/[id]/handoff/route.ts +7 -1
  25. package/mission-control/src/app/api/workspace/read/route.ts +11 -0
  26. package/mission-control/src/lib/config.ts +9 -0
  27. package/mission-control/src/lib/db/index.ts +16 -1
  28. package/mission-control/src/lib/memory/extract.ts +2 -1
  29. package/mission-control/src/lib/memory/retrieval.ts +3 -2
  30. package/mission-control/src/middleware.ts +82 -0
  31. package/package.json +1 -1
  32. package/services/launchd/ai.openclaw.log-rotate.plist +11 -0
  33. package/services/launchd/ai.openclaw.mesh-deploy-listener.plist +4 -0
  34. package/services/launchd/ai.openclaw.mesh-health-publisher.plist +4 -0
  35. package/services/launchd/ai.openclaw.mission-control.plist +1 -1
  36. package/uninstall.sh +37 -9
@@ -2,7 +2,7 @@ import Database from "better-sqlite3";
2
2
  import { drizzle } from "drizzle-orm/better-sqlite3";
3
3
  import * as schema from "./schema";
4
4
  import { DB_PATH } from "../config";
5
- import fs from "fs";
5
+ import fs, { chmodSync, existsSync } from "fs";
6
6
  import path from "path";
7
7
 
8
8
  let _db: ReturnType<typeof drizzle<typeof schema>> | null = null;
@@ -488,6 +488,21 @@ export function getDb() {
488
488
 
489
489
  runMigrations(_sqlite);
490
490
 
491
+ // Lock down DB file permissions — owner read/write only
492
+ try {
493
+ if (existsSync(DB_PATH)) {
494
+ chmodSync(DB_PATH, 0o600);
495
+ }
496
+ const walPath = DB_PATH + "-wal";
497
+ if (existsSync(walPath)) {
498
+ chmodSync(walPath, 0o600);
499
+ }
500
+ const journalPath = DB_PATH + "-journal";
501
+ if (existsSync(journalPath)) {
502
+ chmodSync(journalPath, 0o600);
503
+ }
504
+ } catch {}
505
+
491
506
  _db = drizzle(_sqlite, { schema });
492
507
  return _db;
493
508
  }
@@ -263,7 +263,8 @@ export function getItemsWithSource(limit = 50, offset = 0) {
263
263
  */
264
264
  export function searchItems(query: string, category?: string, limit = 20) {
265
265
  const raw = getRawDb();
266
- const safeQuery = query.replace(/"/g, '""');
266
+ const safeQuery = query.replace(/"/g, '""').replace(/[*(){}^]/g, '').trim();
267
+ if (!safeQuery) return [];
267
268
 
268
269
  if (category) {
269
270
  return raw
@@ -35,8 +35,9 @@ function decay(relevance: number, daysOld: number, rate = 0.01): number {
35
35
  * Multi-word → ("word1 word2") OR ("word1"* OR "word2"*)
36
36
  */
37
37
  function buildFtsQuery(query: string): string {
38
- const safe = query.replace(/"/g, '""');
39
- const terms = safe.trim().split(/\s+/).filter((t) => t.length >= 2);
38
+ const safe = query.replace(/"/g, '""').replace(/[*(){}^]/g, '').trim();
39
+ if (!safe) return '""';
40
+ const terms = safe.split(/\s+/).filter((t) => t.length >= 2);
40
41
  if (terms.length <= 1) return `"${safe}"*`;
41
42
  const phrase = `"${safe}"`;
42
43
  const individual = terms.map((t) => `"${t}"*`).join(" OR ");
@@ -0,0 +1,82 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+
3
+ /**
4
+ * API authentication middleware.
5
+ *
6
+ * Protects all /api/* routes with a Bearer token check.
7
+ * Token is read from MC_AUTH_TOKEN env var. If unset, auth is disabled
8
+ * (localhost-only deployments). When set, every API request must include:
9
+ * Authorization: Bearer <token>
10
+ *
11
+ * Page routes (non-API) are not gated — the dashboard is a local UI.
12
+ */
13
+
14
+ const AUTH_TOKEN = process.env.MC_AUTH_TOKEN || "";
15
+
16
+ export function middleware(request: NextRequest) {
17
+ // Only gate API routes
18
+ if (!request.nextUrl.pathname.startsWith("/api/")) {
19
+ return NextResponse.next();
20
+ }
21
+
22
+ // Body size limit (1MB) for mutation requests
23
+ const contentLength = parseInt(request.headers.get("content-length") || "0", 10);
24
+ const MAX_BODY_SIZE = 1024 * 1024; // 1MB
25
+ if (contentLength > MAX_BODY_SIZE) {
26
+ return NextResponse.json(
27
+ { error: `Request body too large (${contentLength} bytes, max ${MAX_BODY_SIZE})` },
28
+ { status: 413 }
29
+ );
30
+ }
31
+
32
+ // SSE endpoints use EventSource which can't set headers — allow if
33
+ // the token is passed as a query param instead.
34
+ const tokenFromQuery = request.nextUrl.searchParams.get("token");
35
+
36
+ // If no token is configured, auth is disabled (backwards-compatible)
37
+ if (!AUTH_TOKEN) {
38
+ return NextResponse.next();
39
+ }
40
+
41
+ const authHeader = request.headers.get("authorization") || "";
42
+ const bearer = authHeader.startsWith("Bearer ")
43
+ ? authHeader.slice(7).trim()
44
+ : "";
45
+
46
+ const providedToken = bearer || tokenFromQuery || "";
47
+
48
+ if (!providedToken) {
49
+ return NextResponse.json(
50
+ { error: "Missing Authorization header" },
51
+ { status: 401 }
52
+ );
53
+ }
54
+
55
+ // Constant-time comparison to prevent timing attacks
56
+ if (!timingSafeEqual(providedToken, AUTH_TOKEN)) {
57
+ return NextResponse.json({ error: "Invalid token" }, { status: 403 });
58
+ }
59
+
60
+ return NextResponse.next();
61
+ }
62
+
63
+ /** Constant-time string comparison (Edge Runtime compatible). */
64
+ function timingSafeEqual(a: string, b: string): boolean {
65
+ if (a.length !== b.length) {
66
+ // Still do a full comparison to avoid length-based timing leak
67
+ let result = a.length ^ b.length;
68
+ for (let i = 0; i < a.length; i++) {
69
+ result |= a.charCodeAt(i) ^ (b.charCodeAt(i % b.length) || 0);
70
+ }
71
+ return result === 0;
72
+ }
73
+ let result = 0;
74
+ for (let i = 0; i < a.length; i++) {
75
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
76
+ }
77
+ return result === 0;
78
+ }
79
+
80
+ export const config = {
81
+ matcher: "/api/:path*",
82
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-node-harness",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "One-command installer for the OpenClaw node layer — identity, skills, souls, daemon, and Mission Control.",
5
5
  "bin": {
6
6
  "openclaw-node": "./cli.js"
@@ -20,6 +20,17 @@
20
20
  <key>Minute</key>
21
21
  <integer>0</integer>
22
22
  </dict>
23
+ <key>RunAtLoad</key>
24
+ <false/>
25
+ <key>KeepAlive</key>
26
+ <false/>
27
+ <key>EnvironmentVariables</key>
28
+ <dict>
29
+ <key>HOME</key>
30
+ <string>${HOME}</string>
31
+ <key>PATH</key>
32
+ <string>${HOME}/.bun/bin:${HOME}/.local/bin:${HOME}/.npm-global/bin:${HOME}/bin:${HOME}/.volta/bin:${HOME}/.asdf/shims:${HOME}/Library/Application Support/fnm/aliases/default/bin:${HOME}/.fnm/aliases/default/bin:${HOME}/Library/pnpm:${HOME}/.local/share/pnpm:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
33
+ </dict>
23
34
  <key>StandardOutPath</key>
24
35
  <string>${HOME}/.openclaw/logs/log-rotate.log</string>
25
36
  <key>StandardErrorPath</key>
@@ -12,6 +12,10 @@
12
12
  </array>
13
13
  <key>EnvironmentVariables</key>
14
14
  <dict>
15
+ <key>HOME</key>
16
+ <string>${HOME}</string>
17
+ <key>PATH</key>
18
+ <string>${HOME}/.bun/bin:${HOME}/.local/bin:${HOME}/.npm-global/bin:${HOME}/bin:${HOME}/.volta/bin:${HOME}/.asdf/shims:${HOME}/Library/Application Support/fnm/aliases/default/bin:${HOME}/.fnm/aliases/default/bin:${HOME}/Library/pnpm:${HOME}/.local/share/pnpm:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
15
19
  <key>OPENCLAW_NODE_ID</key>
16
20
  <string>${OPENCLAW_NODE_ID}</string>
17
21
  <key>OPENCLAW_NATS</key>
@@ -12,6 +12,10 @@
12
12
  </array>
13
13
  <key>EnvironmentVariables</key>
14
14
  <dict>
15
+ <key>HOME</key>
16
+ <string>${HOME}</string>
17
+ <key>PATH</key>
18
+ <string>${HOME}/.bun/bin:${HOME}/.local/bin:${HOME}/.npm-global/bin:${HOME}/bin:${HOME}/.volta/bin:${HOME}/.asdf/shims:${HOME}/Library/Application Support/fnm/aliases/default/bin:${HOME}/.fnm/aliases/default/bin:${HOME}/Library/pnpm:${HOME}/.local/share/pnpm:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
15
19
  <key>OPENCLAW_NODE_ID</key>
16
20
  <string>${OPENCLAW_NODE_ID}</string>
17
21
  <key>OPENCLAW_NATS</key>
@@ -20,7 +20,7 @@
20
20
  <key>PATH</key>
21
21
  <string>/usr/local/bin:/usr/bin:/bin</string>
22
22
  <key>NODE_ENV</key>
23
- <string>development</string>
23
+ <string>production</string>
24
24
  </dict>
25
25
 
26
26
  <key>RunAtLoad</key>
package/uninstall.sh CHANGED
@@ -35,24 +35,52 @@ if [ -f "$WORKSPACE/bin/install-daemon" ]; then
35
35
  bash "$WORKSPACE/bin/install-daemon" --uninstall 2>/dev/null || true
36
36
  fi
37
37
 
38
- # Stop and remove mesh agent service (if installed)
38
+ # Stop and remove services
39
39
  OS="$(uname -s)"
40
40
  if [ "$OS" = "Linux" ]; then
41
+ # --- Current services: openclaw-*.service under user systemd ---
42
+ SYSTEMD_USER_DIR="$HOME/.config/systemd/user"
43
+ if [ -d "$SYSTEMD_USER_DIR" ]; then
44
+ for unit in "$SYSTEMD_USER_DIR"/openclaw-*.service "$SYSTEMD_USER_DIR"/openclaw-*.timer; do
45
+ [ -f "$unit" ] || continue
46
+ UNIT_NAME="$(basename "$unit")"
47
+ info "Stopping $UNIT_NAME..."
48
+ systemctl --user stop "$UNIT_NAME" 2>/dev/null || true
49
+ systemctl --user disable "$UNIT_NAME" 2>/dev/null || true
50
+ rm -f "$unit"
51
+ info "Removed $UNIT_NAME"
52
+ done
53
+ systemctl --user daemon-reload 2>/dev/null || true
54
+ fi
55
+ # --- Legacy fallback: old system-level openclaw-agent ---
41
56
  if systemctl is-active --quiet openclaw-agent 2>/dev/null; then
42
- info "Stopping mesh agent..."
57
+ info "Stopping legacy mesh agent (openclaw-agent)..."
43
58
  sudo systemctl stop openclaw-agent 2>/dev/null || true
44
59
  sudo systemctl disable openclaw-agent 2>/dev/null || true
45
60
  sudo rm -f /etc/systemd/system/openclaw-agent.service
46
61
  sudo systemctl daemon-reload 2>/dev/null || true
47
- info "Mesh agent service removed"
62
+ info "Legacy mesh agent service removed"
48
63
  fi
49
64
  elif [ "$OS" = "Darwin" ]; then
50
- MESH_PLIST="/Library/LaunchDaemons/com.openclaw.agent.plist"
51
- if [ -f "$MESH_PLIST" ]; then
52
- info "Stopping mesh agent..."
53
- sudo launchctl unload "$MESH_PLIST" 2>/dev/null || true
54
- sudo rm -f "$MESH_PLIST"
55
- info "Mesh agent LaunchDaemon removed"
65
+ # --- Current services: ai.openclaw.*.plist under ~/Library/LaunchAgents ---
66
+ LAUNCHD_AGENTS_DIR="$HOME/Library/LaunchAgents"
67
+ if [ -d "$LAUNCHD_AGENTS_DIR" ]; then
68
+ for plist in "$LAUNCHD_AGENTS_DIR"/ai.openclaw.*.plist; do
69
+ [ -f "$plist" ] || continue
70
+ PLIST_NAME="$(basename "$plist")"
71
+ info "Unloading $PLIST_NAME..."
72
+ launchctl unload "$plist" 2>/dev/null || true
73
+ rm -f "$plist"
74
+ info "Removed $PLIST_NAME"
75
+ done
76
+ fi
77
+ # --- Legacy fallback: old system-level com.openclaw.agent ---
78
+ LEGACY_PLIST="/Library/LaunchDaemons/com.openclaw.agent.plist"
79
+ if [ -f "$LEGACY_PLIST" ]; then
80
+ info "Stopping legacy mesh agent (com.openclaw.agent)..."
81
+ sudo launchctl unload "$LEGACY_PLIST" 2>/dev/null || true
82
+ sudo rm -f "$LEGACY_PLIST"
83
+ info "Legacy mesh agent LaunchDaemon removed"
56
84
  fi
57
85
  fi
58
86
  # Remove mesh symlinks