@silicaclaw/cli 2026.3.20-1 → 2026.3.20-10
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/CHANGELOG.md +54 -0
- package/INSTALL.md +13 -7
- package/README.md +60 -12
- package/VERSION +1 -1
- package/apps/local-console/dist/apps/local-console/src/server.d.ts +129 -2
- package/apps/local-console/dist/apps/local-console/src/server.js +887 -91
- package/apps/local-console/dist/packages/core/src/index.d.ts +2 -0
- package/apps/local-console/dist/packages/core/src/index.js +2 -0
- package/apps/local-console/dist/packages/core/src/privateCrypto.d.ts +17 -0
- package/apps/local-console/dist/packages/core/src/privateCrypto.js +40 -0
- package/apps/local-console/dist/packages/core/src/privateMessage.d.ts +23 -0
- package/apps/local-console/dist/packages/core/src/privateMessage.js +74 -0
- package/apps/local-console/dist/packages/core/src/profile.js +2 -0
- package/apps/local-console/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
- package/apps/local-console/dist/packages/core/src/publicProfileSummary.js +3 -0
- package/apps/local-console/dist/packages/core/src/types.d.ts +40 -0
- package/apps/local-console/dist/packages/network/src/relayPreview.d.ts +12 -0
- package/apps/local-console/dist/packages/network/src/relayPreview.js +108 -8
- package/apps/local-console/dist/packages/network/src/types.d.ts +4 -0
- package/apps/local-console/dist/packages/storage/src/repos.d.ts +13 -1
- package/apps/local-console/dist/packages/storage/src/repos.js +19 -1
- package/apps/local-console/public/app/app.js +465 -11
- package/apps/local-console/public/app/events.js +21 -0
- package/apps/local-console/public/app/network.js +144 -32
- package/apps/local-console/public/app/overview.js +60 -52
- package/apps/local-console/public/app/social.js +316 -93
- package/apps/local-console/public/app/styles.css +127 -1
- package/apps/local-console/public/app/template.js +121 -35
- package/apps/local-console/public/app/translations.js +430 -316
- package/apps/local-console/src/server.ts +1024 -89
- package/apps/public-explorer/public/app/template.js +2 -2
- package/apps/public-explorer/public/app/translations.js +36 -36
- package/docs/NEW_USER_OPERATIONS.md +5 -5
- package/docs/OPENCLAW_BRIDGE.md +7 -7
- package/docs/OPENCLAW_BRIDGE_ZH.md +6 -6
- package/node_modules/@silicaclaw/core/dist/packages/core/src/index.d.ts +2 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/index.js +2 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/privateCrypto.d.ts +17 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/privateCrypto.js +40 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/privateMessage.d.ts +23 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/privateMessage.js +74 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/profile.js +2 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/publicProfileSummary.js +3 -0
- package/node_modules/@silicaclaw/core/dist/packages/core/src/types.d.ts +40 -0
- package/node_modules/@silicaclaw/core/src/index.ts +2 -0
- package/node_modules/@silicaclaw/core/src/privateCrypto.ts +57 -0
- package/node_modules/@silicaclaw/core/src/privateMessage.ts +101 -0
- package/node_modules/@silicaclaw/core/src/profile.ts +2 -0
- package/node_modules/@silicaclaw/core/src/publicProfileSummary.ts +7 -0
- package/node_modules/@silicaclaw/core/src/types.ts +44 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.d.ts +12 -0
- package/node_modules/@silicaclaw/network/dist/packages/network/src/relayPreview.js +108 -8
- package/node_modules/@silicaclaw/network/dist/packages/network/src/types.d.ts +4 -0
- package/node_modules/@silicaclaw/network/src/relayPreview.ts +120 -10
- package/node_modules/@silicaclaw/network/src/types.ts +2 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.d.ts +2 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/index.js +2 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateCrypto.d.ts +17 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateCrypto.js +40 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateMessage.d.ts +23 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/privateMessage.js +74 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/profile.js +2 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/publicProfileSummary.js +3 -0
- package/node_modules/@silicaclaw/storage/dist/packages/core/src/types.d.ts +40 -0
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.d.ts +13 -1
- package/node_modules/@silicaclaw/storage/dist/packages/storage/src/repos.js +19 -1
- package/node_modules/@silicaclaw/storage/src/repos.ts +31 -1
- package/openclaw-skills/silicaclaw-bridge-setup/SKILL.md +18 -0
- package/openclaw-skills/silicaclaw-bridge-setup/VERSION +1 -1
- package/openclaw-skills/silicaclaw-bridge-setup/manifest.json +2 -2
- package/openclaw-skills/silicaclaw-broadcast/SKILL.md +18 -0
- package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
- package/openclaw-skills/silicaclaw-broadcast/manifest.json +2 -2
- package/openclaw-skills/silicaclaw-network-config/SKILL.md +158 -0
- package/openclaw-skills/silicaclaw-network-config/VERSION +1 -0
- package/openclaw-skills/silicaclaw-network-config/agents/openai.yaml +6 -0
- package/openclaw-skills/silicaclaw-network-config/manifest.json +27 -0
- package/openclaw-skills/silicaclaw-network-config/references/network-modes.md +22 -0
- package/openclaw-skills/silicaclaw-network-config/references/owner-dialogue-cheatsheet-zh.md +47 -0
- package/openclaw-skills/silicaclaw-network-config/references/public-discovery.md +22 -0
- package/openclaw-skills/silicaclaw-owner-push/SKILL.md +18 -0
- package/openclaw-skills/silicaclaw-owner-push/VERSION +1 -1
- package/openclaw-skills/silicaclaw-owner-push/manifest.json +2 -2
- package/openclaw-skills/silicaclaw-owner-push/references/runtime-setup.md +3 -0
- package/openclaw-skills/silicaclaw-owner-push/scripts/owner-push-forwarder.mjs +151 -9
- package/package.json +1 -1
- package/packages/core/dist/packages/core/src/index.d.ts +2 -0
- package/packages/core/dist/packages/core/src/index.js +2 -0
- package/packages/core/dist/packages/core/src/privateCrypto.d.ts +17 -0
- package/packages/core/dist/packages/core/src/privateCrypto.js +40 -0
- package/packages/core/dist/packages/core/src/privateMessage.d.ts +23 -0
- package/packages/core/dist/packages/core/src/privateMessage.js +74 -0
- package/packages/core/dist/packages/core/src/profile.js +2 -0
- package/packages/core/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
- package/packages/core/dist/packages/core/src/publicProfileSummary.js +3 -0
- package/packages/core/dist/packages/core/src/types.d.ts +40 -0
- package/packages/core/src/index.ts +2 -0
- package/packages/core/src/privateCrypto.ts +57 -0
- package/packages/core/src/privateMessage.ts +101 -0
- package/packages/core/src/profile.ts +2 -0
- package/packages/core/src/publicProfileSummary.ts +7 -0
- package/packages/core/src/types.ts +44 -0
- package/packages/network/dist/packages/network/src/relayPreview.d.ts +12 -0
- package/packages/network/dist/packages/network/src/relayPreview.js +108 -8
- package/packages/network/dist/packages/network/src/types.d.ts +4 -0
- package/packages/network/src/relayPreview.ts +120 -10
- package/packages/network/src/types.ts +2 -0
- package/packages/storage/dist/packages/core/src/index.d.ts +2 -0
- package/packages/storage/dist/packages/core/src/index.js +2 -0
- package/packages/storage/dist/packages/core/src/privateCrypto.d.ts +17 -0
- package/packages/storage/dist/packages/core/src/privateCrypto.js +40 -0
- package/packages/storage/dist/packages/core/src/privateMessage.d.ts +23 -0
- package/packages/storage/dist/packages/core/src/privateMessage.js +74 -0
- package/packages/storage/dist/packages/core/src/profile.js +2 -0
- package/packages/storage/dist/packages/core/src/publicProfileSummary.d.ts +4 -0
- package/packages/storage/dist/packages/core/src/publicProfileSummary.js +3 -0
- package/packages/storage/dist/packages/core/src/types.d.ts +40 -0
- package/packages/storage/dist/packages/storage/src/repos.d.ts +13 -1
- package/packages/storage/dist/packages/storage/src/repos.js +19 -1
- package/packages/storage/src/repos.ts +31 -1
- package/scripts/silicaclaw-cli.mjs +59 -6
- package/scripts/silicaclaw-gateway.mjs +108 -0
- package/scripts/validate-openclaw-skill.mjs +19 -0
|
@@ -36,7 +36,10 @@ const DEFAULT_GLOBAL_ROOM = silicaclaw_defaults_json_1.default.network.global_pr
|
|
|
36
36
|
const DEFAULT_BRIDGE_API_BASE = silicaclaw_defaults_json_1.default.bridge.api_base;
|
|
37
37
|
const OPENCLAW_GATEWAY_PORT = silicaclaw_defaults_json_1.default.ports.openclaw_gateway;
|
|
38
38
|
const OPENCLAW_GATEWAY_URL = `http://${OPENCLAW_GATEWAY_HOST}:${OPENCLAW_GATEWAY_PORT}/`;
|
|
39
|
+
const OPENCLAW_RUNTIME_CACHE_MS = 15_000;
|
|
40
|
+
const OPENCLAW_BRIDGE_STATUS_CACHE_MS = 5_000;
|
|
39
41
|
const NETWORK_PEER_REMOVE_AFTER_MS = Number(process.env.NETWORK_PEER_REMOVE_AFTER_MS || 180_000);
|
|
42
|
+
const DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT = Number(process.env.DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT || 1000);
|
|
40
43
|
const NETWORK_UDP_BIND_ADDRESS = process.env.NETWORK_UDP_BIND_ADDRESS || "0.0.0.0";
|
|
41
44
|
const NETWORK_UDP_BROADCAST_ADDRESS = process.env.NETWORK_UDP_BROADCAST_ADDRESS || "255.255.255.255";
|
|
42
45
|
const NETWORK_PEER_ID = process.env.NETWORK_PEER_ID;
|
|
@@ -49,6 +52,12 @@ const WEBRTC_BOOTSTRAP_HINTS = process.env.WEBRTC_BOOTSTRAP_HINTS || "";
|
|
|
49
52
|
const PROFILE_VERSION = "v0.9";
|
|
50
53
|
const SOCIAL_MESSAGE_TOPIC = "social.message";
|
|
51
54
|
const SOCIAL_MESSAGE_OBSERVATION_TOPIC = "social.message.observation";
|
|
55
|
+
const PRIVATE_MESSAGE_TOPIC = "private.message";
|
|
56
|
+
const PRIVATE_MESSAGE_RECEIPT_TOPIC = "private.message.receipt";
|
|
57
|
+
const PRIVATE_MESSAGE_HISTORY_LIMIT = Number(process.env.PRIVATE_MESSAGE_HISTORY_LIMIT || 1000);
|
|
58
|
+
const PRIVATE_MESSAGE_RECEIPT_HISTORY_LIMIT = Number(process.env.PRIVATE_MESSAGE_RECEIPT_HISTORY_LIMIT || 2000);
|
|
59
|
+
const PRIVATE_MESSAGE_QUERY_LIMIT = Number(process.env.PRIVATE_MESSAGE_QUERY_LIMIT || 100);
|
|
60
|
+
const PRIVATE_MESSAGE_PERSIST_DEBOUNCE_MS = Number(process.env.PRIVATE_MESSAGE_PERSIST_DEBOUNCE_MS || 750);
|
|
52
61
|
const DEFAULT_SOCIAL_MESSAGE_CHANNEL = "global";
|
|
53
62
|
const SOCIAL_MESSAGE_MAX_BODY_CHARS = Number(process.env.SOCIAL_MESSAGE_MAX_BODY_CHARS || 500);
|
|
54
63
|
const SOCIAL_MESSAGE_HISTORY_LIMIT = Number(process.env.SOCIAL_MESSAGE_HISTORY_LIMIT || 100);
|
|
@@ -62,6 +71,8 @@ const SOCIAL_MESSAGE_MAX_AGE_MS = Number(process.env.SOCIAL_MESSAGE_MAX_AGE_MS |
|
|
|
62
71
|
const SOCIAL_MESSAGE_OBSERVATION_HISTORY_LIMIT = Number(process.env.SOCIAL_MESSAGE_OBSERVATION_HISTORY_LIMIT || 500);
|
|
63
72
|
const SOCIAL_MESSAGE_REPLAY_WINDOW_MS = Number(process.env.SOCIAL_MESSAGE_REPLAY_WINDOW_MS || 10 * 60_000);
|
|
64
73
|
const SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST = Number(process.env.SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST || 3);
|
|
74
|
+
const SOCIAL_MESSAGE_REPLAY_REFRESH_INTERVAL_MS = Number(process.env.SOCIAL_MESSAGE_REPLAY_REFRESH_INTERVAL_MS || 120_000);
|
|
75
|
+
const PROFILE_RELAY_REFRESH_INTERVAL_MS = Number(process.env.PROFILE_RELAY_REFRESH_INTERVAL_MS || 120_000);
|
|
65
76
|
const SOCIAL_MESSAGE_BLOCKED_AGENT_IDS = new Set(dedupeStrings(parseListEnv(process.env.SOCIAL_MESSAGE_BLOCKED_AGENT_IDS || "")));
|
|
66
77
|
const SOCIAL_MESSAGE_BLOCKED_TERMS = dedupeStrings(parseListEnv(process.env.SOCIAL_MESSAGE_BLOCKED_TERMS || ""))
|
|
67
78
|
.map((term) => term.trim().toLowerCase())
|
|
@@ -101,6 +112,9 @@ function normalizeVersionText(value) {
|
|
|
101
112
|
const text = String(value || "").trim();
|
|
102
113
|
return text.startsWith("v") ? text.slice(1) : text;
|
|
103
114
|
}
|
|
115
|
+
function formatBytesToMiB(value) {
|
|
116
|
+
return Math.round((value / (1024 * 1024)) * 10) / 10;
|
|
117
|
+
}
|
|
104
118
|
function tokenizeVersion(value) {
|
|
105
119
|
return normalizeVersionText(value)
|
|
106
120
|
.split(/[^0-9A-Za-z]+/)
|
|
@@ -133,6 +147,12 @@ function compareVersionTokens(left, right) {
|
|
|
133
147
|
}
|
|
134
148
|
return 0;
|
|
135
149
|
}
|
|
150
|
+
function userNpmCacheDir() {
|
|
151
|
+
return (0, path_1.resolve)((0, os_1.homedir)(), ".silicaclaw", "npm-cache");
|
|
152
|
+
}
|
|
153
|
+
function userShimPath() {
|
|
154
|
+
return (0, path_1.resolve)((0, os_1.homedir)(), ".silicaclaw", "bin", "silicaclaw");
|
|
155
|
+
}
|
|
136
156
|
function resolveWorkspaceRoot(cwd = process.cwd()) {
|
|
137
157
|
if ((0, fs_1.existsSync)((0, path_1.resolve)(cwd, "apps", "local-console", "package.json"))) {
|
|
138
158
|
return cwd;
|
|
@@ -380,44 +400,56 @@ function readOpenClawConfiguredGateway(workspaceRoot) {
|
|
|
380
400
|
gateway_url: OPENCLAW_GATEWAY_URL,
|
|
381
401
|
};
|
|
382
402
|
}
|
|
403
|
+
function resolveOpenClawStatusCommand(workspaceRoot) {
|
|
404
|
+
const explicitBin = String(process.env.OPENCLAW_BIN || "").trim();
|
|
405
|
+
if (explicitBin) {
|
|
406
|
+
return { cmd: explicitBin, args: ["status"] };
|
|
407
|
+
}
|
|
408
|
+
const configuredSourceDir = String(process.env.OPENCLAW_SOURCE_DIR || "").trim();
|
|
409
|
+
const defaultSourceDir = defaultOpenClawSourceDir(workspaceRoot);
|
|
410
|
+
const sourceDir = configuredSourceDir || defaultSourceDir;
|
|
411
|
+
const sourceEntry = existingPathOrNull((0, path_1.resolve)(sourceDir, "openclaw.mjs"));
|
|
412
|
+
if (sourceEntry) {
|
|
413
|
+
return { cmd: process.execPath, args: [sourceEntry, "status"] };
|
|
414
|
+
}
|
|
415
|
+
const commandPath = resolveExecutableInPath("openclaw");
|
|
416
|
+
if (commandPath) {
|
|
417
|
+
return { cmd: commandPath, args: ["status"] };
|
|
418
|
+
}
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
function resolveOpenClawGatewayProbeCommand(workspaceRoot) {
|
|
422
|
+
const explicitBin = String(process.env.OPENCLAW_BIN || "").trim();
|
|
423
|
+
if (explicitBin) {
|
|
424
|
+
return { cmd: explicitBin, args: ["gateway", "probe"] };
|
|
425
|
+
}
|
|
426
|
+
const configuredSourceDir = String(process.env.OPENCLAW_SOURCE_DIR || "").trim();
|
|
427
|
+
const defaultSourceDir = defaultOpenClawSourceDir(workspaceRoot);
|
|
428
|
+
const sourceDir = configuredSourceDir || defaultSourceDir;
|
|
429
|
+
const sourceEntry = existingPathOrNull((0, path_1.resolve)(sourceDir, "openclaw.mjs"));
|
|
430
|
+
if (sourceEntry) {
|
|
431
|
+
return { cmd: process.execPath, args: [sourceEntry, "gateway", "probe"] };
|
|
432
|
+
}
|
|
433
|
+
const commandPath = resolveExecutableInPath("openclaw");
|
|
434
|
+
if (commandPath) {
|
|
435
|
+
return { cmd: commandPath, args: ["gateway", "probe"] };
|
|
436
|
+
}
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
383
439
|
function detectOpenClawRuntime(workspaceRoot) {
|
|
384
440
|
const configuredGateway = readOpenClawConfiguredGateway(workspaceRoot);
|
|
385
|
-
const
|
|
441
|
+
const statusCommand = resolveOpenClawStatusCommand(workspaceRoot);
|
|
442
|
+
const statusLooksConfigured = Boolean(statusCommand ||
|
|
443
|
+
configuredGateway.config_path ||
|
|
444
|
+
detectOpenClawInstallation(workspaceRoot).detected);
|
|
445
|
+
const gatewayProbeCommand = ["lsof", "-nP", `-iTCP:${configuredGateway.gateway_port}`, "-sTCP:LISTEN"];
|
|
446
|
+
const gatewayProbe = (0, child_process_1.spawnSync)(gatewayProbeCommand[0], gatewayProbeCommand.slice(1), {
|
|
386
447
|
encoding: "utf8",
|
|
448
|
+
timeout: 1200,
|
|
387
449
|
});
|
|
388
|
-
const
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
.map((line) => line.trim())
|
|
392
|
-
.filter(Boolean);
|
|
393
|
-
const processes = lines
|
|
394
|
-
.map((line) => {
|
|
395
|
-
const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/);
|
|
396
|
-
if (!match)
|
|
397
|
-
return null;
|
|
398
|
-
const command = match[3] || "";
|
|
399
|
-
const lower = command.toLowerCase();
|
|
400
|
-
const isOpenClaw = lower.includes(" openclaw ") ||
|
|
401
|
-
lower.endsWith(" openclaw") ||
|
|
402
|
-
lower.includes("/openclaw ") ||
|
|
403
|
-
lower.includes("openclaw.mjs") ||
|
|
404
|
-
lower.includes("openclaw gateway") ||
|
|
405
|
-
lower.includes("openclaw agent") ||
|
|
406
|
-
lower.includes("openclaw message");
|
|
407
|
-
if (!isOpenClaw)
|
|
408
|
-
return null;
|
|
409
|
-
return {
|
|
410
|
-
pid: Number(match[1]),
|
|
411
|
-
ppid: Number(match[2]),
|
|
412
|
-
command,
|
|
413
|
-
};
|
|
414
|
-
})
|
|
415
|
-
.filter((item) => Boolean(item));
|
|
416
|
-
const openclawPids = new Set(processes.map((item) => item.pid));
|
|
417
|
-
const gatewayProbe = (0, child_process_1.spawnSync)("lsof", ["-nP", "-iTCP", "-sTCP:LISTEN"], {
|
|
418
|
-
encoding: "utf8",
|
|
419
|
-
});
|
|
420
|
-
const gatewayLines = String(gatewayProbe.stdout || "")
|
|
450
|
+
const gatewayStatusStdout = String(gatewayProbe.stdout || "");
|
|
451
|
+
const gatewayStatusStderr = String(gatewayProbe.stderr || "");
|
|
452
|
+
const gatewayLines = gatewayStatusStdout
|
|
421
453
|
.split("\n")
|
|
422
454
|
.map((line) => line.trim())
|
|
423
455
|
.filter(Boolean);
|
|
@@ -427,15 +459,10 @@ function detectOpenClawRuntime(workspaceRoot) {
|
|
|
427
459
|
const parts = line.split(/\s+/);
|
|
428
460
|
const pid = Number(parts[1] || 0);
|
|
429
461
|
const command = parts[0] || "";
|
|
430
|
-
const lowerCommand = command.toLowerCase();
|
|
431
462
|
const endpoint = parts[8] || parts[parts.length - 1] || "";
|
|
432
463
|
const portMatch = endpoint.match(/:(\d+)(?:\s*\(|$)/);
|
|
433
464
|
if (!pid || !command || !portMatch)
|
|
434
465
|
return null;
|
|
435
|
-
const isOpenClawListener = openclawPids.has(pid) ||
|
|
436
|
-
lowerCommand.includes("openclaw");
|
|
437
|
-
if (!isOpenClawListener)
|
|
438
|
-
return null;
|
|
439
466
|
const port = Number(portMatch[1]);
|
|
440
467
|
if (!Number.isFinite(port) || port <= 0)
|
|
441
468
|
return null;
|
|
@@ -447,49 +474,107 @@ function detectOpenClawRuntime(workspaceRoot) {
|
|
|
447
474
|
};
|
|
448
475
|
})
|
|
449
476
|
.filter((item) => Boolean(item));
|
|
477
|
+
const gatewayProbeOk = gatewayListeners.length > 0;
|
|
478
|
+
let processes = gatewayListeners.map((item) => ({
|
|
479
|
+
pid: item.pid,
|
|
480
|
+
ppid: item.ppid,
|
|
481
|
+
command: item.command,
|
|
482
|
+
}));
|
|
483
|
+
let processResult = null;
|
|
484
|
+
if (!gatewayProbeOk) {
|
|
485
|
+
processResult = (0, child_process_1.spawnSync)("ps", ["-Ao", "pid=,ppid=,command="], {
|
|
486
|
+
encoding: "utf8",
|
|
487
|
+
timeout: 1200,
|
|
488
|
+
});
|
|
489
|
+
const stdout = String(processResult.stdout || "");
|
|
490
|
+
const lines = stdout
|
|
491
|
+
.split("\n")
|
|
492
|
+
.map((line) => line.trim())
|
|
493
|
+
.filter(Boolean);
|
|
494
|
+
processes = lines
|
|
495
|
+
.map((line) => {
|
|
496
|
+
const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/);
|
|
497
|
+
if (!match)
|
|
498
|
+
return null;
|
|
499
|
+
const command = match[3] || "";
|
|
500
|
+
const lower = command.toLowerCase();
|
|
501
|
+
const isOpenClaw = lower.includes(" openclaw ") ||
|
|
502
|
+
lower.endsWith(" openclaw") ||
|
|
503
|
+
lower.includes("/openclaw ") ||
|
|
504
|
+
lower.includes("openclaw.mjs") ||
|
|
505
|
+
lower.includes("openclaw gateway") ||
|
|
506
|
+
lower.includes("openclaw agent") ||
|
|
507
|
+
lower.includes("openclaw message");
|
|
508
|
+
if (!isOpenClaw)
|
|
509
|
+
return null;
|
|
510
|
+
return {
|
|
511
|
+
pid: Number(match[1]),
|
|
512
|
+
ppid: Number(match[2]),
|
|
513
|
+
command,
|
|
514
|
+
};
|
|
515
|
+
})
|
|
516
|
+
.filter((item) => Boolean(item));
|
|
517
|
+
}
|
|
450
518
|
const preferredListener = gatewayListeners.find((item) => item.port === configuredGateway.gateway_port) ||
|
|
451
519
|
gatewayListeners[0] ||
|
|
452
520
|
null;
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
if (!combinedProcesses.has(process.pid)) {
|
|
456
|
-
combinedProcesses.set(process.pid, process);
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
|
-
const current = combinedProcesses.get(process.pid);
|
|
460
|
-
if (current && current.command.length < process.command.length) {
|
|
461
|
-
combinedProcesses.set(process.pid, process);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
const allProcesses = Array.from(combinedProcesses.values());
|
|
465
|
-
const gatewayReachable = gatewayListeners.length > 0;
|
|
521
|
+
const allProcesses = processes.slice(0, 10);
|
|
522
|
+
const gatewayReachable = gatewayProbeOk;
|
|
466
523
|
const detectionNotes = [];
|
|
467
|
-
if (result.status !== 0)
|
|
468
|
-
detectionNotes.push(String(result.stderr || "ps failed").trim());
|
|
469
524
|
if (gatewayProbe.status !== 0 && gatewayLines.length === 0) {
|
|
470
|
-
detectionNotes.push(String(
|
|
525
|
+
detectionNotes.push(String(gatewayStatusStderr || "openclaw gateway probe failed").trim());
|
|
526
|
+
}
|
|
527
|
+
if (processResult && processResult.status !== 0) {
|
|
528
|
+
detectionNotes.push(String(processResult.stderr || "ps failed").trim());
|
|
471
529
|
}
|
|
472
530
|
const gatewayPort = preferredListener?.port || configuredGateway.gateway_port;
|
|
473
531
|
const gatewayUrl = `http://${OPENCLAW_GATEWAY_HOST}:${gatewayPort}/`;
|
|
474
532
|
return {
|
|
475
|
-
running: allProcesses.length > 0 || gatewayReachable,
|
|
533
|
+
running: gatewayProbeOk || allProcesses.length > 0 || gatewayReachable,
|
|
476
534
|
process_count: allProcesses.length,
|
|
477
535
|
processes: allProcesses.slice(0, 10),
|
|
478
536
|
detection_error: detectionNotes.filter(Boolean).join(" | ") || null,
|
|
479
537
|
gateway_url: gatewayUrl,
|
|
480
538
|
gateway_port: gatewayPort,
|
|
481
539
|
gateway_reachable: gatewayReachable,
|
|
540
|
+
status_command: statusCommand ? [statusCommand.cmd, ...statusCommand.args].join(" ") : null,
|
|
541
|
+
status_ok: statusLooksConfigured,
|
|
542
|
+
status_summary: statusLooksConfigured
|
|
543
|
+
? configuredGateway.config_path
|
|
544
|
+
? `configured via ${configuredGateway.config_path}`
|
|
545
|
+
: statusCommand
|
|
546
|
+
? `command available: ${[statusCommand.cmd, ...statusCommand.args].join(" ")}`
|
|
547
|
+
: "OpenClaw environment detected"
|
|
548
|
+
: null,
|
|
549
|
+
gateway_probe_command: gatewayProbeCommand.join(" "),
|
|
550
|
+
gateway_probe_ok: gatewayProbeOk,
|
|
551
|
+
gateway_probe_summary: gatewayProbeOk
|
|
552
|
+
? gatewayStatusStdout
|
|
553
|
+
.split("\n")
|
|
554
|
+
.map((line) => line.trim())
|
|
555
|
+
.filter(Boolean)
|
|
556
|
+
.slice(0, 4)
|
|
557
|
+
.join(" | ")
|
|
558
|
+
: null,
|
|
482
559
|
configured_gateway_url: configuredGateway.gateway_url,
|
|
483
560
|
configured_gateway_port: configuredGateway.gateway_port,
|
|
484
561
|
configured_gateway_bind: configuredGateway.gateway_bind,
|
|
485
562
|
configured_gateway_config_path: configuredGateway.config_path,
|
|
486
|
-
detection_mode:
|
|
487
|
-
?
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
563
|
+
detection_mode: gatewayProbeOk
|
|
564
|
+
? (processes.length > 0 && gatewayReachable
|
|
565
|
+
? "gateway-probe+process+gateway"
|
|
566
|
+
: gatewayReachable
|
|
567
|
+
? "gateway-probe+gateway"
|
|
568
|
+
: processes.length > 0
|
|
569
|
+
? "gateway-probe+process"
|
|
570
|
+
: "gateway-probe")
|
|
571
|
+
: processes.length > 0 && gatewayReachable
|
|
572
|
+
? "process+gateway"
|
|
573
|
+
: gatewayReachable
|
|
574
|
+
? "gateway"
|
|
575
|
+
: processes.length > 0
|
|
576
|
+
? "process"
|
|
577
|
+
: "not_running",
|
|
493
578
|
};
|
|
494
579
|
}
|
|
495
580
|
function detectOpenClawSkillInstallation() {
|
|
@@ -688,20 +773,39 @@ class LocalNodeService {
|
|
|
688
773
|
socialMessageGovernanceRepo;
|
|
689
774
|
socialMessageRepo;
|
|
690
775
|
socialMessageObservationRepo;
|
|
776
|
+
privateMessageRepo;
|
|
777
|
+
privateMessageReceiptRepo;
|
|
778
|
+
privateEncryptionKeyRepo;
|
|
691
779
|
socialRuntimeRepo;
|
|
692
780
|
identity = null;
|
|
693
781
|
profile = null;
|
|
694
782
|
directory = (0, core_1.createEmptyDirectoryState)();
|
|
695
783
|
socialMessages = [];
|
|
696
784
|
socialMessageObservations = [];
|
|
785
|
+
privateMessages = [];
|
|
786
|
+
privateMessageReceipts = [];
|
|
787
|
+
privateEncryptionKeyPair = null;
|
|
788
|
+
privatePeerRoutes = {};
|
|
789
|
+
privateMessageBodyCache = new Map();
|
|
697
790
|
messageGovernance;
|
|
791
|
+
privateMessagesPersistDirty = false;
|
|
792
|
+
privateMessageReceiptsPersistDirty = false;
|
|
793
|
+
privateMessagesPersistTimer = null;
|
|
794
|
+
privateMessageReceiptsPersistTimer = null;
|
|
698
795
|
receivedCount = 0;
|
|
699
796
|
broadcastCount = 0;
|
|
700
797
|
lastMessageAt = 0;
|
|
701
798
|
lastBroadcastAt = 0;
|
|
799
|
+
lastProfileBroadcastAt = 0;
|
|
800
|
+
lastProfileBroadcastSignature = "";
|
|
801
|
+
lastReplayBroadcastAt = 0;
|
|
802
|
+
lastReplayBroadcastSignature = "";
|
|
702
803
|
lastBroadcastErrorAt = 0;
|
|
703
804
|
lastBroadcastError = null;
|
|
704
805
|
broadcastFailureCount = 0;
|
|
806
|
+
consecutiveBroadcastFailures = 0;
|
|
807
|
+
lastBroadcastRecoveryAttemptAt = 0;
|
|
808
|
+
broadcastRecoveryInFlight = false;
|
|
705
809
|
broadcaster = null;
|
|
706
810
|
subscriptionsBound = false;
|
|
707
811
|
broadcastEnabled = true;
|
|
@@ -739,6 +843,8 @@ class LocalNodeService {
|
|
|
739
843
|
networkReconnectTimer = null;
|
|
740
844
|
networkReconnectDelayMs = 5_000;
|
|
741
845
|
appVersion = "unknown";
|
|
846
|
+
openclawRuntimeCache = null;
|
|
847
|
+
openclawBridgeStatusCache = null;
|
|
742
848
|
constructor(options) {
|
|
743
849
|
this.workspaceRoot = options?.workspaceRoot || resolveWorkspaceRoot();
|
|
744
850
|
this.projectRoot = options?.projectRoot || resolveProjectRoot(this.workspaceRoot);
|
|
@@ -752,6 +858,9 @@ class LocalNodeService {
|
|
|
752
858
|
this.socialMessageGovernanceRepo = new storage_1.SocialMessageGovernanceRepo(this.storageRoot);
|
|
753
859
|
this.socialMessageRepo = new storage_1.SocialMessageRepo(this.storageRoot);
|
|
754
860
|
this.socialMessageObservationRepo = new storage_1.SocialMessageObservationRepo(this.storageRoot);
|
|
861
|
+
this.privateMessageRepo = new storage_1.PrivateMessageRepo(this.storageRoot);
|
|
862
|
+
this.privateMessageReceiptRepo = new storage_1.PrivateMessageReceiptRepo(this.storageRoot);
|
|
863
|
+
this.privateEncryptionKeyRepo = new storage_1.PrivateEncryptionKeyRepo(this.storageRoot);
|
|
755
864
|
this.socialRuntimeRepo = new storage_1.SocialRuntimeRepo(this.storageRoot);
|
|
756
865
|
this.messageGovernance = this.defaultMessageGovernance();
|
|
757
866
|
let loadedSocial = (0, core_1.loadSocialConfig)(this.projectRoot);
|
|
@@ -779,6 +888,22 @@ class LocalNodeService {
|
|
|
779
888
|
this.adapterMode = resolved.mode;
|
|
780
889
|
this.networkPort = resolved.port;
|
|
781
890
|
}
|
|
891
|
+
getCachedOpenClawRuntime() {
|
|
892
|
+
const now = Date.now();
|
|
893
|
+
if (this.openclawRuntimeCache && this.openclawRuntimeCache.expiresAt > now) {
|
|
894
|
+
return this.openclawRuntimeCache.value;
|
|
895
|
+
}
|
|
896
|
+
const value = detectOpenClawRuntime(this.projectRoot);
|
|
897
|
+
this.openclawRuntimeCache = {
|
|
898
|
+
value,
|
|
899
|
+
expiresAt: now + OPENCLAW_RUNTIME_CACHE_MS,
|
|
900
|
+
};
|
|
901
|
+
return value;
|
|
902
|
+
}
|
|
903
|
+
invalidateOpenClawCaches() {
|
|
904
|
+
this.openclawRuntimeCache = null;
|
|
905
|
+
this.openclawBridgeStatusCache = null;
|
|
906
|
+
}
|
|
782
907
|
async start() {
|
|
783
908
|
await this.hydrateFromDisk();
|
|
784
909
|
this.bindNetworkSubscriptions();
|
|
@@ -790,6 +915,7 @@ class LocalNodeService {
|
|
|
790
915
|
clearInterval(this.broadcaster);
|
|
791
916
|
this.broadcaster = null;
|
|
792
917
|
}
|
|
918
|
+
await this.flushPrivatePersistence();
|
|
793
919
|
if (this.networkStarted) {
|
|
794
920
|
await this.network.stop();
|
|
795
921
|
}
|
|
@@ -808,6 +934,9 @@ class LocalNodeService {
|
|
|
808
934
|
getOverview() {
|
|
809
935
|
const discovered = this.search("");
|
|
810
936
|
const onlineCount = discovered.filter((profile) => profile.online).length;
|
|
937
|
+
const openclawInstallation = detectOpenClawInstallation(this.projectRoot);
|
|
938
|
+
const openclawRuntime = this.getCachedOpenClawRuntime();
|
|
939
|
+
const openclawSkillInstallation = detectOpenClawSkillInstallation();
|
|
811
940
|
return {
|
|
812
941
|
app_version: this.appVersion,
|
|
813
942
|
agent_id: this.identity?.agent_id ?? "",
|
|
@@ -823,6 +952,15 @@ class LocalNodeService {
|
|
|
823
952
|
init_state: this.initState,
|
|
824
953
|
presence_ttl_ms: PRESENCE_TTL_MS,
|
|
825
954
|
onboarding: this.getOnboardingSummary(),
|
|
955
|
+
openclaw: {
|
|
956
|
+
detected: openclawInstallation.detected,
|
|
957
|
+
running: openclawRuntime.running,
|
|
958
|
+
detection_mode: openclawRuntime.detection_mode,
|
|
959
|
+
gateway_url: openclawRuntime.gateway_url,
|
|
960
|
+
gateway_probe_ok: openclawRuntime.gateway_probe_ok,
|
|
961
|
+
status_ok: openclawRuntime.status_ok,
|
|
962
|
+
skill_installed: openclawSkillInstallation.installed,
|
|
963
|
+
},
|
|
826
964
|
social: {
|
|
827
965
|
found: this.socialFound,
|
|
828
966
|
enabled: this.socialConfig.enabled,
|
|
@@ -965,6 +1103,7 @@ class LocalNodeService {
|
|
|
965
1103
|
const relayCapable = this.adapterMode === "webrtc-preview" || this.adapterMode === "relay-preview";
|
|
966
1104
|
const peers = diagnostics?.peers?.items ?? [];
|
|
967
1105
|
const online = peers.filter((peer) => peer.status === "online").length;
|
|
1106
|
+
const memory = process.memoryUsage();
|
|
968
1107
|
return {
|
|
969
1108
|
adapter: this.adapterMode,
|
|
970
1109
|
mode: this.networkMode,
|
|
@@ -988,6 +1127,23 @@ class LocalNodeService {
|
|
|
988
1127
|
adapter_stats: diagnostics?.stats ?? null,
|
|
989
1128
|
adapter_transport_stats: diagnostics?.transport_stats ?? null,
|
|
990
1129
|
adapter_discovery_stats: diagnostics?.discovery_stats ?? null,
|
|
1130
|
+
runtime_diagnostics: {
|
|
1131
|
+
memory_mib: {
|
|
1132
|
+
rss: formatBytesToMiB(memory.rss),
|
|
1133
|
+
heap_used: formatBytesToMiB(memory.heapUsed),
|
|
1134
|
+
heap_total: formatBytesToMiB(memory.heapTotal),
|
|
1135
|
+
external: formatBytesToMiB(memory.external),
|
|
1136
|
+
},
|
|
1137
|
+
directory: {
|
|
1138
|
+
profile_count: Object.keys(this.directory.profiles).length,
|
|
1139
|
+
presence_count: Object.keys(this.directory.presence).length,
|
|
1140
|
+
index_key_count: Object.keys(this.directory.index).length,
|
|
1141
|
+
},
|
|
1142
|
+
social: {
|
|
1143
|
+
message_count: this.socialMessages.length,
|
|
1144
|
+
observation_count: this.socialMessageObservations.length,
|
|
1145
|
+
},
|
|
1146
|
+
},
|
|
991
1147
|
adapter_diagnostics_summary: relayCapable || diagnostics
|
|
992
1148
|
? {
|
|
993
1149
|
started: this.networkStarted,
|
|
@@ -1099,6 +1255,91 @@ class LocalNodeService {
|
|
|
1099
1255
|
social_source_path: this.socialSourcePath,
|
|
1100
1256
|
};
|
|
1101
1257
|
}
|
|
1258
|
+
getAppUpdateStatus() {
|
|
1259
|
+
const currentVersion = normalizeVersionText(this.appVersion) || "unknown";
|
|
1260
|
+
const fallback = {
|
|
1261
|
+
current_version: currentVersion,
|
|
1262
|
+
latest_version: currentVersion,
|
|
1263
|
+
update_available: false,
|
|
1264
|
+
channel: "latest",
|
|
1265
|
+
platform: process.platform,
|
|
1266
|
+
checked_at: Date.now(),
|
|
1267
|
+
can_update: true,
|
|
1268
|
+
check_error: null,
|
|
1269
|
+
};
|
|
1270
|
+
try {
|
|
1271
|
+
const result = (0, child_process_1.spawnSync)("npm", ["view", "@silicaclaw/cli", "dist-tags", "--json"], {
|
|
1272
|
+
cwd: this.projectRoot,
|
|
1273
|
+
encoding: "utf8",
|
|
1274
|
+
env: {
|
|
1275
|
+
...process.env,
|
|
1276
|
+
SILICACLAW_WORKSPACE_DIR: this.projectRoot,
|
|
1277
|
+
SILICACLAW_APP_DIR: this.workspaceRoot,
|
|
1278
|
+
npm_config_cache: process.env.npm_config_cache || userNpmCacheDir(),
|
|
1279
|
+
},
|
|
1280
|
+
});
|
|
1281
|
+
if ((result.status ?? 1) !== 0) {
|
|
1282
|
+
return {
|
|
1283
|
+
...fallback,
|
|
1284
|
+
check_error: String(result.stderr || result.stdout || "npm view failed").trim() || "npm view failed",
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
const tags = JSON.parse(String(result.stdout || "{}").trim() || "{}");
|
|
1288
|
+
const latestVersion = normalizeVersionText(tags.latest || currentVersion) || currentVersion;
|
|
1289
|
+
return {
|
|
1290
|
+
...fallback,
|
|
1291
|
+
latest_version: latestVersion,
|
|
1292
|
+
update_available: compareVersionTokens(latestVersion, currentVersion) > 0,
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
catch (error) {
|
|
1296
|
+
return {
|
|
1297
|
+
...fallback,
|
|
1298
|
+
check_error: error instanceof Error ? error.message : String(error),
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
startAppUpdate() {
|
|
1303
|
+
const status = this.getAppUpdateStatus();
|
|
1304
|
+
if (!status.update_available || !status.latest_version) {
|
|
1305
|
+
return {
|
|
1306
|
+
started: false,
|
|
1307
|
+
target_version: status.latest_version || status.current_version,
|
|
1308
|
+
platform: process.platform,
|
|
1309
|
+
reason: status.check_error || "already_current",
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
const shimPath = userShimPath();
|
|
1313
|
+
const scriptPath = (0, path_1.resolve)(this.workspaceRoot, "scripts", "silicaclaw-cli.mjs");
|
|
1314
|
+
const useShim = (0, fs_1.existsSync)(shimPath);
|
|
1315
|
+
if (!useShim && !(0, fs_1.existsSync)(scriptPath)) {
|
|
1316
|
+
return {
|
|
1317
|
+
started: false,
|
|
1318
|
+
target_version: status.latest_version,
|
|
1319
|
+
platform: process.platform,
|
|
1320
|
+
reason: "missing_cli_script",
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
const command = useShim ? shimPath : process.execPath;
|
|
1324
|
+
const args = useShim ? ["update"] : [scriptPath, "update"];
|
|
1325
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
1326
|
+
cwd: this.projectRoot,
|
|
1327
|
+
detached: true,
|
|
1328
|
+
stdio: "ignore",
|
|
1329
|
+
env: {
|
|
1330
|
+
...process.env,
|
|
1331
|
+
SILICACLAW_WORKSPACE_DIR: this.projectRoot,
|
|
1332
|
+
SILICACLAW_APP_DIR: this.workspaceRoot,
|
|
1333
|
+
npm_config_cache: process.env.npm_config_cache || userNpmCacheDir(),
|
|
1334
|
+
},
|
|
1335
|
+
});
|
|
1336
|
+
child.unref();
|
|
1337
|
+
return {
|
|
1338
|
+
started: true,
|
|
1339
|
+
target_version: status.latest_version,
|
|
1340
|
+
platform: process.platform,
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1102
1343
|
getIntegrationSummary() {
|
|
1103
1344
|
const status = this.getIntegrationStatus();
|
|
1104
1345
|
const runtimeGenerated = Boolean(this.socialRuntime && this.socialRuntime.last_loaded_at > 0);
|
|
@@ -1355,6 +1596,7 @@ class LocalNodeService {
|
|
|
1355
1596
|
return {
|
|
1356
1597
|
...message,
|
|
1357
1598
|
display_name: profile?.display_name || message.display_name || "Unnamed",
|
|
1599
|
+
avatar_url: profile?.avatar_url || "",
|
|
1358
1600
|
is_self: message.agent_id === this.identity?.agent_id,
|
|
1359
1601
|
online: (0, core_1.isAgentOnline)(lastSeenAt, Date.now(), PRESENCE_TTL_MS),
|
|
1360
1602
|
last_seen_at: lastSeenAt || null,
|
|
@@ -1373,10 +1615,115 @@ class LocalNodeService {
|
|
|
1373
1615
|
},
|
|
1374
1616
|
};
|
|
1375
1617
|
}
|
|
1618
|
+
getPrivateMessagingState() {
|
|
1619
|
+
return {
|
|
1620
|
+
enabled: Boolean(this.identity && this.privateEncryptionKeyPair),
|
|
1621
|
+
agent_id: this.identity?.agent_id || "",
|
|
1622
|
+
encryption_public_key: this.privateEncryptionKeyPair?.public_key || "",
|
|
1623
|
+
conversation_count: this.getPrivateConversations().length,
|
|
1624
|
+
message_count: this.privateMessages.length,
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
getPrivateConversations() {
|
|
1628
|
+
const conversations = new Map();
|
|
1629
|
+
for (const message of this.privateMessages) {
|
|
1630
|
+
if (message.from_agent_id === message.to_agent_id) {
|
|
1631
|
+
continue;
|
|
1632
|
+
}
|
|
1633
|
+
const peerAgentId = message.from_agent_id === this.identity?.agent_id ? message.to_agent_id : message.from_agent_id;
|
|
1634
|
+
if (!peerAgentId || peerAgentId === this.identity?.agent_id) {
|
|
1635
|
+
continue;
|
|
1636
|
+
}
|
|
1637
|
+
const peerProfile = this.directory.profiles[peerAgentId];
|
|
1638
|
+
const current = conversations.get(message.conversation_id);
|
|
1639
|
+
const nextLast = Math.max(current?.last_message_at || 0, message.created_at || 0) || null;
|
|
1640
|
+
conversations.set(message.conversation_id, {
|
|
1641
|
+
conversation_id: message.conversation_id,
|
|
1642
|
+
peer_agent_id: peerAgentId,
|
|
1643
|
+
peer_display_name: peerProfile?.display_name || peerAgentId,
|
|
1644
|
+
peer_avatar_url: peerProfile?.avatar_url || "",
|
|
1645
|
+
peer_public_key: peerProfile?.private_encryption_public_key || "",
|
|
1646
|
+
last_message_at: nextLast,
|
|
1647
|
+
unread_count: current?.unread_count || 0,
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
return Array.from(conversations.values()).sort((a, b) => (b.last_message_at || 0) - (a.last_message_at || 0));
|
|
1651
|
+
}
|
|
1652
|
+
getPrivateMessages(conversationId, limit = PRIVATE_MESSAGE_QUERY_LIMIT) {
|
|
1653
|
+
const normalizedConversationId = String(conversationId || "").trim();
|
|
1654
|
+
const resolvedLimit = Math.max(1, Math.min(PRIVATE_MESSAGE_QUERY_LIMIT, Number(limit) || PRIVATE_MESSAGE_QUERY_LIMIT));
|
|
1655
|
+
const receiptsByMessageId = new Map(this.privateMessageReceipts.map((receipt) => [receipt.message_id, receipt.status]));
|
|
1656
|
+
return this.privateMessages
|
|
1657
|
+
.filter((message) => {
|
|
1658
|
+
if (message.from_agent_id === message.to_agent_id) {
|
|
1659
|
+
return false;
|
|
1660
|
+
}
|
|
1661
|
+
const peerAgentId = message.from_agent_id === this.identity?.agent_id ? message.to_agent_id : message.from_agent_id;
|
|
1662
|
+
if (!peerAgentId || peerAgentId === this.identity?.agent_id) {
|
|
1663
|
+
return false;
|
|
1664
|
+
}
|
|
1665
|
+
return !normalizedConversationId || message.conversation_id === normalizedConversationId;
|
|
1666
|
+
})
|
|
1667
|
+
.sort((a, b) => a.created_at - b.created_at)
|
|
1668
|
+
.slice(-resolvedLimit)
|
|
1669
|
+
.map((message) => ({
|
|
1670
|
+
message_id: message.message_id,
|
|
1671
|
+
conversation_id: message.conversation_id,
|
|
1672
|
+
from_agent_id: message.from_agent_id,
|
|
1673
|
+
to_agent_id: message.to_agent_id,
|
|
1674
|
+
body: this.decryptPrivateMessageBody(message),
|
|
1675
|
+
created_at: message.created_at,
|
|
1676
|
+
is_self: message.from_agent_id === this.identity?.agent_id,
|
|
1677
|
+
delivery_status: receiptsByMessageId.get(message.message_id) || "sent",
|
|
1678
|
+
}));
|
|
1679
|
+
}
|
|
1680
|
+
async sendPrivateMessage(input) {
|
|
1681
|
+
if (!this.identity || !this.privateEncryptionKeyPair) {
|
|
1682
|
+
return { sent: false, reason: "missing_identity_or_private_key" };
|
|
1683
|
+
}
|
|
1684
|
+
const toAgentId = String(input.to_agent_id || "").trim();
|
|
1685
|
+
const recipientKey = String(input.recipient_encryption_public_key || "").trim();
|
|
1686
|
+
const body = String(input.body || "").trim();
|
|
1687
|
+
if (toAgentId === this.identity.agent_id) {
|
|
1688
|
+
return { sent: false, reason: "self_private_message_not_allowed" };
|
|
1689
|
+
}
|
|
1690
|
+
const toPeerId = this.privatePeerRoutes[toAgentId] || "";
|
|
1691
|
+
if (!toAgentId || !toPeerId || !recipientKey || !body) {
|
|
1692
|
+
return { sent: false, reason: "invalid_private_message_input" };
|
|
1693
|
+
}
|
|
1694
|
+
if (typeof this.network.sendDirect !== "function") {
|
|
1695
|
+
return { sent: false, reason: "direct_delivery_not_supported" };
|
|
1696
|
+
}
|
|
1697
|
+
const encrypted = (0, core_1.encryptPrivatePayload)({
|
|
1698
|
+
plaintext: body,
|
|
1699
|
+
recipient_public_key: recipientKey,
|
|
1700
|
+
sender_keypair: this.privateEncryptionKeyPair,
|
|
1701
|
+
});
|
|
1702
|
+
const message = (0, core_1.signPrivateMessage)({
|
|
1703
|
+
identity: this.identity,
|
|
1704
|
+
message_id: (0, crypto_1.createHash)("sha256").update(`${this.identity.agent_id}:${toAgentId}:${Date.now()}:${body}:${Math.random()}`, "utf8").digest("hex"),
|
|
1705
|
+
conversation_id: this.buildPrivateConversationId(this.identity.agent_id, toAgentId),
|
|
1706
|
+
to_agent_id: toAgentId,
|
|
1707
|
+
sender_encryption_public_key: encrypted.sender_encryption_public_key,
|
|
1708
|
+
recipient_encryption_public_key: recipientKey,
|
|
1709
|
+
ciphertext: encrypted.ciphertext,
|
|
1710
|
+
nonce: encrypted.nonce,
|
|
1711
|
+
created_at: Date.now(),
|
|
1712
|
+
});
|
|
1713
|
+
this.ingestPrivateMessage(message);
|
|
1714
|
+
await this.persistPrivateMessages();
|
|
1715
|
+
await this.network.sendDirect(toPeerId, PRIVATE_MESSAGE_TOPIC, message);
|
|
1716
|
+
const view = this.getPrivateMessages(message.conversation_id).find((item) => item.message_id === message.message_id);
|
|
1717
|
+
return { sent: true, reason: "sent", message: view };
|
|
1718
|
+
}
|
|
1376
1719
|
getOpenClawBridgeStatus() {
|
|
1720
|
+
const now = Date.now();
|
|
1721
|
+
if (this.openclawBridgeStatusCache && this.openclawBridgeStatusCache.expiresAt > now) {
|
|
1722
|
+
return this.openclawBridgeStatusCache.value;
|
|
1723
|
+
}
|
|
1377
1724
|
const integration = this.getIntegrationStatus();
|
|
1378
1725
|
const openclawInstallation = detectOpenClawInstallation(this.projectRoot);
|
|
1379
|
-
const openclawRuntime =
|
|
1726
|
+
const openclawRuntime = this.getCachedOpenClawRuntime();
|
|
1380
1727
|
const skillInstallation = detectOpenClawSkillInstallation();
|
|
1381
1728
|
const ownerDelivery = detectOwnerDeliveryStatus({
|
|
1382
1729
|
workspaceRoot: this.projectRoot,
|
|
@@ -1384,7 +1731,7 @@ class LocalNodeService {
|
|
|
1384
1731
|
openclawRunning: openclawRuntime.running,
|
|
1385
1732
|
skillInstalled: skillInstallation.installed,
|
|
1386
1733
|
});
|
|
1387
|
-
|
|
1734
|
+
const value = {
|
|
1388
1735
|
enabled: this.socialConfig.enabled,
|
|
1389
1736
|
connected_to_silicaclaw: integration.connected_to_silicaclaw,
|
|
1390
1737
|
public_enabled: integration.public_enabled,
|
|
@@ -1440,6 +1787,11 @@ class LocalNodeService {
|
|
|
1440
1787
|
install_skill: "/api/openclaw/bridge/skill-install",
|
|
1441
1788
|
},
|
|
1442
1789
|
};
|
|
1790
|
+
this.openclawBridgeStatusCache = {
|
|
1791
|
+
value,
|
|
1792
|
+
expiresAt: now + OPENCLAW_BRIDGE_STATUS_CACHE_MS,
|
|
1793
|
+
};
|
|
1794
|
+
return value;
|
|
1443
1795
|
}
|
|
1444
1796
|
async installOpenClawSkill(skillName) {
|
|
1445
1797
|
const scriptPath = (0, path_1.resolve)(this.workspaceRoot, "scripts", "install-openclaw-skill.mjs");
|
|
@@ -1453,6 +1805,7 @@ class LocalNodeService {
|
|
|
1453
1805
|
maxBuffer: 1024 * 1024,
|
|
1454
1806
|
});
|
|
1455
1807
|
const parsed = JSON.parse(String(stdout || "{}"));
|
|
1808
|
+
this.invalidateOpenClawCaches();
|
|
1456
1809
|
return {
|
|
1457
1810
|
...parsed,
|
|
1458
1811
|
bridge: this.getOpenClawBridgeStatus(),
|
|
@@ -1472,7 +1825,7 @@ class LocalNodeService {
|
|
|
1472
1825
|
const workspaceSkillDir = (0, path_1.resolve)(homeDir, "workspace", "skills");
|
|
1473
1826
|
const legacySkillDir = (0, path_1.resolve)(homeDir, "skills");
|
|
1474
1827
|
const openclawSourceDir = defaultOpenClawSourceDir(this.projectRoot);
|
|
1475
|
-
const openclawRuntime =
|
|
1828
|
+
const openclawRuntime = this.getCachedOpenClawRuntime();
|
|
1476
1829
|
return {
|
|
1477
1830
|
bridge_api_base: DEFAULT_BRIDGE_API_BASE,
|
|
1478
1831
|
openclaw_detected: detectOpenClawInstallation(this.projectRoot).detected,
|
|
@@ -1845,14 +2198,13 @@ class LocalNodeService {
|
|
|
1845
2198
|
profile: this.profile,
|
|
1846
2199
|
};
|
|
1847
2200
|
const presenceRecord = (0, core_1.signPresence)(this.identity, Date.now());
|
|
1848
|
-
const
|
|
1849
|
-
const replayMessages = this.getReplayableSelfSocialMessages();
|
|
2201
|
+
const shouldPublishProfile = this.shouldPublishProfileRecord(profileRecord, reason, presenceRecord.timestamp);
|
|
2202
|
+
const replayMessages = this.getReplayableSelfSocialMessages(reason);
|
|
1850
2203
|
try {
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
for (const record of indexRecords) {
|
|
1854
|
-
await this.publish("index", record);
|
|
2204
|
+
if (shouldPublishProfile) {
|
|
2205
|
+
await this.publish("profile", profileRecord);
|
|
1855
2206
|
}
|
|
2207
|
+
await this.publish("presence", presenceRecord);
|
|
1856
2208
|
for (const message of replayMessages) {
|
|
1857
2209
|
await this.publish(SOCIAL_MESSAGE_TOPIC, message);
|
|
1858
2210
|
}
|
|
@@ -1862,23 +2214,67 @@ class LocalNodeService {
|
|
|
1862
2214
|
this.lastBroadcastErrorAt = Date.now();
|
|
1863
2215
|
this.lastBroadcastError = message;
|
|
1864
2216
|
this.broadcastFailureCount += 1;
|
|
2217
|
+
this.consecutiveBroadcastFailures += 1;
|
|
1865
2218
|
await this.log("error", `Broadcast failed (reason=${reason}): ${message}`);
|
|
2219
|
+
await this.maybeRecoverFromBroadcastFailure(reason, message);
|
|
1866
2220
|
return { sent: false, reason: "publish_failed", error: message };
|
|
1867
2221
|
}
|
|
1868
2222
|
this.lastBroadcastAt = Date.now();
|
|
1869
2223
|
this.broadcastCount += 1;
|
|
1870
2224
|
this.lastBroadcastError = null;
|
|
1871
2225
|
this.lastBroadcastErrorAt = 0;
|
|
2226
|
+
this.consecutiveBroadcastFailures = 0;
|
|
1872
2227
|
this.directory = (0, core_1.ingestProfileRecord)(this.directory, profileRecord);
|
|
1873
2228
|
this.directory = (0, core_1.ingestPresenceRecord)(this.directory, presenceRecord);
|
|
1874
|
-
for (const record of indexRecords) {
|
|
1875
|
-
this.directory = (0, core_1.ingestIndexRecord)(this.directory, record);
|
|
1876
|
-
}
|
|
1877
2229
|
this.compactCacheInMemory();
|
|
1878
2230
|
await this.persistCache();
|
|
1879
|
-
await this.log("info", `Broadcast sent (${
|
|
2231
|
+
await this.log("info", `Broadcast sent (${shouldPublishProfile ? "profile + " : ""}presence, replayed_messages=${replayMessages.length}, reason=${reason})`);
|
|
1880
2232
|
return { sent: true, reason };
|
|
1881
2233
|
}
|
|
2234
|
+
shouldPublishProfileRecord(profileRecord, reason, now = Date.now()) {
|
|
2235
|
+
if (reason !== "interval") {
|
|
2236
|
+
this.lastProfileBroadcastSignature = profileRecord.profile.signature;
|
|
2237
|
+
this.lastProfileBroadcastAt = now;
|
|
2238
|
+
return true;
|
|
2239
|
+
}
|
|
2240
|
+
const signature = profileRecord.profile.signature;
|
|
2241
|
+
const changedSinceLastPublish = signature !== this.lastProfileBroadcastSignature;
|
|
2242
|
+
const refreshDue = now - this.lastProfileBroadcastAt >= PROFILE_RELAY_REFRESH_INTERVAL_MS;
|
|
2243
|
+
if (!changedSinceLastPublish && !refreshDue) {
|
|
2244
|
+
return false;
|
|
2245
|
+
}
|
|
2246
|
+
this.lastProfileBroadcastSignature = signature;
|
|
2247
|
+
this.lastProfileBroadcastAt = now;
|
|
2248
|
+
return true;
|
|
2249
|
+
}
|
|
2250
|
+
async maybeRecoverFromBroadcastFailure(reason, errorMessage) {
|
|
2251
|
+
const recoveryThreshold = 3;
|
|
2252
|
+
const recoveryCooldownMs = 60_000;
|
|
2253
|
+
if (this.broadcastRecoveryInFlight) {
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
if (this.consecutiveBroadcastFailures < recoveryThreshold) {
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
if (Date.now() - this.lastBroadcastRecoveryAttemptAt < recoveryCooldownMs) {
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2262
|
+
if (this.adapterMode !== "relay-preview" && this.adapterMode !== "webrtc-preview" && this.adapterMode !== "real-preview") {
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
2265
|
+
this.broadcastRecoveryInFlight = true;
|
|
2266
|
+
this.lastBroadcastRecoveryAttemptAt = Date.now();
|
|
2267
|
+
try {
|
|
2268
|
+
await this.log("warn", `Broadcast recovery triggered after ${this.consecutiveBroadcastFailures} consecutive failures (${reason}): ${errorMessage}`);
|
|
2269
|
+
await this.restartNetworkAdapter("broadcast_failure_recovery");
|
|
2270
|
+
}
|
|
2271
|
+
catch (recoveryError) {
|
|
2272
|
+
await this.log("error", `Broadcast recovery failed: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}`);
|
|
2273
|
+
}
|
|
2274
|
+
finally {
|
|
2275
|
+
this.broadcastRecoveryInFlight = false;
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
1882
2278
|
async hydrateFromDisk() {
|
|
1883
2279
|
this.initState = {
|
|
1884
2280
|
identity_auto_created: false,
|
|
@@ -1907,6 +2303,8 @@ class LocalNodeService {
|
|
|
1907
2303
|
await this.log("info", `Bound existing OpenClaw identity: ${resolvedIdentity.openclaw_source_path}`);
|
|
1908
2304
|
}
|
|
1909
2305
|
await this.identityRepo.set(this.identity);
|
|
2306
|
+
this.privateEncryptionKeyPair = (await this.privateEncryptionKeyRepo.get()) || (0, core_1.createPrivateEncryptionKeyPair)();
|
|
2307
|
+
await this.privateEncryptionKeyRepo.set(this.privateEncryptionKeyPair);
|
|
1910
2308
|
const existingProfile = await this.profileRepo.get();
|
|
1911
2309
|
const profileInput = (0, core_1.resolveProfileInputWithSocial)({
|
|
1912
2310
|
socialConfig: this.socialConfig,
|
|
@@ -1914,7 +2312,10 @@ class LocalNodeService {
|
|
|
1914
2312
|
existingProfile: existingProfile && existingProfile.agent_id === this.identity.agent_id ? existingProfile : null,
|
|
1915
2313
|
rootDir: this.projectRoot,
|
|
1916
2314
|
});
|
|
1917
|
-
this.profile = (0, core_1.signProfile)(
|
|
2315
|
+
this.profile = (0, core_1.signProfile)({
|
|
2316
|
+
...profileInput,
|
|
2317
|
+
private_encryption_public_key: this.privateEncryptionKeyPair?.public_key || profileInput.private_encryption_public_key || "",
|
|
2318
|
+
}, this.identity);
|
|
1918
2319
|
if (!existingProfile || existingProfile.agent_id !== this.identity.agent_id) {
|
|
1919
2320
|
this.initState.profile_auto_created = true;
|
|
1920
2321
|
await this.log("info", "profile.json missing/invalid, initialized from social/default profile");
|
|
@@ -1927,6 +2328,8 @@ class LocalNodeService {
|
|
|
1927
2328
|
};
|
|
1928
2329
|
this.socialMessages = this.normalizeSocialMessages(await this.socialMessageRepo.get());
|
|
1929
2330
|
this.socialMessageObservations = this.normalizeSocialMessageObservations(await this.socialMessageObservationRepo.get());
|
|
2331
|
+
this.privateMessages = this.normalizePrivateMessages(await this.privateMessageRepo.get());
|
|
2332
|
+
this.privateMessageReceipts = this.normalizePrivateMessageReceipts(await this.privateMessageReceiptRepo.get());
|
|
1930
2333
|
this.directory = (0, core_1.ingestProfileRecord)(this.directory, { type: "profile", profile: this.profile });
|
|
1931
2334
|
this.compactCacheInMemory();
|
|
1932
2335
|
await this.persistCache();
|
|
@@ -1943,7 +2346,10 @@ class LocalNodeService {
|
|
|
1943
2346
|
existingProfile: this.profile,
|
|
1944
2347
|
rootDir: this.projectRoot,
|
|
1945
2348
|
});
|
|
1946
|
-
const nextProfile = (0, core_1.signProfile)(
|
|
2349
|
+
const nextProfile = (0, core_1.signProfile)({
|
|
2350
|
+
...nextProfileInput,
|
|
2351
|
+
private_encryption_public_key: this.privateEncryptionKeyPair?.public_key || nextProfileInput.private_encryption_public_key || "",
|
|
2352
|
+
}, this.identity);
|
|
1947
2353
|
this.profile = nextProfile;
|
|
1948
2354
|
await this.profileRepo.set(nextProfile);
|
|
1949
2355
|
this.directory = (0, core_1.ingestProfileRecord)(this.directory, { type: "profile", profile: nextProfile });
|
|
@@ -2001,7 +2407,7 @@ class LocalNodeService {
|
|
|
2001
2407
|
this.socialRuntime = runtime;
|
|
2002
2408
|
await this.socialRuntimeRepo.set(runtime);
|
|
2003
2409
|
}
|
|
2004
|
-
async onMessage(topic, data) {
|
|
2410
|
+
async onMessage(topic, data, meta) {
|
|
2005
2411
|
this.receivedCount += 1;
|
|
2006
2412
|
this.receivedByTopic[topic] = (this.receivedByTopic[topic] ?? 0) + 1;
|
|
2007
2413
|
this.lastMessageAt = Date.now();
|
|
@@ -2016,6 +2422,9 @@ class LocalNodeService {
|
|
|
2016
2422
|
return;
|
|
2017
2423
|
}
|
|
2018
2424
|
}
|
|
2425
|
+
if (meta?.peerId && record.profile.agent_id) {
|
|
2426
|
+
this.privatePeerRoutes[record.profile.agent_id] = meta.peerId;
|
|
2427
|
+
}
|
|
2019
2428
|
this.directory = (0, core_1.ingestProfileRecord)(this.directory, record);
|
|
2020
2429
|
this.compactCacheInMemory();
|
|
2021
2430
|
await this.persistCache();
|
|
@@ -2032,6 +2441,9 @@ class LocalNodeService {
|
|
|
2032
2441
|
return;
|
|
2033
2442
|
}
|
|
2034
2443
|
}
|
|
2444
|
+
if (meta?.peerId && record.agent_id) {
|
|
2445
|
+
this.privatePeerRoutes[record.agent_id] = meta.peerId;
|
|
2446
|
+
}
|
|
2035
2447
|
this.directory = (0, core_1.ingestPresenceRecord)(this.directory, record);
|
|
2036
2448
|
this.compactCacheInMemory();
|
|
2037
2449
|
await this.persistCache();
|
|
@@ -2046,6 +2458,9 @@ class LocalNodeService {
|
|
|
2046
2458
|
await this.log("warn", `Rejected social message with invalid signature (${record.message_id.slice(0, 10)})`);
|
|
2047
2459
|
return;
|
|
2048
2460
|
}
|
|
2461
|
+
if (meta?.peerId && record.agent_id) {
|
|
2462
|
+
this.privatePeerRoutes[record.agent_id] = meta.peerId;
|
|
2463
|
+
}
|
|
2049
2464
|
if (this.hasSocialMessage(record.message_id)) {
|
|
2050
2465
|
await this.publishObservationForMessage(record);
|
|
2051
2466
|
return;
|
|
@@ -2081,6 +2496,30 @@ class LocalNodeService {
|
|
|
2081
2496
|
this.directory = (0, core_1.dedupeIndex)(this.directory);
|
|
2082
2497
|
await this.persistCache();
|
|
2083
2498
|
}
|
|
2499
|
+
async onDirectMessage(topic, data, meta) {
|
|
2500
|
+
if (topic === PRIVATE_MESSAGE_TOPIC) {
|
|
2501
|
+
const record = this.normalizeIncomingPrivateMessage(data);
|
|
2502
|
+
if (!record || !(0, core_1.verifyPrivateMessage)(record)) {
|
|
2503
|
+
return;
|
|
2504
|
+
}
|
|
2505
|
+
if (record.to_agent_id !== this.identity?.agent_id || this.hasPrivateMessage(record.message_id)) {
|
|
2506
|
+
return;
|
|
2507
|
+
}
|
|
2508
|
+
this.ingestPrivateMessage(record);
|
|
2509
|
+
await this.persistPrivateMessages();
|
|
2510
|
+
await this.sendPrivateMessageReceipt(record, meta?.peerId);
|
|
2511
|
+
return;
|
|
2512
|
+
}
|
|
2513
|
+
const receipt = this.normalizeIncomingPrivateMessageReceipt(data);
|
|
2514
|
+
if (!receipt || !(0, core_1.verifyPrivateMessageReceipt)(receipt)) {
|
|
2515
|
+
return;
|
|
2516
|
+
}
|
|
2517
|
+
if (receipt.to_agent_id !== this.identity?.agent_id) {
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2520
|
+
this.ingestPrivateMessageReceipt(receipt);
|
|
2521
|
+
await this.persistPrivateMessageReceipts();
|
|
2522
|
+
}
|
|
2084
2523
|
startBroadcastLoop() {
|
|
2085
2524
|
if (this.broadcaster) {
|
|
2086
2525
|
clearInterval(this.broadcaster);
|
|
@@ -2101,21 +2540,29 @@ class LocalNodeService {
|
|
|
2101
2540
|
if (this.subscriptionsBound) {
|
|
2102
2541
|
return;
|
|
2103
2542
|
}
|
|
2104
|
-
this.network.subscribe("profile", (data) => {
|
|
2105
|
-
this.onMessage("profile", data);
|
|
2543
|
+
this.network.subscribe("profile", (data, meta) => {
|
|
2544
|
+
this.onMessage("profile", data, meta);
|
|
2106
2545
|
});
|
|
2107
|
-
this.network.subscribe("presence", (data) => {
|
|
2108
|
-
this.onMessage("presence", data);
|
|
2546
|
+
this.network.subscribe("presence", (data, meta) => {
|
|
2547
|
+
this.onMessage("presence", data, meta);
|
|
2109
2548
|
});
|
|
2110
|
-
this.network.subscribe("index", (data) => {
|
|
2111
|
-
this.onMessage("index", data);
|
|
2549
|
+
this.network.subscribe("index", (data, meta) => {
|
|
2550
|
+
this.onMessage("index", data, meta);
|
|
2112
2551
|
});
|
|
2113
|
-
this.network.subscribe(SOCIAL_MESSAGE_TOPIC, (data) => {
|
|
2114
|
-
this.onMessage(SOCIAL_MESSAGE_TOPIC, data);
|
|
2552
|
+
this.network.subscribe(SOCIAL_MESSAGE_TOPIC, (data, meta) => {
|
|
2553
|
+
this.onMessage(SOCIAL_MESSAGE_TOPIC, data, meta);
|
|
2115
2554
|
});
|
|
2116
|
-
this.network.subscribe(SOCIAL_MESSAGE_OBSERVATION_TOPIC, (data) => {
|
|
2117
|
-
this.onMessage(SOCIAL_MESSAGE_OBSERVATION_TOPIC, data);
|
|
2555
|
+
this.network.subscribe(SOCIAL_MESSAGE_OBSERVATION_TOPIC, (data, meta) => {
|
|
2556
|
+
this.onMessage(SOCIAL_MESSAGE_OBSERVATION_TOPIC, data, meta);
|
|
2118
2557
|
});
|
|
2558
|
+
if (typeof this.network.subscribeDirect === "function") {
|
|
2559
|
+
this.network.subscribeDirect(PRIVATE_MESSAGE_TOPIC, (data, meta) => {
|
|
2560
|
+
this.onDirectMessage(PRIVATE_MESSAGE_TOPIC, data, meta);
|
|
2561
|
+
});
|
|
2562
|
+
this.network.subscribeDirect(PRIVATE_MESSAGE_RECEIPT_TOPIC, (data, meta) => {
|
|
2563
|
+
this.onDirectMessage(PRIVATE_MESSAGE_RECEIPT_TOPIC, data, meta);
|
|
2564
|
+
});
|
|
2565
|
+
}
|
|
2119
2566
|
this.subscriptionsBound = true;
|
|
2120
2567
|
}
|
|
2121
2568
|
buildNetworkAdapter() {
|
|
@@ -2255,9 +2702,58 @@ class LocalNodeService {
|
|
|
2255
2702
|
}, delayMs);
|
|
2256
2703
|
this.networkReconnectDelayMs = Math.min(30_000, Math.max(5_000, Math.floor(delayMs * 1.5)));
|
|
2257
2704
|
}
|
|
2705
|
+
pruneRemoteProfilesInMemory(now = Date.now()) {
|
|
2706
|
+
if (!Number.isFinite(DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT) || DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT <= 0) {
|
|
2707
|
+
return 0;
|
|
2708
|
+
}
|
|
2709
|
+
const selfAgentId = this.profile?.agent_id || this.identity?.agent_id || "";
|
|
2710
|
+
const remoteProfiles = Object.values(this.directory.profiles).filter((profile) => profile.agent_id !== selfAgentId);
|
|
2711
|
+
if (remoteProfiles.length <= DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT) {
|
|
2712
|
+
return 0;
|
|
2713
|
+
}
|
|
2714
|
+
const onlineRemoteProfiles = remoteProfiles.filter((profile) => (0, core_1.isAgentOnline)(this.directory.presence[profile.agent_id], now, PRESENCE_TTL_MS));
|
|
2715
|
+
const offlineRemoteProfiles = remoteProfiles
|
|
2716
|
+
.filter((profile) => !(0, core_1.isAgentOnline)(this.directory.presence[profile.agent_id], now, PRESENCE_TTL_MS))
|
|
2717
|
+
.sort((a, b) => (b.updated_at || 0) - (a.updated_at || 0));
|
|
2718
|
+
const keepOfflineCount = Math.max(0, DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT - onlineRemoteProfiles.length);
|
|
2719
|
+
const keptRemoteProfiles = [
|
|
2720
|
+
...onlineRemoteProfiles,
|
|
2721
|
+
...offlineRemoteProfiles.slice(0, keepOfflineCount),
|
|
2722
|
+
];
|
|
2723
|
+
const keptRemoteIds = new Set(keptRemoteProfiles.map((profile) => profile.agent_id));
|
|
2724
|
+
const removedIds = remoteProfiles
|
|
2725
|
+
.map((profile) => profile.agent_id)
|
|
2726
|
+
.filter((agentId) => !keptRemoteIds.has(agentId));
|
|
2727
|
+
if (removedIds.length === 0) {
|
|
2728
|
+
return 0;
|
|
2729
|
+
}
|
|
2730
|
+
const next = (0, core_1.createEmptyDirectoryState)();
|
|
2731
|
+
const selfProfile = selfAgentId ? this.directory.profiles[selfAgentId] : null;
|
|
2732
|
+
if (selfProfile) {
|
|
2733
|
+
next.profiles[selfAgentId] = selfProfile;
|
|
2734
|
+
const selfPresence = this.directory.presence[selfAgentId];
|
|
2735
|
+
if (typeof selfPresence === "number" && Number.isFinite(selfPresence)) {
|
|
2736
|
+
next.presence[selfAgentId] = selfPresence;
|
|
2737
|
+
}
|
|
2738
|
+
const rebuilt = (0, core_1.rebuildIndexForProfile)(next, selfProfile);
|
|
2739
|
+
next.index = rebuilt.index;
|
|
2740
|
+
}
|
|
2741
|
+
for (const profile of keptRemoteProfiles) {
|
|
2742
|
+
next.profiles[profile.agent_id] = profile;
|
|
2743
|
+
const seenAt = this.directory.presence[profile.agent_id];
|
|
2744
|
+
if (typeof seenAt === "number" && Number.isFinite(seenAt)) {
|
|
2745
|
+
next.presence[profile.agent_id] = seenAt;
|
|
2746
|
+
}
|
|
2747
|
+
const rebuilt = (0, core_1.rebuildIndexForProfile)(next, profile);
|
|
2748
|
+
next.index = rebuilt.index;
|
|
2749
|
+
}
|
|
2750
|
+
this.directory = (0, core_1.dedupeIndex)(next);
|
|
2751
|
+
return removedIds.length;
|
|
2752
|
+
}
|
|
2258
2753
|
compactCacheInMemory() {
|
|
2259
2754
|
const cleaned = (0, core_1.cleanupExpiredPresence)(this.directory, Date.now(), PRESENCE_TTL_MS);
|
|
2260
2755
|
this.directory = (0, core_1.dedupeIndex)(cleaned.state);
|
|
2756
|
+
this.pruneRemoteProfilesInMemory();
|
|
2261
2757
|
return cleaned.removed;
|
|
2262
2758
|
}
|
|
2263
2759
|
async publish(topic, data) {
|
|
@@ -2288,6 +2784,52 @@ class LocalNodeService {
|
|
|
2288
2784
|
async persistSocialMessageObservations() {
|
|
2289
2785
|
await this.socialMessageObservationRepo.set(this.socialMessageObservations);
|
|
2290
2786
|
}
|
|
2787
|
+
async persistPrivateMessages() {
|
|
2788
|
+
this.privateMessagesPersistDirty = true;
|
|
2789
|
+
if (this.privateMessagesPersistTimer) {
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
this.privateMessagesPersistTimer = setTimeout(() => {
|
|
2793
|
+
this.flushPrivateMessagesPersist().catch(() => { });
|
|
2794
|
+
}, PRIVATE_MESSAGE_PERSIST_DEBOUNCE_MS);
|
|
2795
|
+
}
|
|
2796
|
+
async persistPrivateMessageReceipts() {
|
|
2797
|
+
this.privateMessageReceiptsPersistDirty = true;
|
|
2798
|
+
if (this.privateMessageReceiptsPersistTimer) {
|
|
2799
|
+
return;
|
|
2800
|
+
}
|
|
2801
|
+
this.privateMessageReceiptsPersistTimer = setTimeout(() => {
|
|
2802
|
+
this.flushPrivateMessageReceiptsPersist().catch(() => { });
|
|
2803
|
+
}, PRIVATE_MESSAGE_PERSIST_DEBOUNCE_MS);
|
|
2804
|
+
}
|
|
2805
|
+
async flushPrivatePersistence() {
|
|
2806
|
+
await Promise.all([
|
|
2807
|
+
this.flushPrivateMessagesPersist(),
|
|
2808
|
+
this.flushPrivateMessageReceiptsPersist(),
|
|
2809
|
+
]);
|
|
2810
|
+
}
|
|
2811
|
+
async flushPrivateMessagesPersist() {
|
|
2812
|
+
if (this.privateMessagesPersistTimer) {
|
|
2813
|
+
clearTimeout(this.privateMessagesPersistTimer);
|
|
2814
|
+
this.privateMessagesPersistTimer = null;
|
|
2815
|
+
}
|
|
2816
|
+
if (!this.privateMessagesPersistDirty) {
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
this.privateMessagesPersistDirty = false;
|
|
2820
|
+
await this.privateMessageRepo.set(this.privateMessages);
|
|
2821
|
+
}
|
|
2822
|
+
async flushPrivateMessageReceiptsPersist() {
|
|
2823
|
+
if (this.privateMessageReceiptsPersistTimer) {
|
|
2824
|
+
clearTimeout(this.privateMessageReceiptsPersistTimer);
|
|
2825
|
+
this.privateMessageReceiptsPersistTimer = null;
|
|
2826
|
+
}
|
|
2827
|
+
if (!this.privateMessageReceiptsPersistDirty) {
|
|
2828
|
+
return;
|
|
2829
|
+
}
|
|
2830
|
+
this.privateMessageReceiptsPersistDirty = false;
|
|
2831
|
+
await this.privateMessageReceiptRepo.set(this.privateMessageReceipts);
|
|
2832
|
+
}
|
|
2291
2833
|
async log(level, message) {
|
|
2292
2834
|
await this.logRepo.append({
|
|
2293
2835
|
level,
|
|
@@ -2334,6 +2876,7 @@ class LocalNodeService {
|
|
|
2334
2876
|
(0, core_1.verifyProfile)(profile, selfPublicKey));
|
|
2335
2877
|
return (0, core_1.buildPublicProfileSummary)({
|
|
2336
2878
|
profile,
|
|
2879
|
+
is_self: isSelf,
|
|
2337
2880
|
online,
|
|
2338
2881
|
last_seen_at: lastSeenAt || null,
|
|
2339
2882
|
network_mode: isSelf ? this.networkMode : "unknown",
|
|
@@ -2379,6 +2922,7 @@ class LocalNodeService {
|
|
|
2379
2922
|
updated_at: message.created_at,
|
|
2380
2923
|
signature: "",
|
|
2381
2924
|
},
|
|
2925
|
+
is_self: message.agent_id === this.identity?.agent_id,
|
|
2382
2926
|
online: false,
|
|
2383
2927
|
last_seen_at: null,
|
|
2384
2928
|
network_mode: "unknown",
|
|
@@ -2566,6 +3110,32 @@ class LocalNodeService {
|
|
|
2566
3110
|
.join("\n")
|
|
2567
3111
|
.trim();
|
|
2568
3112
|
}
|
|
3113
|
+
buildPrivateConversationId(leftAgentId, rightAgentId) {
|
|
3114
|
+
return [String(leftAgentId || "").trim(), String(rightAgentId || "").trim()].sort().join(":");
|
|
3115
|
+
}
|
|
3116
|
+
decryptPrivateMessageBody(message) {
|
|
3117
|
+
const cached = this.privateMessageBodyCache.get(message.message_id);
|
|
3118
|
+
if (typeof cached === "string") {
|
|
3119
|
+
return cached;
|
|
3120
|
+
}
|
|
3121
|
+
if (!this.privateEncryptionKeyPair) {
|
|
3122
|
+
return "[encrypted]";
|
|
3123
|
+
}
|
|
3124
|
+
const decrypted = (0, core_1.decryptPrivatePayload)({
|
|
3125
|
+
ciphertext: message.ciphertext,
|
|
3126
|
+
nonce: message.nonce,
|
|
3127
|
+
sender_encryption_public_key: message.sender_encryption_public_key,
|
|
3128
|
+
recipient_private_key: this.privateEncryptionKeyPair.private_key,
|
|
3129
|
+
}) || "[encrypted]";
|
|
3130
|
+
this.privateMessageBodyCache.set(message.message_id, decrypted);
|
|
3131
|
+
if (this.privateMessageBodyCache.size > PRIVATE_MESSAGE_HISTORY_LIMIT * 2) {
|
|
3132
|
+
const firstKey = this.privateMessageBodyCache.keys().next().value;
|
|
3133
|
+
if (firstKey) {
|
|
3134
|
+
this.privateMessageBodyCache.delete(firstKey);
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
return decrypted;
|
|
3138
|
+
}
|
|
2569
3139
|
normalizeWindowTimestamps(timestamps, windowMs, now = Date.now()) {
|
|
2570
3140
|
return timestamps.filter((timestamp) => now - timestamp <= windowMs);
|
|
2571
3141
|
}
|
|
@@ -2586,16 +3156,30 @@ class LocalNodeService {
|
|
|
2586
3156
|
hasSocialMessage(messageId) {
|
|
2587
3157
|
return this.socialMessages.some((item) => item.message_id === messageId);
|
|
2588
3158
|
}
|
|
2589
|
-
getReplayableSelfSocialMessages(now = Date.now()) {
|
|
3159
|
+
getReplayableSelfSocialMessages(reason = "manual", now = Date.now()) {
|
|
2590
3160
|
const maxCount = Math.max(0, SOCIAL_MESSAGE_REPLAY_MAX_PER_BROADCAST);
|
|
2591
3161
|
if (!this.identity || maxCount === 0) {
|
|
2592
3162
|
return [];
|
|
2593
3163
|
}
|
|
2594
|
-
|
|
3164
|
+
const replayable = this.socialMessages
|
|
2595
3165
|
.filter((item) => (item.agent_id === this.identity?.agent_id &&
|
|
2596
3166
|
now - item.created_at <= SOCIAL_MESSAGE_REPLAY_WINDOW_MS))
|
|
2597
3167
|
.sort((a, b) => a.created_at - b.created_at)
|
|
2598
3168
|
.slice(-maxCount);
|
|
3169
|
+
if (!replayable.length) {
|
|
3170
|
+
this.lastReplayBroadcastSignature = "";
|
|
3171
|
+
return [];
|
|
3172
|
+
}
|
|
3173
|
+
const signature = replayable.map((item) => item.message_id).join(",");
|
|
3174
|
+
const isIntervalReplay = reason === "interval";
|
|
3175
|
+
const changedSinceLastReplay = signature !== this.lastReplayBroadcastSignature;
|
|
3176
|
+
const refreshDue = now - this.lastReplayBroadcastAt >= SOCIAL_MESSAGE_REPLAY_REFRESH_INTERVAL_MS;
|
|
3177
|
+
if (isIntervalReplay && !changedSinceLastReplay && !refreshDue) {
|
|
3178
|
+
return [];
|
|
3179
|
+
}
|
|
3180
|
+
this.lastReplayBroadcastSignature = signature;
|
|
3181
|
+
this.lastReplayBroadcastAt = now;
|
|
3182
|
+
return replayable;
|
|
2599
3183
|
}
|
|
2600
3184
|
hasRecentDuplicateMessage(agentId, body, topic, now = Date.now()) {
|
|
2601
3185
|
return this.socialMessages.some((item) => (item.agent_id === agentId &&
|
|
@@ -2659,6 +3243,167 @@ class LocalNodeService {
|
|
|
2659
3243
|
await this.publish(SOCIAL_MESSAGE_OBSERVATION_TOPIC, observation);
|
|
2660
3244
|
await this.persistSocialMessageObservations();
|
|
2661
3245
|
}
|
|
3246
|
+
async sendPrivateMessageReceipt(message, replyPeerId) {
|
|
3247
|
+
if (!this.identity || typeof this.network.sendDirect !== "function" || !replyPeerId) {
|
|
3248
|
+
return;
|
|
3249
|
+
}
|
|
3250
|
+
const receipt = (0, core_1.signPrivateMessageReceipt)({
|
|
3251
|
+
identity: this.identity,
|
|
3252
|
+
receipt_id: (0, crypto_1.createHash)("sha256").update(`${message.message_id}:${this.identity.agent_id}:${Date.now()}`, "utf8").digest("hex"),
|
|
3253
|
+
message_id: message.message_id,
|
|
3254
|
+
conversation_id: message.conversation_id,
|
|
3255
|
+
to_agent_id: message.from_agent_id,
|
|
3256
|
+
status: "received",
|
|
3257
|
+
created_at: Date.now(),
|
|
3258
|
+
});
|
|
3259
|
+
this.ingestPrivateMessageReceipt(receipt);
|
|
3260
|
+
await this.network.sendDirect(replyPeerId, PRIVATE_MESSAGE_RECEIPT_TOPIC, receipt);
|
|
3261
|
+
await this.persistPrivateMessageReceipts();
|
|
3262
|
+
}
|
|
3263
|
+
normalizeIncomingPrivateMessage(value) {
|
|
3264
|
+
if (typeof value !== "object" || value === null) {
|
|
3265
|
+
return null;
|
|
3266
|
+
}
|
|
3267
|
+
const record = value;
|
|
3268
|
+
const createdAt = Number(record.created_at || 0);
|
|
3269
|
+
const fromAgentId = String(record.from_agent_id || "").trim();
|
|
3270
|
+
const toAgentId = String(record.to_agent_id || "").trim();
|
|
3271
|
+
const conversationId = String(record.conversation_id || "").trim();
|
|
3272
|
+
if (record.type !== PRIVATE_MESSAGE_TOPIC ||
|
|
3273
|
+
!String(record.message_id || "").trim() ||
|
|
3274
|
+
!conversationId ||
|
|
3275
|
+
!fromAgentId ||
|
|
3276
|
+
!toAgentId ||
|
|
3277
|
+
!String(record.sender_public_key || "").trim() ||
|
|
3278
|
+
!String(record.sender_encryption_public_key || "").trim() ||
|
|
3279
|
+
!String(record.recipient_encryption_public_key || "").trim() ||
|
|
3280
|
+
!String(record.ciphertext || "").trim() ||
|
|
3281
|
+
!String(record.nonce || "").trim() ||
|
|
3282
|
+
String(record.cipher_scheme || "") !== "nacl-box-v1" ||
|
|
3283
|
+
!String(record.signature || "").trim() ||
|
|
3284
|
+
!Number.isFinite(createdAt)) {
|
|
3285
|
+
return null;
|
|
3286
|
+
}
|
|
3287
|
+
if (fromAgentId === toAgentId) {
|
|
3288
|
+
return null;
|
|
3289
|
+
}
|
|
3290
|
+
if (conversationId !== this.buildPrivateConversationId(fromAgentId, toAgentId)) {
|
|
3291
|
+
return null;
|
|
3292
|
+
}
|
|
3293
|
+
return {
|
|
3294
|
+
type: PRIVATE_MESSAGE_TOPIC,
|
|
3295
|
+
message_id: String(record.message_id).trim(),
|
|
3296
|
+
conversation_id: conversationId,
|
|
3297
|
+
from_agent_id: fromAgentId,
|
|
3298
|
+
to_agent_id: toAgentId,
|
|
3299
|
+
sender_public_key: String(record.sender_public_key).trim(),
|
|
3300
|
+
sender_encryption_public_key: String(record.sender_encryption_public_key).trim(),
|
|
3301
|
+
recipient_encryption_public_key: String(record.recipient_encryption_public_key).trim(),
|
|
3302
|
+
cipher_scheme: "nacl-box-v1",
|
|
3303
|
+
ciphertext: String(record.ciphertext).trim(),
|
|
3304
|
+
nonce: String(record.nonce).trim(),
|
|
3305
|
+
created_at: createdAt,
|
|
3306
|
+
signature: String(record.signature).trim(),
|
|
3307
|
+
};
|
|
3308
|
+
}
|
|
3309
|
+
normalizePrivateMessages(items) {
|
|
3310
|
+
if (!Array.isArray(items)) {
|
|
3311
|
+
return [];
|
|
3312
|
+
}
|
|
3313
|
+
const deduped = new Set();
|
|
3314
|
+
return items
|
|
3315
|
+
.map((item) => this.normalizeIncomingPrivateMessage(item))
|
|
3316
|
+
.filter((item) => Boolean(item))
|
|
3317
|
+
.sort((a, b) => a.created_at - b.created_at)
|
|
3318
|
+
.filter((item) => {
|
|
3319
|
+
if (deduped.has(item.message_id)) {
|
|
3320
|
+
return false;
|
|
3321
|
+
}
|
|
3322
|
+
deduped.add(item.message_id);
|
|
3323
|
+
return true;
|
|
3324
|
+
})
|
|
3325
|
+
.slice(-PRIVATE_MESSAGE_HISTORY_LIMIT);
|
|
3326
|
+
}
|
|
3327
|
+
normalizeIncomingPrivateMessageReceipt(value) {
|
|
3328
|
+
if (typeof value !== "object" || value === null) {
|
|
3329
|
+
return null;
|
|
3330
|
+
}
|
|
3331
|
+
const record = value;
|
|
3332
|
+
const createdAt = Number(record.created_at || 0);
|
|
3333
|
+
const status = String(record.status || "").trim();
|
|
3334
|
+
if (record.type !== PRIVATE_MESSAGE_RECEIPT_TOPIC ||
|
|
3335
|
+
!String(record.receipt_id || "").trim() ||
|
|
3336
|
+
!String(record.message_id || "").trim() ||
|
|
3337
|
+
!String(record.conversation_id || "").trim() ||
|
|
3338
|
+
!String(record.from_agent_id || "").trim() ||
|
|
3339
|
+
!String(record.to_agent_id || "").trim() ||
|
|
3340
|
+
!String(record.sender_public_key || "").trim() ||
|
|
3341
|
+
(status !== "received" && status !== "read") ||
|
|
3342
|
+
!String(record.signature || "").trim() ||
|
|
3343
|
+
!Number.isFinite(createdAt)) {
|
|
3344
|
+
return null;
|
|
3345
|
+
}
|
|
3346
|
+
return {
|
|
3347
|
+
type: PRIVATE_MESSAGE_RECEIPT_TOPIC,
|
|
3348
|
+
receipt_id: String(record.receipt_id).trim(),
|
|
3349
|
+
message_id: String(record.message_id).trim(),
|
|
3350
|
+
conversation_id: String(record.conversation_id).trim(),
|
|
3351
|
+
from_agent_id: String(record.from_agent_id).trim(),
|
|
3352
|
+
to_agent_id: String(record.to_agent_id).trim(),
|
|
3353
|
+
sender_public_key: String(record.sender_public_key).trim(),
|
|
3354
|
+
status: status,
|
|
3355
|
+
created_at: createdAt,
|
|
3356
|
+
signature: String(record.signature).trim(),
|
|
3357
|
+
};
|
|
3358
|
+
}
|
|
3359
|
+
normalizePrivateMessageReceipts(items) {
|
|
3360
|
+
if (!Array.isArray(items)) {
|
|
3361
|
+
return [];
|
|
3362
|
+
}
|
|
3363
|
+
const deduped = new Set();
|
|
3364
|
+
return items
|
|
3365
|
+
.map((item) => this.normalizeIncomingPrivateMessageReceipt(item))
|
|
3366
|
+
.filter((item) => Boolean(item))
|
|
3367
|
+
.sort((a, b) => a.created_at - b.created_at)
|
|
3368
|
+
.filter((item) => {
|
|
3369
|
+
if (deduped.has(item.receipt_id)) {
|
|
3370
|
+
return false;
|
|
3371
|
+
}
|
|
3372
|
+
deduped.add(item.receipt_id);
|
|
3373
|
+
return true;
|
|
3374
|
+
})
|
|
3375
|
+
.slice(-PRIVATE_MESSAGE_RECEIPT_HISTORY_LIMIT);
|
|
3376
|
+
}
|
|
3377
|
+
hasPrivateMessage(messageId) {
|
|
3378
|
+
return this.privateMessages.some((item) => item.message_id === messageId);
|
|
3379
|
+
}
|
|
3380
|
+
ingestPrivateMessage(message) {
|
|
3381
|
+
const existing = this.privateMessages.findIndex((item) => item.message_id === message.message_id);
|
|
3382
|
+
if (existing >= 0) {
|
|
3383
|
+
this.privateMessages[existing] = message;
|
|
3384
|
+
}
|
|
3385
|
+
else {
|
|
3386
|
+
this.privateMessages.push(message);
|
|
3387
|
+
}
|
|
3388
|
+
this.privateMessages = this.normalizePrivateMessages(this.privateMessages);
|
|
3389
|
+
const validIds = new Set(this.privateMessages.map((item) => item.message_id));
|
|
3390
|
+
this.privateMessageBodyCache.delete(message.message_id);
|
|
3391
|
+
for (const key of Array.from(this.privateMessageBodyCache.keys())) {
|
|
3392
|
+
if (!validIds.has(key)) {
|
|
3393
|
+
this.privateMessageBodyCache.delete(key);
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
ingestPrivateMessageReceipt(receipt) {
|
|
3398
|
+
const existing = this.privateMessageReceipts.findIndex((item) => item.receipt_id === receipt.receipt_id);
|
|
3399
|
+
if (existing >= 0) {
|
|
3400
|
+
this.privateMessageReceipts[existing] = receipt;
|
|
3401
|
+
}
|
|
3402
|
+
else {
|
|
3403
|
+
this.privateMessageReceipts.push(receipt);
|
|
3404
|
+
}
|
|
3405
|
+
this.privateMessageReceipts = this.normalizePrivateMessageReceipts(this.privateMessageReceipts);
|
|
3406
|
+
}
|
|
2662
3407
|
normalizeIncomingSocialMessage(value) {
|
|
2663
3408
|
if (typeof value !== "object" || value === null) {
|
|
2664
3409
|
return null;
|
|
@@ -2905,6 +3650,36 @@ async function main() {
|
|
|
2905
3650
|
app.get("/api/runtime/paths", (_req, res) => {
|
|
2906
3651
|
sendOk(res, node.getRuntimePaths());
|
|
2907
3652
|
});
|
|
3653
|
+
app.get("/api/app/update-status", (_req, res) => {
|
|
3654
|
+
sendOk(res, node.getAppUpdateStatus());
|
|
3655
|
+
});
|
|
3656
|
+
app.post("/api/app/update", asyncRoute(async (_req, res) => {
|
|
3657
|
+
const status = node.getAppUpdateStatus();
|
|
3658
|
+
if (!status.update_available || !status.latest_version) {
|
|
3659
|
+
sendOk(res, {
|
|
3660
|
+
started: false,
|
|
3661
|
+
current_version: status.current_version,
|
|
3662
|
+
latest_version: status.latest_version,
|
|
3663
|
+
platform: status.platform,
|
|
3664
|
+
reason: status.check_error || "already_current",
|
|
3665
|
+
}, { message: "Already on the latest version" });
|
|
3666
|
+
return;
|
|
3667
|
+
}
|
|
3668
|
+
sendOk(res, {
|
|
3669
|
+
started: true,
|
|
3670
|
+
current_version: status.current_version,
|
|
3671
|
+
target_version: status.latest_version,
|
|
3672
|
+
platform: status.platform,
|
|
3673
|
+
}, { message: `Updating to ${status.latest_version}` });
|
|
3674
|
+
setTimeout(() => {
|
|
3675
|
+
try {
|
|
3676
|
+
node.startAppUpdate();
|
|
3677
|
+
}
|
|
3678
|
+
catch {
|
|
3679
|
+
// best effort after response has been sent
|
|
3680
|
+
}
|
|
3681
|
+
}, 150);
|
|
3682
|
+
}));
|
|
2908
3683
|
app.put("/api/profile", asyncRoute(async (req, res) => {
|
|
2909
3684
|
const body = req.body;
|
|
2910
3685
|
const tags = Array.isArray(body.tags)
|
|
@@ -3003,6 +3778,27 @@ async function main() {
|
|
|
3003
3778
|
const agentId = String(req.query.agent_id ?? "").trim();
|
|
3004
3779
|
sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null }));
|
|
3005
3780
|
});
|
|
3781
|
+
app.get("/api/private/state", (_req, res) => {
|
|
3782
|
+
sendOk(res, node.getPrivateMessagingState());
|
|
3783
|
+
});
|
|
3784
|
+
app.get("/api/private/conversations", (_req, res) => {
|
|
3785
|
+
sendOk(res, node.getPrivateConversations());
|
|
3786
|
+
});
|
|
3787
|
+
app.get("/api/private/messages", (req, res) => {
|
|
3788
|
+
const conversationId = String(req.query.conversation_id ?? "").trim();
|
|
3789
|
+
const limit = Number(req.query.limit ?? PRIVATE_MESSAGE_QUERY_LIMIT);
|
|
3790
|
+
sendOk(res, node.getPrivateMessages(conversationId, limit));
|
|
3791
|
+
});
|
|
3792
|
+
app.post("/api/private/messages/send", asyncRoute(async (req, res) => {
|
|
3793
|
+
const result = await node.sendPrivateMessage({
|
|
3794
|
+
to_agent_id: String(req.body?.to_agent_id || ""),
|
|
3795
|
+
recipient_encryption_public_key: String(req.body?.recipient_encryption_public_key || ""),
|
|
3796
|
+
body: String(req.body?.body || ""),
|
|
3797
|
+
});
|
|
3798
|
+
sendOk(res, result, {
|
|
3799
|
+
message: result.sent ? "Private message sent" : `Private message skipped: ${result.reason}`,
|
|
3800
|
+
});
|
|
3801
|
+
}));
|
|
3006
3802
|
app.get("/api/openclaw/bridge", (_req, res) => {
|
|
3007
3803
|
sendOk(res, node.getOpenClawBridgeStatus());
|
|
3008
3804
|
});
|