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.
- package/bin/lane-watchdog.js +23 -2
- package/bin/mesh-agent.js +38 -16
- package/bin/mesh-bridge.js +3 -2
- package/bin/mesh-health-publisher.js +41 -1
- package/bin/mesh-task-daemon.js +5 -0
- package/bin/mesh.js +8 -19
- package/install.sh +3 -2
- package/lib/agent-activity.js +2 -2
- package/lib/exec-safety.js +105 -0
- package/lib/kanban-io.js +15 -31
- package/lib/llm-providers.js +16 -0
- package/lib/mcp-knowledge/core.mjs +7 -5
- package/lib/mcp-knowledge/server.mjs +8 -1
- package/lib/mesh-collab.js +268 -250
- package/lib/mesh-plans.js +66 -45
- package/lib/mesh-tasks.js +89 -73
- package/lib/nats-resolve.js +4 -4
- package/lib/pre-compression-flush.mjs +2 -0
- package/lib/session-store.mjs +6 -3
- package/mission-control/src/app/api/memory/search/route.ts +6 -3
- 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 +7 -1
- package/mission-control/src/app/api/workspace/read/route.ts +11 -0
- package/mission-control/src/lib/config.ts +9 -0
- package/mission-control/src/lib/db/index.ts +16 -1
- package/mission-control/src/lib/memory/extract.ts +2 -1
- package/mission-control/src/lib/memory/retrieval.ts +3 -2
- package/mission-control/src/middleware.ts +82 -0
- package/package.json +1 -1
- 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/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
|
-
|
|
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
|
@@ -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>
|
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
|
|
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 "
|
|
62
|
+
info "Legacy mesh agent service removed"
|
|
48
63
|
fi
|
|
49
64
|
elif [ "$OS" = "Darwin" ]; then
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|