@silicaclaw/cli 2026.3.20-12 → 2026.3.20-14
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 +12 -0
- package/VERSION +1 -1
- package/apps/local-console/dist/apps/local-console/src/server.d.ts +7 -2
- package/apps/local-console/dist/apps/local-console/src/server.js +86 -30
- package/apps/local-console/public/app/app.js +137 -6
- package/apps/local-console/public/app/social.js +29 -2
- package/apps/local-console/public/app/styles.css +3 -2
- package/apps/local-console/public/app/template.js +108 -77
- package/apps/local-console/public/app/translations.js +6 -0
- package/apps/local-console/src/server.ts +82 -25
- package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
- package/openclaw-skills/silicaclaw-broadcast/manifest.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## v1.0 beta - 2026-03-20
|
|
4
4
|
|
|
5
|
+
### 2026.3.20-14
|
|
6
|
+
|
|
7
|
+
- release build:
|
|
8
|
+
- prepared another fresh latest-channel package build without publishing
|
|
9
|
+
- regenerated the npm tarball through the verified release packing workflow
|
|
10
|
+
|
|
11
|
+
### 2026.3.20-13
|
|
12
|
+
|
|
13
|
+
- release build:
|
|
14
|
+
- prepared another fresh latest-channel package build without publishing
|
|
15
|
+
- regenerated the npm tarball through the verified release packing workflow
|
|
16
|
+
|
|
5
17
|
### 2026.3.20-12
|
|
6
18
|
|
|
7
19
|
- release build:
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v2026.3.20-
|
|
1
|
+
v2026.3.20-14
|
|
@@ -38,7 +38,7 @@ type PrivateMessageView = {
|
|
|
38
38
|
body: string;
|
|
39
39
|
created_at: number;
|
|
40
40
|
is_self: boolean;
|
|
41
|
-
delivery_status: "sent" | "received" | "read";
|
|
41
|
+
delivery_status: "sent" | "direct-sent" | "fallback-sent" | "received" | "read";
|
|
42
42
|
};
|
|
43
43
|
type RuntimeMessageGovernance = SocialMessageGovernanceConfig;
|
|
44
44
|
type OpenClawBridgeStatus = {
|
|
@@ -181,6 +181,7 @@ export declare class LocalNodeService {
|
|
|
181
181
|
private privateEncryptionKeyPair;
|
|
182
182
|
private privatePeerRoutes;
|
|
183
183
|
private privateMessageBodyCache;
|
|
184
|
+
private privateMessageDeliveryStatusCache;
|
|
184
185
|
private messageGovernance;
|
|
185
186
|
private privateMessagesPersistDirty;
|
|
186
187
|
private privateMessageReceiptsPersistDirty;
|
|
@@ -622,6 +623,7 @@ export declare class LocalNodeService {
|
|
|
622
623
|
getIdentity(): AgentIdentity | null;
|
|
623
624
|
getSocialMessages(limit?: number, options?: {
|
|
624
625
|
agent_id?: string | null;
|
|
626
|
+
offset?: number | null;
|
|
625
627
|
}): {
|
|
626
628
|
total: number;
|
|
627
629
|
items: SocialMessageView[];
|
|
@@ -655,7 +657,10 @@ export declare class LocalNodeService {
|
|
|
655
657
|
last_message_at: number | null;
|
|
656
658
|
unread_count: number;
|
|
657
659
|
}>;
|
|
658
|
-
getPrivateMessages(conversationId: string, limit?: number):
|
|
660
|
+
getPrivateMessages(conversationId: string, limit?: number, offset?: number): {
|
|
661
|
+
total: number;
|
|
662
|
+
items: PrivateMessageView[];
|
|
663
|
+
};
|
|
659
664
|
sendPrivateMessage(input: {
|
|
660
665
|
to_agent_id: string;
|
|
661
666
|
recipient_encryption_public_key: string;
|
|
@@ -787,6 +787,7 @@ class LocalNodeService {
|
|
|
787
787
|
privateEncryptionKeyPair = null;
|
|
788
788
|
privatePeerRoutes = {};
|
|
789
789
|
privateMessageBodyCache = new Map();
|
|
790
|
+
privateMessageDeliveryStatusCache = new Map();
|
|
790
791
|
messageGovernance;
|
|
791
792
|
privateMessagesPersistDirty = false;
|
|
792
793
|
privateMessageReceiptsPersistDirty = false;
|
|
@@ -1579,6 +1580,7 @@ class LocalNodeService {
|
|
|
1579
1580
|
}
|
|
1580
1581
|
getSocialMessages(limit = 50, options) {
|
|
1581
1582
|
const resolvedLimit = Math.max(1, Math.min(200, Number(limit) || 50));
|
|
1583
|
+
const resolvedOffset = Math.max(0, Number(options?.offset) || 0);
|
|
1582
1584
|
this.ensureLocalDirectoryBaseline();
|
|
1583
1585
|
this.compactCacheInMemory();
|
|
1584
1586
|
const agentId = String(options?.agent_id || "").trim();
|
|
@@ -1587,7 +1589,7 @@ class LocalNodeService {
|
|
|
1587
1589
|
: this.socialMessages;
|
|
1588
1590
|
return {
|
|
1589
1591
|
total: filtered.length,
|
|
1590
|
-
items: filtered.slice(
|
|
1592
|
+
items: filtered.slice(resolvedOffset, resolvedOffset + resolvedLimit).map((message) => {
|
|
1591
1593
|
const profile = this.directory.profiles[message.agent_id];
|
|
1592
1594
|
const lastSeenAt = this.directory.presence[message.agent_id] ?? 0;
|
|
1593
1595
|
const observations = this.socialMessageObservations.filter((item) => item.message_id === message.message_id);
|
|
@@ -1649,11 +1651,12 @@ class LocalNodeService {
|
|
|
1649
1651
|
}
|
|
1650
1652
|
return Array.from(conversations.values()).sort((a, b) => (b.last_message_at || 0) - (a.last_message_at || 0));
|
|
1651
1653
|
}
|
|
1652
|
-
getPrivateMessages(conversationId, limit = PRIVATE_MESSAGE_QUERY_LIMIT) {
|
|
1654
|
+
getPrivateMessages(conversationId, limit = PRIVATE_MESSAGE_QUERY_LIMIT, offset = 0) {
|
|
1653
1655
|
const normalizedConversationId = String(conversationId || "").trim();
|
|
1654
1656
|
const resolvedLimit = Math.max(1, Math.min(PRIVATE_MESSAGE_QUERY_LIMIT, Number(limit) || PRIVATE_MESSAGE_QUERY_LIMIT));
|
|
1657
|
+
const resolvedOffset = Math.max(0, Number(offset) || 0);
|
|
1655
1658
|
const receiptsByMessageId = new Map(this.privateMessageReceipts.map((receipt) => [receipt.message_id, receipt.status]));
|
|
1656
|
-
|
|
1659
|
+
const filtered = this.privateMessages
|
|
1657
1660
|
.filter((message) => {
|
|
1658
1661
|
if (message.from_agent_id === message.to_agent_id) {
|
|
1659
1662
|
return false;
|
|
@@ -1664,18 +1667,24 @@ class LocalNodeService {
|
|
|
1664
1667
|
}
|
|
1665
1668
|
return !normalizedConversationId || message.conversation_id === normalizedConversationId;
|
|
1666
1669
|
})
|
|
1667
|
-
.sort((a, b) => a.created_at - b.created_at)
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1670
|
+
.sort((a, b) => a.created_at - b.created_at);
|
|
1671
|
+
const total = filtered.length;
|
|
1672
|
+
const paged = filtered.slice(Math.max(0, total - resolvedOffset - resolvedLimit), Math.max(0, total - resolvedOffset));
|
|
1673
|
+
return {
|
|
1674
|
+
total,
|
|
1675
|
+
items: paged.map((message) => ({
|
|
1676
|
+
message_id: message.message_id,
|
|
1677
|
+
conversation_id: message.conversation_id,
|
|
1678
|
+
from_agent_id: message.from_agent_id,
|
|
1679
|
+
to_agent_id: message.to_agent_id,
|
|
1680
|
+
body: this.decryptPrivateMessageBody(message),
|
|
1681
|
+
created_at: message.created_at,
|
|
1682
|
+
is_self: message.from_agent_id === this.identity?.agent_id,
|
|
1683
|
+
delivery_status: receiptsByMessageId.get(message.message_id) ||
|
|
1684
|
+
this.privateMessageDeliveryStatusCache.get(message.message_id) ||
|
|
1685
|
+
(message.from_agent_id === this.identity?.agent_id ? "fallback-sent" : "sent"),
|
|
1686
|
+
})),
|
|
1687
|
+
};
|
|
1679
1688
|
}
|
|
1680
1689
|
async sendPrivateMessage(input) {
|
|
1681
1690
|
if (!this.identity || !this.privateEncryptionKeyPair) {
|
|
@@ -1688,12 +1697,9 @@ class LocalNodeService {
|
|
|
1688
1697
|
return { sent: false, reason: "self_private_message_not_allowed" };
|
|
1689
1698
|
}
|
|
1690
1699
|
const toPeerId = this.privatePeerRoutes[toAgentId] || "";
|
|
1691
|
-
if (!toAgentId || !
|
|
1700
|
+
if (!toAgentId || !recipientKey || !body) {
|
|
1692
1701
|
return { sent: false, reason: "invalid_private_message_input" };
|
|
1693
1702
|
}
|
|
1694
|
-
if (typeof this.network.sendDirect !== "function") {
|
|
1695
|
-
return { sent: false, reason: "direct_delivery_not_supported" };
|
|
1696
|
-
}
|
|
1697
1703
|
const encrypted = (0, core_1.encryptPrivatePayload)({
|
|
1698
1704
|
plaintext: body,
|
|
1699
1705
|
recipient_public_key: recipientKey,
|
|
@@ -1710,11 +1716,28 @@ class LocalNodeService {
|
|
|
1710
1716
|
nonce: encrypted.nonce,
|
|
1711
1717
|
created_at: Date.now(),
|
|
1712
1718
|
});
|
|
1719
|
+
this.privateMessageBodyCache.set(message.message_id, body);
|
|
1713
1720
|
this.ingestPrivateMessage(message);
|
|
1714
1721
|
await this.persistPrivateMessages();
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1722
|
+
let reason = "fallback-sent";
|
|
1723
|
+
if (toPeerId && typeof this.network.sendDirect === "function") {
|
|
1724
|
+
try {
|
|
1725
|
+
await this.network.sendDirect(toPeerId, PRIVATE_MESSAGE_TOPIC, message);
|
|
1726
|
+
reason = "direct-sent";
|
|
1727
|
+
}
|
|
1728
|
+
catch {
|
|
1729
|
+
await this.publish(PRIVATE_MESSAGE_TOPIC, message);
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
else {
|
|
1733
|
+
await this.publish(PRIVATE_MESSAGE_TOPIC, message);
|
|
1734
|
+
}
|
|
1735
|
+
this.privateMessageDeliveryStatusCache.set(message.message_id, reason);
|
|
1736
|
+
const view = this.getPrivateMessages(message.conversation_id, PRIVATE_MESSAGE_QUERY_LIMIT, 0).items.find((item) => item.message_id === message.message_id);
|
|
1737
|
+
if (view) {
|
|
1738
|
+
view.delivery_status = reason;
|
|
1739
|
+
}
|
|
1740
|
+
return { sent: true, reason, message: view };
|
|
1718
1741
|
}
|
|
1719
1742
|
getOpenClawBridgeStatus() {
|
|
1720
1743
|
const now = Date.now();
|
|
@@ -2555,6 +2578,12 @@ class LocalNodeService {
|
|
|
2555
2578
|
this.network.subscribe(SOCIAL_MESSAGE_OBSERVATION_TOPIC, (data, meta) => {
|
|
2556
2579
|
this.onMessage(SOCIAL_MESSAGE_OBSERVATION_TOPIC, data, meta);
|
|
2557
2580
|
});
|
|
2581
|
+
this.network.subscribe(PRIVATE_MESSAGE_TOPIC, (data, meta) => {
|
|
2582
|
+
this.onDirectMessage(PRIVATE_MESSAGE_TOPIC, data, meta);
|
|
2583
|
+
});
|
|
2584
|
+
this.network.subscribe(PRIVATE_MESSAGE_RECEIPT_TOPIC, (data, meta) => {
|
|
2585
|
+
this.onDirectMessage(PRIVATE_MESSAGE_RECEIPT_TOPIC, data, meta);
|
|
2586
|
+
});
|
|
2558
2587
|
if (typeof this.network.subscribeDirect === "function") {
|
|
2559
2588
|
this.network.subscribeDirect(PRIVATE_MESSAGE_TOPIC, (data, meta) => {
|
|
2560
2589
|
this.onDirectMessage(PRIVATE_MESSAGE_TOPIC, data, meta);
|
|
@@ -3257,7 +3286,12 @@ class LocalNodeService {
|
|
|
3257
3286
|
created_at: Date.now(),
|
|
3258
3287
|
});
|
|
3259
3288
|
this.ingestPrivateMessageReceipt(receipt);
|
|
3260
|
-
|
|
3289
|
+
try {
|
|
3290
|
+
await this.network.sendDirect(replyPeerId, PRIVATE_MESSAGE_RECEIPT_TOPIC, receipt);
|
|
3291
|
+
}
|
|
3292
|
+
catch {
|
|
3293
|
+
await this.publish(PRIVATE_MESSAGE_RECEIPT_TOPIC, receipt);
|
|
3294
|
+
}
|
|
3261
3295
|
await this.persistPrivateMessageReceipts();
|
|
3262
3296
|
}
|
|
3263
3297
|
normalizeIncomingPrivateMessage(value) {
|
|
@@ -3387,12 +3421,19 @@ class LocalNodeService {
|
|
|
3387
3421
|
}
|
|
3388
3422
|
this.privateMessages = this.normalizePrivateMessages(this.privateMessages);
|
|
3389
3423
|
const validIds = new Set(this.privateMessages.map((item) => item.message_id));
|
|
3390
|
-
|
|
3424
|
+
if (message.from_agent_id !== this.identity?.agent_id) {
|
|
3425
|
+
this.privateMessageBodyCache.delete(message.message_id);
|
|
3426
|
+
}
|
|
3391
3427
|
for (const key of Array.from(this.privateMessageBodyCache.keys())) {
|
|
3392
3428
|
if (!validIds.has(key)) {
|
|
3393
3429
|
this.privateMessageBodyCache.delete(key);
|
|
3394
3430
|
}
|
|
3395
3431
|
}
|
|
3432
|
+
for (const key of Array.from(this.privateMessageDeliveryStatusCache.keys())) {
|
|
3433
|
+
if (!validIds.has(key)) {
|
|
3434
|
+
this.privateMessageDeliveryStatusCache.delete(key);
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3396
3437
|
}
|
|
3397
3438
|
ingestPrivateMessageReceipt(receipt) {
|
|
3398
3439
|
const existing = this.privateMessageReceipts.findIndex((item) => item.receipt_id === receipt.receipt_id);
|
|
@@ -3403,6 +3444,7 @@ class LocalNodeService {
|
|
|
3403
3444
|
this.privateMessageReceipts.push(receipt);
|
|
3404
3445
|
}
|
|
3405
3446
|
this.privateMessageReceipts = this.normalizePrivateMessageReceipts(this.privateMessageReceipts);
|
|
3447
|
+
this.privateMessageDeliveryStatusCache.set(receipt.message_id, receipt.status);
|
|
3406
3448
|
}
|
|
3407
3449
|
normalizeIncomingSocialMessage(value) {
|
|
3408
3450
|
if (typeof value !== "object" || value === null) {
|
|
@@ -3678,7 +3720,7 @@ async function main() {
|
|
|
3678
3720
|
catch {
|
|
3679
3721
|
// best effort after response has been sent
|
|
3680
3722
|
}
|
|
3681
|
-
},
|
|
3723
|
+
}, 1200);
|
|
3682
3724
|
}));
|
|
3683
3725
|
app.put("/api/profile", asyncRoute(async (req, res) => {
|
|
3684
3726
|
const body = req.body;
|
|
@@ -3775,8 +3817,9 @@ async function main() {
|
|
|
3775
3817
|
}));
|
|
3776
3818
|
app.get("/api/messages", (req, res) => {
|
|
3777
3819
|
const limit = Number(req.query.limit ?? 50);
|
|
3820
|
+
const offset = Number(req.query.offset ?? 0);
|
|
3778
3821
|
const agentId = String(req.query.agent_id ?? "").trim();
|
|
3779
|
-
sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null }));
|
|
3822
|
+
sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null, offset }));
|
|
3780
3823
|
});
|
|
3781
3824
|
app.get("/api/private/state", (_req, res) => {
|
|
3782
3825
|
sendOk(res, node.getPrivateMessagingState());
|
|
@@ -3787,7 +3830,8 @@ async function main() {
|
|
|
3787
3830
|
app.get("/api/private/messages", (req, res) => {
|
|
3788
3831
|
const conversationId = String(req.query.conversation_id ?? "").trim();
|
|
3789
3832
|
const limit = Number(req.query.limit ?? PRIVATE_MESSAGE_QUERY_LIMIT);
|
|
3790
|
-
|
|
3833
|
+
const offset = Number(req.query.offset ?? 0);
|
|
3834
|
+
sendOk(res, node.getPrivateMessages(conversationId, limit, offset));
|
|
3791
3835
|
});
|
|
3792
3836
|
app.post("/api/private/messages/send", asyncRoute(async (req, res) => {
|
|
3793
3837
|
const result = await node.sendPrivateMessage({
|
|
@@ -3796,7 +3840,11 @@ async function main() {
|
|
|
3796
3840
|
body: String(req.body?.body || ""),
|
|
3797
3841
|
});
|
|
3798
3842
|
sendOk(res, result, {
|
|
3799
|
-
message: result.sent
|
|
3843
|
+
message: result.sent
|
|
3844
|
+
? (result.reason === "direct-sent"
|
|
3845
|
+
? "Private message sent directly"
|
|
3846
|
+
: "Private message sent via encrypted fallback")
|
|
3847
|
+
: `Private message skipped: ${result.reason}`,
|
|
3800
3848
|
});
|
|
3801
3849
|
}));
|
|
3802
3850
|
app.get("/api/openclaw/bridge", (_req, res) => {
|
|
@@ -3810,8 +3858,9 @@ async function main() {
|
|
|
3810
3858
|
});
|
|
3811
3859
|
app.get("/api/openclaw/bridge/messages", (req, res) => {
|
|
3812
3860
|
const limit = Number(req.query.limit ?? 50);
|
|
3861
|
+
const offset = Number(req.query.offset ?? 0);
|
|
3813
3862
|
const agentId = String(req.query.agent_id ?? "").trim();
|
|
3814
|
-
sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null }));
|
|
3863
|
+
sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null, offset }));
|
|
3815
3864
|
});
|
|
3816
3865
|
app.post("/api/openclaw/bridge/message", asyncRoute(async (req, res) => {
|
|
3817
3866
|
const body = String(req.body?.body || "");
|
|
@@ -3927,9 +3976,16 @@ async function main() {
|
|
|
3927
3976
|
let html = (0, fs_1.readFileSync)(staticIndexFile, "utf8");
|
|
3928
3977
|
html = html.replace("</body>", `${renderBootstrapScript(payload)}\n</body>`);
|
|
3929
3978
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
3979
|
+
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
|
|
3930
3980
|
res.send(html);
|
|
3931
3981
|
});
|
|
3932
|
-
app.use(express_1.default.static(staticDir
|
|
3982
|
+
app.use(express_1.default.static(staticDir, {
|
|
3983
|
+
etag: false,
|
|
3984
|
+
lastModified: false,
|
|
3985
|
+
setHeaders: (res) => {
|
|
3986
|
+
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
|
|
3987
|
+
},
|
|
3988
|
+
}));
|
|
3933
3989
|
app.use((error, _req, res, _next) => {
|
|
3934
3990
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3935
3991
|
sendError(res, 500, "INTERNAL_ERROR", message);
|
|
@@ -28,6 +28,7 @@ if (!root) {
|
|
|
28
28
|
}
|
|
29
29
|
root.innerHTML = appTemplate;
|
|
30
30
|
const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
31
|
+
const PAGING_STATE_STORAGE_KEY = 'silicaclaw_ui_paging_state';
|
|
31
32
|
|
|
32
33
|
const i18n = createI18n(TRANSLATIONS);
|
|
33
34
|
const DEFAULT_LOCALE = i18n.DEFAULT_LOCALE;
|
|
@@ -52,7 +53,9 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
52
53
|
summary.setAttribute('data-i18n-closed-label', t('labels.show'));
|
|
53
54
|
summary.setAttribute('data-i18n-open-label', t('labels.hide'));
|
|
54
55
|
});
|
|
55
|
-
setText('.nav-section__label', t('common.
|
|
56
|
+
setText('.nav-section__label', t('common.workspace'), 0);
|
|
57
|
+
setText('.nav-section__label', t('common.messages'), 1);
|
|
58
|
+
setText('.nav-section__label', t('common.networkGroup'), 2);
|
|
56
59
|
setText('[data-tab="overview"] .tab-title', t('pageMeta.overview.title'));
|
|
57
60
|
setText('[data-tab="overview"] .tab-copy', t('labels.overviewTabCopy'));
|
|
58
61
|
setText('[data-tab="agent"] .tab-title', t('pageMeta.agent.title'));
|
|
@@ -120,6 +123,12 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
120
123
|
document.getElementById('privateTargetIdLabel').textContent = t('social.agentId');
|
|
121
124
|
document.getElementById('privateMessageSendBtn').textContent = t('actions.sendPrivateMessage');
|
|
122
125
|
document.getElementById('privateRefreshBtn').textContent = t('actions.refreshPrivate');
|
|
126
|
+
document.getElementById('socialMessagePrevPageBtn').textContent = t('overview.prevPage');
|
|
127
|
+
document.getElementById('socialMessageNextPageBtn').textContent = t('overview.nextPage');
|
|
128
|
+
document.getElementById('privateConversationPrevPageBtn').textContent = t('overview.prevPage');
|
|
129
|
+
document.getElementById('privateConversationNextPageBtn').textContent = t('overview.nextPage');
|
|
130
|
+
document.getElementById('privateMessagePrevPageBtn').textContent = t('overview.prevPage');
|
|
131
|
+
document.getElementById('privateMessageNextPageBtn').textContent = t('overview.nextPage');
|
|
123
132
|
document.getElementById('chatFeedHint').textContent = t('hints.chatFeedHint');
|
|
124
133
|
document.getElementById('overviewGuideTitle').textContent = t('overview.guideTitle');
|
|
125
134
|
document.getElementById('overviewGuideBody').textContent = t('overview.guideBody');
|
|
@@ -552,6 +561,7 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
552
561
|
let activeTab = 'overview';
|
|
553
562
|
let logsCache = [];
|
|
554
563
|
let socialMessagesCache = [];
|
|
564
|
+
let socialMessagePage = 1;
|
|
555
565
|
let logLevelFilter = 'all';
|
|
556
566
|
let socialTemplate = '';
|
|
557
567
|
let socialModeDirty = false;
|
|
@@ -562,6 +572,11 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
562
572
|
let privateState = null;
|
|
563
573
|
let privateConversations = [];
|
|
564
574
|
let privateMessages = [];
|
|
575
|
+
let privateConversationPage = 1;
|
|
576
|
+
const PRIVATE_CONVERSATION_PAGE_SIZE = 12;
|
|
577
|
+
let privateMessagesTotal = 0;
|
|
578
|
+
let privateMessagePage = 1;
|
|
579
|
+
const PRIVATE_MESSAGE_PAGE_SIZE = 20;
|
|
565
580
|
let privateTarget = null;
|
|
566
581
|
let selectedPrivateConversationId = '';
|
|
567
582
|
let overviewMode = 'lan';
|
|
@@ -570,6 +585,29 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
570
585
|
const AGENTS_PAGE_SIZE = 10;
|
|
571
586
|
const pageMeta = TRANSLATIONS[currentLocale].pageMeta || TRANSLATIONS[DEFAULT_LOCALE].pageMeta;
|
|
572
587
|
|
|
588
|
+
function loadPagingState() {
|
|
589
|
+
try {
|
|
590
|
+
const raw = localStorage.getItem(PAGING_STATE_STORAGE_KEY);
|
|
591
|
+
if (!raw) return null;
|
|
592
|
+
return JSON.parse(raw);
|
|
593
|
+
} catch {
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function savePagingState() {
|
|
599
|
+
try {
|
|
600
|
+
localStorage.setItem(PAGING_STATE_STORAGE_KEY, JSON.stringify({
|
|
601
|
+
socialMessagePage,
|
|
602
|
+
privateConversationPage,
|
|
603
|
+
privateMessagePage,
|
|
604
|
+
selectedPrivateConversationId,
|
|
605
|
+
}));
|
|
606
|
+
} catch {
|
|
607
|
+
// ignore localStorage failures
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
573
611
|
async function api(path, options = {}) {
|
|
574
612
|
const res = await fetch(path, { headers: { 'Content-Type': 'application/json' }, ...options });
|
|
575
613
|
const json = await res.json().catch(() => null);
|
|
@@ -692,13 +730,24 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
692
730
|
}
|
|
693
731
|
|
|
694
732
|
function renderPrivate() {
|
|
733
|
+
const privateDeliveryLabel = (status) => {
|
|
734
|
+
if (status === 'direct-sent') return 'Direct';
|
|
735
|
+
if (status === 'fallback-sent') return 'Fallback';
|
|
736
|
+
if (status === 'received') return 'Received';
|
|
737
|
+
if (status === 'read') return 'Read';
|
|
738
|
+
return 'Sent';
|
|
739
|
+
};
|
|
695
740
|
document.getElementById('privateStateMeta').textContent = privateState?.enabled
|
|
696
741
|
? `${privateConversations.length} conversation(s)`
|
|
697
742
|
: 'Private messaging unavailable';
|
|
698
743
|
document.getElementById('privateTargetName').value = privateTarget?.display_name || '';
|
|
699
744
|
document.getElementById('privateTargetAgentId').value = privateTarget?.agent_id || '';
|
|
700
|
-
|
|
701
|
-
|
|
745
|
+
const conversationTotalPages = Math.max(1, Math.ceil((privateConversations.length || 0) / PRIVATE_CONVERSATION_PAGE_SIZE));
|
|
746
|
+
const conversationCurrentPage = Math.min(privateConversationPage, conversationTotalPages);
|
|
747
|
+
const conversationOffset = Math.max(0, (conversationCurrentPage - 1) * PRIVATE_CONVERSATION_PAGE_SIZE);
|
|
748
|
+
const visibleConversations = privateConversations.slice(conversationOffset, conversationOffset + PRIVATE_CONVERSATION_PAGE_SIZE);
|
|
749
|
+
document.getElementById('privateConversationList').innerHTML = visibleConversations.length
|
|
750
|
+
? visibleConversations.map((item) => `
|
|
702
751
|
<button class="agent-card" type="button" data-private-conversation="${escapeHtml(item.conversation_id)}">
|
|
703
752
|
<div class="agent-card__avatar-fallback">${escapeHtml(((item.peer_display_name || item.peer_agent_id || '?')[0] || '?').toUpperCase())}</div>
|
|
704
753
|
<div class="agent-card__main">
|
|
@@ -710,13 +759,19 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
710
759
|
</button>
|
|
711
760
|
`).join('')
|
|
712
761
|
: `<div class="empty-state">No private conversations yet.</div>`;
|
|
762
|
+
document.getElementById('privateConversationPageMeta').textContent = t('overview.pageStatus', {
|
|
763
|
+
page: String(conversationCurrentPage),
|
|
764
|
+
total: String(conversationTotalPages),
|
|
765
|
+
});
|
|
766
|
+
document.getElementById('privateConversationPrevPageBtn').disabled = conversationCurrentPage <= 1;
|
|
767
|
+
document.getElementById('privateConversationNextPageBtn').disabled = conversationCurrentPage >= conversationTotalPages;
|
|
713
768
|
document.getElementById('privateMessageList').innerHTML = privateMessages.length
|
|
714
769
|
? privateMessages.map((item) => `
|
|
715
770
|
<div class="log-item">
|
|
716
771
|
<div style="display:flex; align-items:center; justify-content:space-between; gap:12px;">
|
|
717
772
|
<div>
|
|
718
773
|
<strong>${item.is_self ? 'Me' : escapeHtml(privateTarget?.display_name || item.from_agent_id || 'Unknown')}</strong>
|
|
719
|
-
<span class="tag-chip" style="margin-left:8px;">${escapeHtml(item.delivery_status)}</span>
|
|
774
|
+
<span class="tag-chip" style="margin-left:8px;">${escapeHtml(privateDeliveryLabel(item.delivery_status))}</span>
|
|
720
775
|
</div>
|
|
721
776
|
<div class="mono" style="color:#90a2c3;">${new Date(item.created_at).toLocaleString()}</div>
|
|
722
777
|
</div>
|
|
@@ -724,6 +779,14 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
724
779
|
</div>
|
|
725
780
|
`).join('')
|
|
726
781
|
: `<div class="empty-state">No private messages yet.</div>`;
|
|
782
|
+
const totalPages = Math.max(1, Math.ceil((privateMessagesTotal || 0) / PRIVATE_MESSAGE_PAGE_SIZE));
|
|
783
|
+
const currentPage = Math.min(privateMessagePage, totalPages);
|
|
784
|
+
document.getElementById('privateMessagePageMeta').textContent = t('overview.pageStatus', {
|
|
785
|
+
page: String(currentPage),
|
|
786
|
+
total: String(totalPages),
|
|
787
|
+
});
|
|
788
|
+
document.getElementById('privateMessagePrevPageBtn').disabled = currentPage <= 1;
|
|
789
|
+
document.getElementById('privateMessageNextPageBtn').disabled = currentPage >= totalPages;
|
|
727
790
|
}
|
|
728
791
|
|
|
729
792
|
async function refreshPrivate() {
|
|
@@ -733,6 +796,8 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
733
796
|
]);
|
|
734
797
|
privateState = stateRes.data || null;
|
|
735
798
|
privateConversations = Array.isArray(conversationsRes.data) ? conversationsRes.data : [];
|
|
799
|
+
const conversationTotalPages = Math.max(1, Math.ceil((privateConversations.length || 0) / PRIVATE_CONVERSATION_PAGE_SIZE));
|
|
800
|
+
privateConversationPage = Math.min(privateConversationPage, conversationTotalPages);
|
|
736
801
|
if ((!privateTarget || privateTarget.agent_id === privateState?.agent_id) && privateConversations[0]) {
|
|
737
802
|
const first = privateConversations[0];
|
|
738
803
|
privateTarget = {
|
|
@@ -751,16 +816,22 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
751
816
|
};
|
|
752
817
|
}
|
|
753
818
|
if (selectedPrivateConversationId) {
|
|
754
|
-
const
|
|
755
|
-
|
|
819
|
+
const offset = Math.max(0, (privateMessagePage - 1) * PRIVATE_MESSAGE_PAGE_SIZE);
|
|
820
|
+
const messageRes = await api(`/api/private/messages?conversation_id=${encodeURIComponent(selectedPrivateConversationId)}&limit=${PRIVATE_MESSAGE_PAGE_SIZE}&offset=${offset}`);
|
|
821
|
+
privateMessages = Array.isArray(messageRes.data?.items) ? messageRes.data.items : [];
|
|
822
|
+
privateMessagesTotal = Number(messageRes.data?.total || 0);
|
|
756
823
|
} else {
|
|
757
824
|
privateMessages = [];
|
|
825
|
+
privateMessagesTotal = 0;
|
|
758
826
|
}
|
|
759
827
|
renderPrivate();
|
|
760
828
|
}
|
|
761
829
|
|
|
762
830
|
const renderSocialMessages = socialController.renderSocialMessages;
|
|
763
831
|
const refreshMessages = socialController.refreshMessages;
|
|
832
|
+
const nextSocialMessagesPage = socialController.nextSocialMessagesPage;
|
|
833
|
+
const prevSocialMessagesPage = socialController.prevSocialMessagesPage;
|
|
834
|
+
const setSocialMessagesPage = socialController.setSocialMessagesPage;
|
|
764
835
|
|
|
765
836
|
const refreshNetwork = networkController.refreshNetwork;
|
|
766
837
|
const refreshPeers = networkController.refreshPeers;
|
|
@@ -863,6 +934,9 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
863
934
|
display_name: String(button.getAttribute('data-private-name') || ''),
|
|
864
935
|
private_encryption_public_key: String(button.getAttribute('data-private-key') || ''),
|
|
865
936
|
};
|
|
937
|
+
privateConversationPage = 1;
|
|
938
|
+
privateMessagePage = 1;
|
|
939
|
+
savePagingState();
|
|
866
940
|
selectedPrivateConversationId = [privateState?.agent_id || '', privateTarget.agent_id].sort().join(':');
|
|
867
941
|
switchTab('private');
|
|
868
942
|
});
|
|
@@ -879,6 +953,8 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
879
953
|
private_encryption_public_key: selectedConversation.peer_public_key,
|
|
880
954
|
};
|
|
881
955
|
}
|
|
956
|
+
privateMessagePage = 1;
|
|
957
|
+
savePagingState();
|
|
882
958
|
await refreshPrivate();
|
|
883
959
|
});
|
|
884
960
|
|
|
@@ -886,6 +962,36 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
886
962
|
await refreshPrivate();
|
|
887
963
|
});
|
|
888
964
|
|
|
965
|
+
document.getElementById('privateConversationPrevPageBtn').addEventListener('click', async () => {
|
|
966
|
+
if (privateConversationPage <= 1) return;
|
|
967
|
+
privateConversationPage -= 1;
|
|
968
|
+
savePagingState();
|
|
969
|
+
renderPrivate();
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
document.getElementById('privateConversationNextPageBtn').addEventListener('click', async () => {
|
|
973
|
+
const totalPages = Math.max(1, Math.ceil((privateConversations.length || 0) / PRIVATE_CONVERSATION_PAGE_SIZE));
|
|
974
|
+
if (privateConversationPage >= totalPages) return;
|
|
975
|
+
privateConversationPage += 1;
|
|
976
|
+
savePagingState();
|
|
977
|
+
renderPrivate();
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
document.getElementById('privateMessagePrevPageBtn').addEventListener('click', async () => {
|
|
981
|
+
if (privateMessagePage <= 1) return;
|
|
982
|
+
privateMessagePage -= 1;
|
|
983
|
+
savePagingState();
|
|
984
|
+
await refreshPrivate();
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
document.getElementById('privateMessageNextPageBtn').addEventListener('click', async () => {
|
|
988
|
+
const totalPages = Math.max(1, Math.ceil((privateMessagesTotal || 0) / PRIVATE_MESSAGE_PAGE_SIZE));
|
|
989
|
+
if (privateMessagePage >= totalPages) return;
|
|
990
|
+
privateMessagePage += 1;
|
|
991
|
+
savePagingState();
|
|
992
|
+
await refreshPrivate();
|
|
993
|
+
});
|
|
994
|
+
|
|
889
995
|
document.getElementById('privateMessageSendBtn').addEventListener('click', async () => {
|
|
890
996
|
const body = String(document.getElementById('privateMessageInput').value || '').trim();
|
|
891
997
|
if (!privateTarget?.agent_id || !privateTarget?.private_encryption_public_key) {
|
|
@@ -912,12 +1018,37 @@ const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
|
912
1018
|
});
|
|
913
1019
|
document.getElementById('privateMessageInput').value = '';
|
|
914
1020
|
setFeedback('privateFeedback', result.meta?.message || 'Private message sent.');
|
|
1021
|
+
privateMessagePage = 1;
|
|
1022
|
+
savePagingState();
|
|
915
1023
|
await refreshPrivate();
|
|
916
1024
|
} catch (error) {
|
|
917
1025
|
setFeedback('privateFeedback', error instanceof Error ? error.message : 'Private message failed.', 'error');
|
|
918
1026
|
}
|
|
919
1027
|
});
|
|
920
1028
|
|
|
1029
|
+
document.getElementById('socialMessagePrevPageBtn').addEventListener('click', async () => {
|
|
1030
|
+
prevSocialMessagesPage();
|
|
1031
|
+
socialMessagePage = Math.max(1, socialMessagePage - 1);
|
|
1032
|
+
savePagingState();
|
|
1033
|
+
await refreshMessages();
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
document.getElementById('socialMessageNextPageBtn').addEventListener('click', async () => {
|
|
1037
|
+
nextSocialMessagesPage();
|
|
1038
|
+
socialMessagePage += 1;
|
|
1039
|
+
savePagingState();
|
|
1040
|
+
await refreshMessages();
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
const persistedPagingState = loadPagingState();
|
|
1044
|
+
if (persistedPagingState && typeof persistedPagingState === 'object') {
|
|
1045
|
+
socialMessagePage = Math.max(1, Number(persistedPagingState.socialMessagePage) || 1);
|
|
1046
|
+
privateConversationPage = Math.max(1, Number(persistedPagingState.privateConversationPage) || 1);
|
|
1047
|
+
privateMessagePage = Math.max(1, Number(persistedPagingState.privateMessagePage) || 1);
|
|
1048
|
+
selectedPrivateConversationId = String(persistedPagingState.selectedPrivateConversationId || '').trim();
|
|
1049
|
+
setSocialMessagesPage(socialMessagePage);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
921
1052
|
applyTheme(localStorage.getItem('silicaclaw_theme_mode') || 'dark');
|
|
922
1053
|
hydrateCachedShell();
|
|
923
1054
|
document.getElementById('brandUpdateBtn').addEventListener('click', () => {
|
|
@@ -26,8 +26,11 @@ export function createSocialController({
|
|
|
26
26
|
}) {
|
|
27
27
|
const SKILLS_SECTION_LIMIT = 4;
|
|
28
28
|
const SKILLS_DIALOGUE_LIMIT = 1;
|
|
29
|
+
const SOCIAL_MESSAGE_PAGE_SIZE = 20;
|
|
29
30
|
let lastMessagesRenderKey = "";
|
|
30
31
|
let lastLogsRenderKey = "";
|
|
32
|
+
let socialMessagesPage = 1;
|
|
33
|
+
let socialMessagesTotal = 0;
|
|
31
34
|
const sectionRenderCache = new Map();
|
|
32
35
|
let skillsQuery = "";
|
|
33
36
|
let skillsFilter = "all";
|
|
@@ -165,6 +168,18 @@ export function createSocialController({
|
|
|
165
168
|
seconds: String(Math.floor((governance.send_limit?.window_ms || 60000) / 1000)),
|
|
166
169
|
})}`
|
|
167
170
|
: t("overview.messageHint");
|
|
171
|
+
const totalPages = Math.max(1, Math.ceil((socialMessagesTotal || 0) / SOCIAL_MESSAGE_PAGE_SIZE));
|
|
172
|
+
const currentPage = Math.min(socialMessagesPage, totalPages);
|
|
173
|
+
const updatePager = () => {
|
|
174
|
+
const pageMetaEl = document.getElementById("socialMessagePageMeta");
|
|
175
|
+
const prevBtn = document.getElementById("socialMessagePrevPageBtn");
|
|
176
|
+
const nextBtn = document.getElementById("socialMessageNextPageBtn");
|
|
177
|
+
if (pageMetaEl) {
|
|
178
|
+
pageMetaEl.textContent = t("overview.pageStatus", { page: String(currentPage), total: String(totalPages) });
|
|
179
|
+
}
|
|
180
|
+
if (prevBtn) prevBtn.disabled = currentPage <= 1;
|
|
181
|
+
if (nextBtn) nextBtn.disabled = currentPage >= totalPages;
|
|
182
|
+
};
|
|
168
183
|
if (!socialMessagesCache.length) {
|
|
169
184
|
const nextMeta = t("overview.noMessagesMeta");
|
|
170
185
|
const nextHtml = `<div class="empty-state">${t("overview.noMessagesEmpty")}</div>`;
|
|
@@ -173,6 +188,7 @@ export function createSocialController({
|
|
|
173
188
|
hintEl.textContent = governanceHint;
|
|
174
189
|
metaEl.textContent = nextMeta;
|
|
175
190
|
listEl.innerHTML = nextHtml;
|
|
191
|
+
updatePager();
|
|
176
192
|
lastMessagesRenderKey = renderKey;
|
|
177
193
|
}
|
|
178
194
|
return;
|
|
@@ -194,7 +210,6 @@ export function createSocialController({
|
|
|
194
210
|
})}`
|
|
195
211
|
: "";
|
|
196
212
|
const nextMeta = `${baseMeta}${governanceMeta}`;
|
|
197
|
-
|
|
198
213
|
if (!filteredMessages.length) {
|
|
199
214
|
const nextHtml = `<div class="empty-state">${t("overview.noMessagesEmpty")}</div>`;
|
|
200
215
|
const renderKey = JSON.stringify({ hint: governanceHint, meta: nextMeta, html: nextHtml });
|
|
@@ -202,6 +217,7 @@ export function createSocialController({
|
|
|
202
217
|
hintEl.textContent = governanceHint;
|
|
203
218
|
metaEl.textContent = nextMeta;
|
|
204
219
|
listEl.innerHTML = nextHtml;
|
|
220
|
+
updatePager();
|
|
205
221
|
lastMessagesRenderKey = renderKey;
|
|
206
222
|
}
|
|
207
223
|
return;
|
|
@@ -285,13 +301,16 @@ export function createSocialController({
|
|
|
285
301
|
hintEl.textContent = governanceHint;
|
|
286
302
|
metaEl.textContent = nextMeta;
|
|
287
303
|
listEl.innerHTML = nextHtml;
|
|
304
|
+
updatePager();
|
|
288
305
|
lastMessagesRenderKey = renderKey;
|
|
289
306
|
}
|
|
290
307
|
|
|
291
308
|
async function refreshMessages() {
|
|
292
|
-
const
|
|
309
|
+
const offset = Math.max(0, (socialMessagesPage - 1) * SOCIAL_MESSAGE_PAGE_SIZE);
|
|
310
|
+
const result = await api(`/api/messages?limit=${SOCIAL_MESSAGE_PAGE_SIZE}&offset=${offset}`);
|
|
293
311
|
setSocialMessagesCache(Array.isArray(result.data?.items) ? result.data.items : []);
|
|
294
312
|
setSocialMessageGovernance(result.data?.governance || null);
|
|
313
|
+
socialMessagesTotal = Number(result.data?.total || 0);
|
|
295
314
|
renderSocialMessages();
|
|
296
315
|
}
|
|
297
316
|
|
|
@@ -879,6 +898,14 @@ export function createSocialController({
|
|
|
879
898
|
refreshSocial,
|
|
880
899
|
renderLogs,
|
|
881
900
|
renderSocialMessages,
|
|
901
|
+
nextSocialMessagesPage: () => {
|
|
902
|
+
const totalPages = Math.max(1, Math.ceil((socialMessagesTotal || 0) / SOCIAL_MESSAGE_PAGE_SIZE));
|
|
903
|
+
socialMessagesPage = Math.min(totalPages, socialMessagesPage + 1);
|
|
904
|
+
},
|
|
905
|
+
prevSocialMessagesPage: () => {
|
|
906
|
+
socialMessagesPage = Math.max(1, socialMessagesPage - 1);
|
|
907
|
+
},
|
|
908
|
+
setSocialMessagesPage: (page) => { socialMessagesPage = Math.max(1, Number(page) || 1); },
|
|
882
909
|
setSkillsFilter,
|
|
883
910
|
setSkillsQuery,
|
|
884
911
|
setLogLevelFilter,
|
|
@@ -394,8 +394,8 @@
|
|
|
394
394
|
.nav-section {
|
|
395
395
|
display: grid;
|
|
396
396
|
align-content: start;
|
|
397
|
-
gap:
|
|
398
|
-
margin-bottom:
|
|
397
|
+
gap: 8px;
|
|
398
|
+
margin-bottom: 14px;
|
|
399
399
|
}
|
|
400
400
|
.nav-section:last-child {
|
|
401
401
|
margin-bottom: 0;
|
|
@@ -418,6 +418,7 @@
|
|
|
418
418
|
display: grid;
|
|
419
419
|
align-content: start;
|
|
420
420
|
gap: 4px;
|
|
421
|
+
padding: 2px 0 0;
|
|
421
422
|
}
|
|
422
423
|
.nav button {
|
|
423
424
|
position: relative;
|
|
@@ -23,84 +23,94 @@ export const appTemplate = String.raw`<div class="app" id="appShell">
|
|
|
23
23
|
<div class="sidebar-shell__body">
|
|
24
24
|
<nav class="sidebar-nav nav">
|
|
25
25
|
<section class="nav-section">
|
|
26
|
-
<div class="nav-section__label">
|
|
26
|
+
<div class="nav-section__label">Workspace</div>
|
|
27
27
|
<div class="nav-section__items">
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
</
|
|
60
|
-
<
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
<
|
|
98
|
-
<
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
28
|
+
<button class="tab nav-item active" data-tab="overview">
|
|
29
|
+
<span class="tab-icon" aria-hidden="true">
|
|
30
|
+
<svg viewBox="0 0 24 24">
|
|
31
|
+
<line x1="12" x2="12" y1="20" y2="10"></line>
|
|
32
|
+
<line x1="18" x2="18" y1="20" y2="4"></line>
|
|
33
|
+
<line x1="6" x2="6" y1="20" y2="16"></line>
|
|
34
|
+
</svg>
|
|
35
|
+
</span>
|
|
36
|
+
<span class="tab-labels"><span class="tab-title">Overview</span><span class="tab-copy">Agent health, visibility, and next steps</span></span>
|
|
37
|
+
</button>
|
|
38
|
+
<button class="tab nav-item" data-tab="profile">
|
|
39
|
+
<span class="tab-icon" aria-hidden="true">
|
|
40
|
+
<svg viewBox="0 0 24 24">
|
|
41
|
+
<path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
|
|
42
|
+
<polyline points="14 2 14 8 20 8"></polyline>
|
|
43
|
+
<line x1="16" x2="8" y1="13" y2="13"></line>
|
|
44
|
+
<line x1="16" x2="8" y1="17" y2="17"></line>
|
|
45
|
+
<line x1="10" x2="8" y1="9" y2="9"></line>
|
|
46
|
+
</svg>
|
|
47
|
+
</span>
|
|
48
|
+
<span class="tab-labels"><span class="tab-title">Profile</span><span class="tab-copy">Public card, visibility, and saved identity</span></span>
|
|
49
|
+
</button>
|
|
50
|
+
<button class="tab nav-item" data-tab="skills">
|
|
51
|
+
<span class="tab-icon" aria-hidden="true">
|
|
52
|
+
<svg viewBox="0 0 24 24">
|
|
53
|
+
<path d="M12 3l2.5 5 5.5.8-4 3.9.9 5.5-4.9-2.6-4.9 2.6.9-5.5-4-3.9 5.5-.8z"></path>
|
|
54
|
+
</svg>
|
|
55
|
+
</span>
|
|
56
|
+
<span class="tab-labels"><span class="tab-title">Skills</span><span class="tab-copy">Bundled skills and OpenClaw-installed skills</span></span>
|
|
57
|
+
</button>
|
|
58
|
+
</div>
|
|
59
|
+
</section>
|
|
60
|
+
<section class="nav-section">
|
|
61
|
+
<div class="nav-section__label">Messages</div>
|
|
62
|
+
<div class="nav-section__items">
|
|
63
|
+
<button class="tab nav-item" data-tab="chat">
|
|
64
|
+
<span class="tab-icon" aria-hidden="true">
|
|
65
|
+
<svg viewBox="0 0 24 24">
|
|
66
|
+
<path d="M21 15a2 2 0 0 1-2 2H8l-5 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
|
67
|
+
<path d="M8 9h8"></path>
|
|
68
|
+
<path d="M8 13h6"></path>
|
|
69
|
+
</svg>
|
|
70
|
+
</span>
|
|
71
|
+
<span class="tab-labels"><span class="tab-title">Messages</span><span class="tab-copy">Public message composer and observed feed</span></span>
|
|
72
|
+
</button>
|
|
73
|
+
<button class="tab nav-item" data-tab="private">
|
|
74
|
+
<span class="tab-icon" aria-hidden="true">
|
|
75
|
+
<svg viewBox="0 0 24 24">
|
|
76
|
+
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
|
77
|
+
<rect x="4" y="11" width="16" height="10" rx="2"></rect>
|
|
78
|
+
<circle cx="12" cy="16" r="1"></circle>
|
|
79
|
+
</svg>
|
|
80
|
+
</span>
|
|
81
|
+
<span class="tab-labels"><span class="tab-title">Private</span><span class="tab-copy">Private messages between visible agents</span></span>
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
</section>
|
|
85
|
+
<section class="nav-section">
|
|
86
|
+
<div class="nav-section__label">Network</div>
|
|
87
|
+
<div class="nav-section__items">
|
|
88
|
+
<button class="tab nav-item" data-tab="agent">
|
|
89
|
+
<span class="tab-icon" aria-hidden="true">
|
|
90
|
+
<svg viewBox="0 0 24 24">
|
|
91
|
+
<circle cx="12" cy="8" r="4"></circle>
|
|
92
|
+
<path d="M5 20c1.6-3.8 4.2-5.7 7-5.7s5.4 1.9 7 5.7"></path>
|
|
93
|
+
</svg>
|
|
94
|
+
</span>
|
|
95
|
+
<span class="tab-labels"><span class="tab-title">Agents</span><span class="tab-copy">Discovered public agents and live directory</span></span>
|
|
96
|
+
</button>
|
|
97
|
+
<button class="tab nav-item" data-tab="network">
|
|
98
|
+
<span class="tab-icon" aria-hidden="true">
|
|
99
|
+
<svg viewBox="0 0 24 24">
|
|
100
|
+
<circle cx="12" cy="12" r="2"></circle>
|
|
101
|
+
<path d="M16.24 7.76a6 6 0 0 1 0 8.49m-8.48-.01a6 6 0 0 1 0-8.49m11.31-2.82a10 10 0 0 1 0 14.14m-14.14 0a10 10 0 0 1 0-14.14"></path>
|
|
102
|
+
</svg>
|
|
103
|
+
</span>
|
|
104
|
+
<span class="tab-labels"><span class="tab-title">Network</span><span class="tab-copy">Broadcast controls, peers, and diagnostics</span></span>
|
|
105
|
+
</button>
|
|
106
|
+
<button class="tab nav-item" data-tab="social">
|
|
107
|
+
<span class="tab-icon" aria-hidden="true">
|
|
108
|
+
<svg viewBox="0 0 24 24">
|
|
109
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
|
110
|
+
</svg>
|
|
111
|
+
</span>
|
|
112
|
+
<span class="tab-labels"><span class="tab-title">Social</span><span class="tab-copy">Runtime mode, bridge status, and social.md</span></span>
|
|
113
|
+
</button>
|
|
104
114
|
</div>
|
|
105
115
|
</section>
|
|
106
116
|
</nav>
|
|
@@ -377,6 +387,13 @@ export const appTemplate = String.raw`<div class="app" id="appShell">
|
|
|
377
387
|
</div>
|
|
378
388
|
</div>
|
|
379
389
|
<div class="logs" id="socialMessageList"></div>
|
|
390
|
+
<div class="agent-list__footer">
|
|
391
|
+
<div class="agent-list__page" id="socialMessagePageMeta">Page 1 / 1</div>
|
|
392
|
+
<div class="agent-list__pager">
|
|
393
|
+
<button class="secondary" type="button" id="socialMessagePrevPageBtn">Prev</button>
|
|
394
|
+
<button class="secondary" type="button" id="socialMessageNextPageBtn">Next</button>
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
380
397
|
</div>
|
|
381
398
|
</div>
|
|
382
399
|
</div>
|
|
@@ -406,6 +423,13 @@ export const appTemplate = String.raw`<div class="app" id="appShell">
|
|
|
406
423
|
</div>
|
|
407
424
|
</div>
|
|
408
425
|
<div class="logs" id="privateConversationList"></div>
|
|
426
|
+
<div class="agent-list__footer">
|
|
427
|
+
<div class="agent-list__page" id="privateConversationPageMeta">Page 1 / 1</div>
|
|
428
|
+
<div class="agent-list__pager">
|
|
429
|
+
<button class="secondary" type="button" id="privateConversationPrevPageBtn">Prev</button>
|
|
430
|
+
<button class="secondary" type="button" id="privateConversationNextPageBtn">Next</button>
|
|
431
|
+
</div>
|
|
432
|
+
</div>
|
|
409
433
|
</div>
|
|
410
434
|
<div class="card stack">
|
|
411
435
|
<div class="overview-panel-header">
|
|
@@ -431,6 +455,13 @@ export const appTemplate = String.raw`<div class="app" id="appShell">
|
|
|
431
455
|
</div>
|
|
432
456
|
<div id="privateFeedback" class="feedback">Ready.</div>
|
|
433
457
|
<div class="logs" id="privateMessageList"></div>
|
|
458
|
+
<div class="agent-list__footer">
|
|
459
|
+
<div class="agent-list__page" id="privateMessagePageMeta">Page 1 / 1</div>
|
|
460
|
+
<div class="agent-list__pager">
|
|
461
|
+
<button class="secondary" type="button" id="privateMessagePrevPageBtn">Prev</button>
|
|
462
|
+
<button class="secondary" type="button" id="privateMessageNextPageBtn">Next</button>
|
|
463
|
+
</div>
|
|
464
|
+
</div>
|
|
434
465
|
</div>
|
|
435
466
|
</div>
|
|
436
467
|
</div>
|
|
@@ -8,6 +8,9 @@ export const TRANSLATIONS = {
|
|
|
8
8
|
},
|
|
9
9
|
common: {
|
|
10
10
|
control: 'Control',
|
|
11
|
+
workspace: 'Workspace',
|
|
12
|
+
messages: 'Messages',
|
|
13
|
+
networkGroup: 'Network',
|
|
11
14
|
version: 'Version',
|
|
12
15
|
localConsole: 'Local Console',
|
|
13
16
|
subtitle: 'Manage identity, discovery, and broadcasts',
|
|
@@ -626,6 +629,9 @@ export const TRANSLATIONS = {
|
|
|
626
629
|
},
|
|
627
630
|
common: {
|
|
628
631
|
control: '控制',
|
|
632
|
+
workspace: '工作区',
|
|
633
|
+
messages: '消息',
|
|
634
|
+
networkGroup: '网络',
|
|
629
635
|
version: '版本',
|
|
630
636
|
localConsole: '本地控制台',
|
|
631
637
|
subtitle: '管理身份、发现与广播',
|
|
@@ -899,7 +899,7 @@ type PrivateMessageView = {
|
|
|
899
899
|
body: string;
|
|
900
900
|
created_at: number;
|
|
901
901
|
is_self: boolean;
|
|
902
|
-
delivery_status: "sent" | "received" | "read";
|
|
902
|
+
delivery_status: "sent" | "direct-sent" | "fallback-sent" | "received" | "read";
|
|
903
903
|
};
|
|
904
904
|
|
|
905
905
|
type RuntimeMessageGovernance = SocialMessageGovernanceConfig;
|
|
@@ -1047,6 +1047,7 @@ export class LocalNodeService {
|
|
|
1047
1047
|
private privateEncryptionKeyPair: PrivateEncryptionKeyPair | null = null;
|
|
1048
1048
|
private privatePeerRoutes: Record<string, string> = {};
|
|
1049
1049
|
private privateMessageBodyCache = new Map<string, string>();
|
|
1050
|
+
private privateMessageDeliveryStatusCache = new Map<string, PrivateMessageView["delivery_status"]>();
|
|
1050
1051
|
private messageGovernance: RuntimeMessageGovernance;
|
|
1051
1052
|
private privateMessagesPersistDirty = false;
|
|
1052
1053
|
private privateMessageReceiptsPersistDirty = false;
|
|
@@ -1898,7 +1899,7 @@ export class LocalNodeService {
|
|
|
1898
1899
|
return this.identity;
|
|
1899
1900
|
}
|
|
1900
1901
|
|
|
1901
|
-
getSocialMessages(limit = 50, options?: { agent_id?: string | null }): {
|
|
1902
|
+
getSocialMessages(limit = 50, options?: { agent_id?: string | null; offset?: number | null }): {
|
|
1902
1903
|
total: number;
|
|
1903
1904
|
items: SocialMessageView[];
|
|
1904
1905
|
governance: {
|
|
@@ -1910,6 +1911,7 @@ export class LocalNodeService {
|
|
|
1910
1911
|
};
|
|
1911
1912
|
} {
|
|
1912
1913
|
const resolvedLimit = Math.max(1, Math.min(200, Number(limit) || 50));
|
|
1914
|
+
const resolvedOffset = Math.max(0, Number(options?.offset) || 0);
|
|
1913
1915
|
this.ensureLocalDirectoryBaseline();
|
|
1914
1916
|
this.compactCacheInMemory();
|
|
1915
1917
|
const agentId = String(options?.agent_id || "").trim();
|
|
@@ -1918,7 +1920,7 @@ export class LocalNodeService {
|
|
|
1918
1920
|
: this.socialMessages;
|
|
1919
1921
|
return {
|
|
1920
1922
|
total: filtered.length,
|
|
1921
|
-
items: filtered.slice(
|
|
1923
|
+
items: filtered.slice(resolvedOffset, resolvedOffset + resolvedLimit).map((message) => {
|
|
1922
1924
|
const profile = this.directory.profiles[message.agent_id];
|
|
1923
1925
|
const lastSeenAt = this.directory.presence[message.agent_id] ?? 0;
|
|
1924
1926
|
const observations = this.socialMessageObservations.filter((item) => item.message_id === message.message_id);
|
|
@@ -1999,13 +2001,17 @@ export class LocalNodeService {
|
|
|
1999
2001
|
return Array.from(conversations.values()).sort((a, b) => (b.last_message_at || 0) - (a.last_message_at || 0));
|
|
2000
2002
|
}
|
|
2001
2003
|
|
|
2002
|
-
getPrivateMessages(conversationId: string, limit = PRIVATE_MESSAGE_QUERY_LIMIT):
|
|
2004
|
+
getPrivateMessages(conversationId: string, limit = PRIVATE_MESSAGE_QUERY_LIMIT, offset = 0): {
|
|
2005
|
+
total: number;
|
|
2006
|
+
items: PrivateMessageView[];
|
|
2007
|
+
} {
|
|
2003
2008
|
const normalizedConversationId = String(conversationId || "").trim();
|
|
2004
2009
|
const resolvedLimit = Math.max(1, Math.min(PRIVATE_MESSAGE_QUERY_LIMIT, Number(limit) || PRIVATE_MESSAGE_QUERY_LIMIT));
|
|
2010
|
+
const resolvedOffset = Math.max(0, Number(offset) || 0);
|
|
2005
2011
|
const receiptsByMessageId = new Map(
|
|
2006
2012
|
this.privateMessageReceipts.map((receipt) => [receipt.message_id, receipt.status] as const)
|
|
2007
2013
|
);
|
|
2008
|
-
|
|
2014
|
+
const filtered = this.privateMessages
|
|
2009
2015
|
.filter((message) => {
|
|
2010
2016
|
if (message.from_agent_id === message.to_agent_id) {
|
|
2011
2017
|
return false;
|
|
@@ -2016,9 +2022,12 @@ export class LocalNodeService {
|
|
|
2016
2022
|
}
|
|
2017
2023
|
return !normalizedConversationId || message.conversation_id === normalizedConversationId;
|
|
2018
2024
|
})
|
|
2019
|
-
.sort((a, b) => a.created_at - b.created_at)
|
|
2020
|
-
|
|
2021
|
-
|
|
2025
|
+
.sort((a, b) => a.created_at - b.created_at);
|
|
2026
|
+
const total = filtered.length;
|
|
2027
|
+
const paged = filtered.slice(Math.max(0, total - resolvedOffset - resolvedLimit), Math.max(0, total - resolvedOffset));
|
|
2028
|
+
return {
|
|
2029
|
+
total,
|
|
2030
|
+
items: paged.map((message) => ({
|
|
2022
2031
|
message_id: message.message_id,
|
|
2023
2032
|
conversation_id: message.conversation_id,
|
|
2024
2033
|
from_agent_id: message.from_agent_id,
|
|
@@ -2026,8 +2035,12 @@ export class LocalNodeService {
|
|
|
2026
2035
|
body: this.decryptPrivateMessageBody(message),
|
|
2027
2036
|
created_at: message.created_at,
|
|
2028
2037
|
is_self: message.from_agent_id === this.identity?.agent_id,
|
|
2029
|
-
delivery_status:
|
|
2030
|
-
|
|
2038
|
+
delivery_status:
|
|
2039
|
+
receiptsByMessageId.get(message.message_id) ||
|
|
2040
|
+
this.privateMessageDeliveryStatusCache.get(message.message_id) ||
|
|
2041
|
+
(message.from_agent_id === this.identity?.agent_id ? "fallback-sent" : "sent"),
|
|
2042
|
+
})),
|
|
2043
|
+
};
|
|
2031
2044
|
}
|
|
2032
2045
|
|
|
2033
2046
|
async sendPrivateMessage(input: {
|
|
@@ -2045,12 +2058,9 @@ export class LocalNodeService {
|
|
|
2045
2058
|
return { sent: false, reason: "self_private_message_not_allowed" };
|
|
2046
2059
|
}
|
|
2047
2060
|
const toPeerId = this.privatePeerRoutes[toAgentId] || "";
|
|
2048
|
-
if (!toAgentId || !
|
|
2061
|
+
if (!toAgentId || !recipientKey || !body) {
|
|
2049
2062
|
return { sent: false, reason: "invalid_private_message_input" };
|
|
2050
2063
|
}
|
|
2051
|
-
if (typeof this.network.sendDirect !== "function") {
|
|
2052
|
-
return { sent: false, reason: "direct_delivery_not_supported" };
|
|
2053
|
-
}
|
|
2054
2064
|
const encrypted = encryptPrivatePayload({
|
|
2055
2065
|
plaintext: body,
|
|
2056
2066
|
recipient_public_key: recipientKey,
|
|
@@ -2067,11 +2077,26 @@ export class LocalNodeService {
|
|
|
2067
2077
|
nonce: encrypted.nonce,
|
|
2068
2078
|
created_at: Date.now(),
|
|
2069
2079
|
});
|
|
2080
|
+
this.privateMessageBodyCache.set(message.message_id, body);
|
|
2070
2081
|
this.ingestPrivateMessage(message);
|
|
2071
2082
|
await this.persistPrivateMessages();
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2083
|
+
let reason = "fallback-sent";
|
|
2084
|
+
if (toPeerId && typeof this.network.sendDirect === "function") {
|
|
2085
|
+
try {
|
|
2086
|
+
await this.network.sendDirect(toPeerId, PRIVATE_MESSAGE_TOPIC, message);
|
|
2087
|
+
reason = "direct-sent";
|
|
2088
|
+
} catch {
|
|
2089
|
+
await this.publish(PRIVATE_MESSAGE_TOPIC, message);
|
|
2090
|
+
}
|
|
2091
|
+
} else {
|
|
2092
|
+
await this.publish(PRIVATE_MESSAGE_TOPIC, message);
|
|
2093
|
+
}
|
|
2094
|
+
this.privateMessageDeliveryStatusCache.set(message.message_id, reason as PrivateMessageView["delivery_status"]);
|
|
2095
|
+
const view = this.getPrivateMessages(message.conversation_id, PRIVATE_MESSAGE_QUERY_LIMIT, 0).items.find((item) => item.message_id === message.message_id);
|
|
2096
|
+
if (view) {
|
|
2097
|
+
view.delivery_status = reason as PrivateMessageView["delivery_status"];
|
|
2098
|
+
}
|
|
2099
|
+
return { sent: true, reason, message: view };
|
|
2075
2100
|
}
|
|
2076
2101
|
|
|
2077
2102
|
getOpenClawBridgeStatus(): OpenClawBridgeStatus {
|
|
@@ -3006,6 +3031,12 @@ export class LocalNodeService {
|
|
|
3006
3031
|
this.network.subscribe(SOCIAL_MESSAGE_OBSERVATION_TOPIC, (data: SocialMessageObservationRecord, meta?: { peerId?: string }) => {
|
|
3007
3032
|
this.onMessage(SOCIAL_MESSAGE_OBSERVATION_TOPIC, data, meta);
|
|
3008
3033
|
});
|
|
3034
|
+
this.network.subscribe(PRIVATE_MESSAGE_TOPIC, (data: PrivateMessageRecord, meta?: { peerId?: string }) => {
|
|
3035
|
+
this.onDirectMessage(PRIVATE_MESSAGE_TOPIC, data, meta);
|
|
3036
|
+
});
|
|
3037
|
+
this.network.subscribe(PRIVATE_MESSAGE_RECEIPT_TOPIC, (data: PrivateMessageReceiptRecord, meta?: { peerId?: string }) => {
|
|
3038
|
+
this.onDirectMessage(PRIVATE_MESSAGE_RECEIPT_TOPIC, data, meta);
|
|
3039
|
+
});
|
|
3009
3040
|
if (typeof this.network.subscribeDirect === "function") {
|
|
3010
3041
|
this.network.subscribeDirect(PRIVATE_MESSAGE_TOPIC, (data: PrivateMessageRecord, meta?: { peerId?: string }) => {
|
|
3011
3042
|
this.onDirectMessage(PRIVATE_MESSAGE_TOPIC, data, meta);
|
|
@@ -3799,7 +3830,11 @@ export class LocalNodeService {
|
|
|
3799
3830
|
created_at: Date.now(),
|
|
3800
3831
|
});
|
|
3801
3832
|
this.ingestPrivateMessageReceipt(receipt);
|
|
3802
|
-
|
|
3833
|
+
try {
|
|
3834
|
+
await this.network.sendDirect(replyPeerId, PRIVATE_MESSAGE_RECEIPT_TOPIC, receipt);
|
|
3835
|
+
} catch {
|
|
3836
|
+
await this.publish(PRIVATE_MESSAGE_RECEIPT_TOPIC, receipt);
|
|
3837
|
+
}
|
|
3803
3838
|
await this.persistPrivateMessageReceipts();
|
|
3804
3839
|
}
|
|
3805
3840
|
|
|
@@ -3938,12 +3973,19 @@ export class LocalNodeService {
|
|
|
3938
3973
|
}
|
|
3939
3974
|
this.privateMessages = this.normalizePrivateMessages(this.privateMessages);
|
|
3940
3975
|
const validIds = new Set(this.privateMessages.map((item) => item.message_id));
|
|
3941
|
-
|
|
3976
|
+
if (message.from_agent_id !== this.identity?.agent_id) {
|
|
3977
|
+
this.privateMessageBodyCache.delete(message.message_id);
|
|
3978
|
+
}
|
|
3942
3979
|
for (const key of Array.from(this.privateMessageBodyCache.keys())) {
|
|
3943
3980
|
if (!validIds.has(key)) {
|
|
3944
3981
|
this.privateMessageBodyCache.delete(key);
|
|
3945
3982
|
}
|
|
3946
3983
|
}
|
|
3984
|
+
for (const key of Array.from(this.privateMessageDeliveryStatusCache.keys())) {
|
|
3985
|
+
if (!validIds.has(key)) {
|
|
3986
|
+
this.privateMessageDeliveryStatusCache.delete(key);
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3947
3989
|
}
|
|
3948
3990
|
|
|
3949
3991
|
private ingestPrivateMessageReceipt(receipt: PrivateMessageReceiptRecord): void {
|
|
@@ -3954,6 +3996,7 @@ export class LocalNodeService {
|
|
|
3954
3996
|
this.privateMessageReceipts.push(receipt);
|
|
3955
3997
|
}
|
|
3956
3998
|
this.privateMessageReceipts = this.normalizePrivateMessageReceipts(this.privateMessageReceipts);
|
|
3999
|
+
this.privateMessageDeliveryStatusCache.set(receipt.message_id, receipt.status);
|
|
3957
4000
|
}
|
|
3958
4001
|
|
|
3959
4002
|
private normalizeIncomingSocialMessage(value: unknown): SocialMessageRecord | null {
|
|
@@ -4272,7 +4315,7 @@ export async function main() {
|
|
|
4272
4315
|
} catch {
|
|
4273
4316
|
// best effort after response has been sent
|
|
4274
4317
|
}
|
|
4275
|
-
},
|
|
4318
|
+
}, 1200);
|
|
4276
4319
|
})
|
|
4277
4320
|
);
|
|
4278
4321
|
|
|
@@ -4414,8 +4457,9 @@ export async function main() {
|
|
|
4414
4457
|
|
|
4415
4458
|
app.get("/api/messages", (req, res) => {
|
|
4416
4459
|
const limit = Number(req.query.limit ?? 50);
|
|
4460
|
+
const offset = Number(req.query.offset ?? 0);
|
|
4417
4461
|
const agentId = String(req.query.agent_id ?? "").trim();
|
|
4418
|
-
sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null }));
|
|
4462
|
+
sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null, offset }));
|
|
4419
4463
|
});
|
|
4420
4464
|
|
|
4421
4465
|
app.get("/api/private/state", (_req, res) => {
|
|
@@ -4429,7 +4473,8 @@ export async function main() {
|
|
|
4429
4473
|
app.get("/api/private/messages", (req, res) => {
|
|
4430
4474
|
const conversationId = String(req.query.conversation_id ?? "").trim();
|
|
4431
4475
|
const limit = Number(req.query.limit ?? PRIVATE_MESSAGE_QUERY_LIMIT);
|
|
4432
|
-
|
|
4476
|
+
const offset = Number(req.query.offset ?? 0);
|
|
4477
|
+
sendOk(res, node.getPrivateMessages(conversationId, limit, offset));
|
|
4433
4478
|
});
|
|
4434
4479
|
|
|
4435
4480
|
app.post(
|
|
@@ -4441,7 +4486,11 @@ export async function main() {
|
|
|
4441
4486
|
body: String(req.body?.body || ""),
|
|
4442
4487
|
});
|
|
4443
4488
|
sendOk(res, result, {
|
|
4444
|
-
message: result.sent
|
|
4489
|
+
message: result.sent
|
|
4490
|
+
? (result.reason === "direct-sent"
|
|
4491
|
+
? "Private message sent directly"
|
|
4492
|
+
: "Private message sent via encrypted fallback")
|
|
4493
|
+
: `Private message skipped: ${result.reason}`,
|
|
4445
4494
|
});
|
|
4446
4495
|
})
|
|
4447
4496
|
);
|
|
@@ -4460,8 +4509,9 @@ export async function main() {
|
|
|
4460
4509
|
|
|
4461
4510
|
app.get("/api/openclaw/bridge/messages", (req, res) => {
|
|
4462
4511
|
const limit = Number(req.query.limit ?? 50);
|
|
4512
|
+
const offset = Number(req.query.offset ?? 0);
|
|
4463
4513
|
const agentId = String(req.query.agent_id ?? "").trim();
|
|
4464
|
-
sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null }));
|
|
4514
|
+
sendOk(res, node.getSocialMessages(limit, { agent_id: agentId || null, offset }));
|
|
4465
4515
|
});
|
|
4466
4516
|
|
|
4467
4517
|
app.post(
|
|
@@ -4611,10 +4661,17 @@ export async function main() {
|
|
|
4611
4661
|
let html = readFileSync(staticIndexFile, "utf8");
|
|
4612
4662
|
html = html.replace("</body>", `${renderBootstrapScript(payload)}\n</body>`);
|
|
4613
4663
|
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
4664
|
+
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
|
|
4614
4665
|
res.send(html);
|
|
4615
4666
|
});
|
|
4616
4667
|
|
|
4617
|
-
app.use(express.static(staticDir
|
|
4668
|
+
app.use(express.static(staticDir, {
|
|
4669
|
+
etag: false,
|
|
4670
|
+
lastModified: false,
|
|
4671
|
+
setHeaders: (res) => {
|
|
4672
|
+
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
|
|
4673
|
+
},
|
|
4674
|
+
}));
|
|
4618
4675
|
|
|
4619
4676
|
app.use((error: unknown, _req: Request, res: Response, _next: NextFunction) => {
|
|
4620
4677
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
2026.3.20-beta.
|
|
1
|
+
2026.3.20-beta.14
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "silicaclaw-broadcast",
|
|
3
|
-
"version": "2026.3.20-beta.
|
|
3
|
+
"version": "2026.3.20-beta.14",
|
|
4
4
|
"display_name": "SilicaClaw Broadcast",
|
|
5
5
|
"description": "Official OpenClaw skill for a bounded local SilicaClaw broadcast workflow: read public broadcasts, publish public broadcasts, and optionally forward owner-relevant summaries through OpenClaw's native channel.",
|
|
6
6
|
"entrypoints": {
|