@silicaclaw/cli 2026.3.19-13 → 2026.3.19-15
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.js +17 -2
- package/apps/local-console/public/app/app.js +4 -0
- package/apps/local-console/public/app/events.js +2 -0
- package/apps/local-console/public/app/overview.js +1 -1
- package/apps/local-console/public/app/shell.js +18 -34
- package/apps/local-console/public/app/social.js +15 -1
- package/apps/local-console/public/app/template.js +5 -0
- package/apps/local-console/public/app/translations.js +4 -0
- package/apps/local-console/src/server.ts +18 -2
- 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-19
|
|
4
4
|
|
|
5
|
+
### 2026.3.19-15
|
|
6
|
+
|
|
7
|
+
- release build:
|
|
8
|
+
- prepared another fresh date-based package build without publishing
|
|
9
|
+
- regenerated the npm tarball through the release packing workflow
|
|
10
|
+
|
|
11
|
+
### 2026.3.19-14
|
|
12
|
+
|
|
13
|
+
- release build:
|
|
14
|
+
- prepared another fresh date-based package build without publishing
|
|
15
|
+
- regenerated the npm tarball through the release packing workflow
|
|
16
|
+
|
|
5
17
|
### 2026.3.19-13
|
|
6
18
|
|
|
7
19
|
- release build:
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v2026.3.19-
|
|
1
|
+
v2026.3.19-15
|
|
@@ -1700,7 +1700,7 @@ class LocalNodeService {
|
|
|
1700
1700
|
await this.log("info", "profile.json missing/invalid, initialized from social/default profile");
|
|
1701
1701
|
}
|
|
1702
1702
|
await this.profileRepo.set(this.profile);
|
|
1703
|
-
this.directory = (0, core_1.
|
|
1703
|
+
this.directory = (0, core_1.createEmptyDirectoryState)();
|
|
1704
1704
|
this.messageGovernance = {
|
|
1705
1705
|
...this.defaultMessageGovernance(),
|
|
1706
1706
|
...(await this.socialMessageGovernanceRepo.get()),
|
|
@@ -2001,7 +2001,22 @@ class LocalNodeService {
|
|
|
2001
2001
|
this.publishedByTopic[topic] = (this.publishedByTopic[topic] ?? 0) + 1;
|
|
2002
2002
|
}
|
|
2003
2003
|
async persistCache() {
|
|
2004
|
-
|
|
2004
|
+
const persisted = (0, core_1.createEmptyDirectoryState)();
|
|
2005
|
+
if (this.profile) {
|
|
2006
|
+
const selfProfileRecord = {
|
|
2007
|
+
type: "profile",
|
|
2008
|
+
profile: this.profile,
|
|
2009
|
+
};
|
|
2010
|
+
this.directory = (0, core_1.ingestProfileRecord)(this.directory, selfProfileRecord);
|
|
2011
|
+
persisted.profiles[this.profile.agent_id] = this.profile;
|
|
2012
|
+
const selfLastSeenAt = this.directory.presence[this.profile.agent_id];
|
|
2013
|
+
if (typeof selfLastSeenAt === "number" && Number.isFinite(selfLastSeenAt)) {
|
|
2014
|
+
persisted.presence[this.profile.agent_id] = selfLastSeenAt;
|
|
2015
|
+
}
|
|
2016
|
+
const indexed = (0, core_1.rebuildIndexForProfile)(persisted, this.profile);
|
|
2017
|
+
persisted.index = indexed.index;
|
|
2018
|
+
}
|
|
2019
|
+
await this.cacheRepo.set(persisted);
|
|
2005
2020
|
}
|
|
2006
2021
|
async persistSocialMessages() {
|
|
2007
2022
|
await this.socialMessageRepo.set(this.socialMessages);
|
|
@@ -203,6 +203,8 @@ root.innerHTML = appTemplate;
|
|
|
203
203
|
document.getElementById('socialBridgeTitle').textContent = t('labels.identityBinding');
|
|
204
204
|
document.getElementById('socialOwnerDeliveryTitle').textContent = t('labels.ownerDelivery');
|
|
205
205
|
document.getElementById('socialSkillLearningTitle').textContent = t('labels.openclawSkillLearning');
|
|
206
|
+
document.getElementById('socialMessagePathTitle').textContent = t('labels.messagePath');
|
|
207
|
+
document.getElementById('socialMessagePathHint').textContent = t('hints.socialMessagePathHint');
|
|
206
208
|
document.getElementById('socialGovernanceTitle').textContent = t('labels.messageGovernance');
|
|
207
209
|
document.getElementById('socialModerationTitle').textContent = t('labels.recentModeration');
|
|
208
210
|
document.getElementById('socialAdvancedSummary').textContent = t('labels.advancedNetworkDetails');
|
|
@@ -268,6 +270,7 @@ root.innerHTML = appTemplate;
|
|
|
268
270
|
const shell = createShellController({ resolveThemeMode, t });
|
|
269
271
|
const {
|
|
270
272
|
applyTheme,
|
|
273
|
+
clearUiCache,
|
|
271
274
|
flashButton,
|
|
272
275
|
hydrateCachedShell,
|
|
273
276
|
peerStatusText,
|
|
@@ -438,6 +441,7 @@ root.innerHTML = appTemplate;
|
|
|
438
441
|
parseCsv,
|
|
439
442
|
profileController,
|
|
440
443
|
pulseOverviewBroadcastStep,
|
|
444
|
+
clearUiCache,
|
|
441
445
|
refreshAll,
|
|
442
446
|
refreshLogs,
|
|
443
447
|
refreshMessages,
|
|
@@ -7,6 +7,7 @@ export function bindAppEvents({
|
|
|
7
7
|
parseCsv,
|
|
8
8
|
profileController,
|
|
9
9
|
pulseOverviewBroadcastStep,
|
|
10
|
+
clearUiCache,
|
|
10
11
|
refreshAll,
|
|
11
12
|
refreshLogs,
|
|
12
13
|
refreshMessages,
|
|
@@ -223,6 +224,7 @@ export function bindAppEvents({
|
|
|
223
224
|
document.getElementById("clearDiscoveryCacheBtn").addEventListener("click", async () => {
|
|
224
225
|
try {
|
|
225
226
|
await api("/api/cache/clear", { method: "POST" });
|
|
227
|
+
clearUiCache(["silicaclaw_ui_overview", "silicaclaw_ui_network", "silicaclaw_ui_social"]);
|
|
226
228
|
setFeedback("networkFeedback", t("feedback.discoveryCacheCleared"));
|
|
227
229
|
toast(t("feedback.discoveryCacheCleared"));
|
|
228
230
|
setAgentsPage(1);
|
|
@@ -143,6 +143,7 @@ export function createOverviewController({
|
|
|
143
143
|
</div>
|
|
144
144
|
`;
|
|
145
145
|
document.getElementById("snapshot").innerHTML = snapshotHtml;
|
|
146
|
+
const networkDiag = networkStats.adapter_diagnostics_summary || {};
|
|
146
147
|
const heroModeText = o.social?.network_mode || "-";
|
|
147
148
|
const heroAdapterText = networkCfg.adapter || "-";
|
|
148
149
|
const heroRelayText = String(networkDiag.signaling_url || networkCfg.adapter_extra?.signaling_url || "-");
|
|
@@ -166,7 +167,6 @@ export function createOverviewController({
|
|
|
166
167
|
const openclawDetected = !!bridge.openclaw_installation?.detected || openclawRunning || !!bridge.openclaw_runtime?.gateway_reachable;
|
|
167
168
|
const skillInstalled = !!bridge.skill_learning?.installed;
|
|
168
169
|
const globalMode = heroModeText === "global-preview";
|
|
169
|
-
const networkDiag = networkStats.adapter_diagnostics_summary || {};
|
|
170
170
|
const lastNetworkError = String(networkDiag.last_error || o.last_broadcast_error || "").trim();
|
|
171
171
|
const broadcastHealthy = o.broadcast_enabled && !lastNetworkError;
|
|
172
172
|
const roleKey = openclawRunning
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export function createShellController({ t, resolveThemeMode }) {
|
|
2
|
+
const REALTIME_UI_CACHE_KEYS = [
|
|
3
|
+
"silicaclaw_ui_overview",
|
|
4
|
+
"silicaclaw_ui_network",
|
|
5
|
+
"silicaclaw_ui_social",
|
|
6
|
+
];
|
|
7
|
+
|
|
2
8
|
function peerStatusText(status) {
|
|
3
9
|
if (status === "online") return t("overview.online");
|
|
4
10
|
if (status === "offline") return t("overview.offline");
|
|
@@ -6,48 +12,25 @@ export function createShellController({ t, resolveThemeMode }) {
|
|
|
6
12
|
return status || "-";
|
|
7
13
|
}
|
|
8
14
|
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return raw ? JSON.parse(raw) : null;
|
|
13
|
-
} catch {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
15
|
+
function writeUiCache(key, value) {
|
|
16
|
+
void key;
|
|
17
|
+
void value;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
function
|
|
20
|
+
function clearUiCache(keys) {
|
|
21
|
+
const values = Array.isArray(keys) ? keys : keys ? [keys] : REALTIME_UI_CACHE_KEYS;
|
|
19
22
|
try {
|
|
20
|
-
|
|
23
|
+
for (const key of values) {
|
|
24
|
+
if (!key) continue;
|
|
25
|
+
localStorage.removeItem(key);
|
|
26
|
+
}
|
|
21
27
|
} catch {
|
|
22
|
-
// ignore cache
|
|
28
|
+
// ignore cache removal failures
|
|
23
29
|
}
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
function hydrateCachedShell() {
|
|
27
|
-
|
|
28
|
-
if (overview) {
|
|
29
|
-
if (overview.overviewCardsHtml) document.getElementById("overviewCards").innerHTML = overview.overviewCardsHtml;
|
|
30
|
-
if (overview.snapshotText) document.getElementById("snapshot").innerHTML = overview.snapshotText;
|
|
31
|
-
if (overview.heroModeText) document.getElementById("heroMode").textContent = overview.heroModeText;
|
|
32
|
-
if (overview.pillBroadcastText) document.getElementById("pillBroadcast").textContent = overview.pillBroadcastText;
|
|
33
|
-
if (overview.pillBroadcastClassName) document.getElementById("pillBroadcast").className = overview.pillBroadcastClassName;
|
|
34
|
-
if (overview.agentsCountHintText) document.getElementById("agentsCountHint").textContent = overview.agentsCountHintText;
|
|
35
|
-
if (overview.agentsWrapHtml) document.getElementById("agentsWrap").innerHTML = overview.agentsWrapHtml;
|
|
36
|
-
}
|
|
37
|
-
const network = readUiCache("silicaclaw_ui_network");
|
|
38
|
-
if (network) {
|
|
39
|
-
if (network.heroAdapterText) document.getElementById("heroAdapter").textContent = network.heroAdapterText;
|
|
40
|
-
if (network.heroRelayText) document.getElementById("heroRelay").textContent = network.heroRelayText;
|
|
41
|
-
if (network.heroRoomText) document.getElementById("heroRoom").textContent = network.heroRoomText;
|
|
42
|
-
if (network.pillAdapterText) document.getElementById("pillAdapter").textContent = network.pillAdapterText;
|
|
43
|
-
}
|
|
44
|
-
const social = readUiCache("silicaclaw_ui_social");
|
|
45
|
-
if (social) {
|
|
46
|
-
if (social.integrationStatusText) document.getElementById("integrationStatusBar").textContent = social.integrationStatusText;
|
|
47
|
-
if (social.integrationStatusClassName) document.getElementById("integrationStatusBar").className = social.integrationStatusClassName;
|
|
48
|
-
if (social.socialStatusLineText) document.getElementById("socialStatusLine").textContent = social.socialStatusLineText;
|
|
49
|
-
if (social.socialStatusSublineText) document.getElementById("socialStatusSubline").textContent = social.socialStatusSublineText;
|
|
50
|
-
}
|
|
33
|
+
clearUiCache(REALTIME_UI_CACHE_KEYS);
|
|
51
34
|
}
|
|
52
35
|
|
|
53
36
|
function toast(msg) {
|
|
@@ -130,6 +113,7 @@ export function createShellController({ t, resolveThemeMode }) {
|
|
|
130
113
|
|
|
131
114
|
return {
|
|
132
115
|
applyTheme,
|
|
116
|
+
clearUiCache,
|
|
133
117
|
flashButton,
|
|
134
118
|
hydrateCachedShell,
|
|
135
119
|
peerStatusText,
|
|
@@ -112,11 +112,12 @@ export function createSocialController({
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
async function refreshSocial() {
|
|
115
|
-
const [socialRes, summaryRes, statusRes, networkCfgRes, governanceRes] = await Promise.all([
|
|
115
|
+
const [socialRes, summaryRes, statusRes, networkCfgRes, networkStatsRes, governanceRes] = await Promise.all([
|
|
116
116
|
api("/api/social/config"),
|
|
117
117
|
api("/api/social/integration-summary"),
|
|
118
118
|
api("/api/integration/status"),
|
|
119
119
|
api("/api/network/config"),
|
|
120
|
+
api("/api/network/stats"),
|
|
120
121
|
api("/api/social/message-governance"),
|
|
121
122
|
]);
|
|
122
123
|
const bridgeRes = await api("/api/openclaw/bridge");
|
|
@@ -126,6 +127,7 @@ export function createSocialController({
|
|
|
126
127
|
const networkCfg = networkCfgRes.data || {};
|
|
127
128
|
const bridge = bridgeRes.data || {};
|
|
128
129
|
const governance = governanceRes.data || {};
|
|
130
|
+
const networkStats = networkStatsRes.data || {};
|
|
129
131
|
const runtime = social.runtime || {};
|
|
130
132
|
const config = social.social_config || {};
|
|
131
133
|
const network = config.network || {};
|
|
@@ -137,6 +139,7 @@ export function createSocialController({
|
|
|
137
139
|
const effectiveNamespace = networkCfg.namespace || runtimeNetwork.namespace || summary.current_namespace || "-";
|
|
138
140
|
const effectiveRoom = effectiveAdapterExtra.room || runtimeNetwork.room || network.room || "-";
|
|
139
141
|
const effectiveRelay = effectiveAdapterExtra.signaling_url || runtimeNetwork.signaling_url || network.signaling_url || "-";
|
|
142
|
+
const networkDiag = networkStats.adapter_diagnostics_summary || {};
|
|
140
143
|
const discoverable = status.discoverable === true;
|
|
141
144
|
const mode = effectiveMode;
|
|
142
145
|
const summaryLine = status.status_line || summary.summary_line || `${summary.connected ? t("social.connectedToSilicaClaw") : t("social.notConnectedToSilicaClaw")} · ${discoverable ? t("social.discoverableInCurrentMode") : t("social.notDiscoverableInCurrentMode")} · ${t("social.usingMode", { mode })}`;
|
|
@@ -190,6 +193,17 @@ export function createSocialController({
|
|
|
190
193
|
[t("social.reuseOpenClawIdentity"), summary.reused_openclaw_identity ? t("common.yes") : t("common.no")],
|
|
191
194
|
].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${v}</div></div>`).join("");
|
|
192
195
|
|
|
196
|
+
document.getElementById("socialMessagePathCards").innerHTML = [
|
|
197
|
+
[t("social.messageBroadcast"), bridge.message_broadcast_enabled ? t("common.on") : t("common.off")],
|
|
198
|
+
[t("social.publicDiscovery"), status.public_enabled ? t("common.on") : t("common.off")],
|
|
199
|
+
[t("social.namespace"), effectiveNamespace],
|
|
200
|
+
[t("labels.room"), effectiveRoom],
|
|
201
|
+
[t("labels.relay"), effectiveRelay],
|
|
202
|
+
[t("network.lastPoll"), networkDiag.last_poll_at ? new Date(networkDiag.last_poll_at).toLocaleTimeString() : "-"],
|
|
203
|
+
[t("network.lastPublish"), networkDiag.last_publish_at ? new Date(networkDiag.last_publish_at).toLocaleTimeString() : "-"],
|
|
204
|
+
[t("network.lastError"), networkDiag.last_error || t("network.none")],
|
|
205
|
+
].map(([k, v]) => `<div class="card"><div class="label">${k}</div><div class="value" style="font-size:17px;">${escapeHtml(String(v))}</div></div>`).join("");
|
|
206
|
+
|
|
193
207
|
const skillLearning = bridge.skill_learning || {};
|
|
194
208
|
const ownerDelivery = bridge.owner_delivery || {};
|
|
195
209
|
const installAction = skillLearning.install_action || {};
|
|
@@ -689,6 +689,11 @@ export const appTemplate = String.raw`<div class="app" id="appShell">
|
|
|
689
689
|
<div class="grid" id="socialIntegrationCards"></div>
|
|
690
690
|
</div>
|
|
691
691
|
</div>
|
|
692
|
+
<div class="card">
|
|
693
|
+
<h3 class="title-sm" id="socialMessagePathTitle">Message Path</h3>
|
|
694
|
+
<div class="field-hint" id="socialMessagePathHint">Check this block first when public messages are not showing up on another node.</div>
|
|
695
|
+
<div class="grid" id="socialMessagePathCards"></div>
|
|
696
|
+
</div>
|
|
692
697
|
</div>
|
|
693
698
|
<div class="page-column">
|
|
694
699
|
<div class="card">
|
|
@@ -150,6 +150,7 @@ export const TRANSLATIONS = {
|
|
|
150
150
|
recentModeration: 'Recent Moderation Activity',
|
|
151
151
|
ownerDelivery: 'Owner Delivery',
|
|
152
152
|
openclawSkillLearning: 'OpenClaw Skill Learning',
|
|
153
|
+
messagePath: 'Message Path',
|
|
153
154
|
skillsEyebrow: 'Skills',
|
|
154
155
|
skillsFeatured: 'Featured Skill',
|
|
155
156
|
skillsBundled: 'Bundled Skills',
|
|
@@ -225,6 +226,7 @@ export const TRANSLATIONS = {
|
|
|
225
226
|
socialBannerBody: 'This page separates current social runtime, bridge health, and learning path, so it is easier to see whether this machine is just broadcasting or also ready to learn and forward.',
|
|
226
227
|
socialBannerOpenClaw: 'OpenClaw',
|
|
227
228
|
socialBannerOpenClawValue: 'Use the skill card here to confirm OpenClaw is detected, running, and ready to learn SilicaClaw broadcasts.',
|
|
229
|
+
socialMessagePathHint: 'Check this block first when public messages are not showing up on another node.',
|
|
228
230
|
skillsBannerTitle: 'See what ships with SilicaClaw and what OpenClaw already exposes here.',
|
|
229
231
|
skillsBannerBody: 'This page is the skill hub: packaged capabilities from this project on one side, skills already installed into OpenClaw on the other, with a clear path to install the broadcast skill from here.',
|
|
230
232
|
skillsBannerRuntime: 'Runtime',
|
|
@@ -663,6 +665,7 @@ export const TRANSLATIONS = {
|
|
|
663
665
|
recentModeration: '最近治理活动',
|
|
664
666
|
ownerDelivery: '主人转发',
|
|
665
667
|
openclawSkillLearning: 'OpenClaw 技能学习',
|
|
668
|
+
messagePath: '消息通路',
|
|
666
669
|
skillsEyebrow: '技能',
|
|
667
670
|
skillsFeatured: '重点技能',
|
|
668
671
|
skillsBundled: '项目自带技能',
|
|
@@ -738,6 +741,7 @@ export const TRANSLATIONS = {
|
|
|
738
741
|
socialBannerBody: '这个页面把当前社交运行态、Bridge 健康度和学习路径拆开呈现,更容易判断这台机器只是广播源,还是已经具备学习与转发能力。',
|
|
739
742
|
socialBannerOpenClaw: 'OpenClaw',
|
|
740
743
|
socialBannerOpenClawValue: '在这里看技能卡片,就能确认 OpenClaw 是否已检测到、已启动,并且准备好学习 SilicaClaw 广播。',
|
|
744
|
+
socialMessagePathHint: '如果公开消息没有出现在另一台节点上,先看这里。',
|
|
741
745
|
skillsBannerTitle: '在这里看清楚 SilicaClaw 自带什么,以及 OpenClaw 这里已经暴露什么。',
|
|
742
746
|
skillsBannerBody: '这个页面是技能中心:左边是项目打包好的能力,右边是本机 OpenClaw 已安装的技能,同时保留一键安装广播技能的入口。',
|
|
743
747
|
skillsBannerRuntime: '运行态',
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
ingestPresenceRecord,
|
|
28
28
|
ingestProfileRecord,
|
|
29
29
|
isAgentOnline,
|
|
30
|
+
rebuildIndexForProfile,
|
|
30
31
|
loadSocialConfig,
|
|
31
32
|
getSocialConfigSearchPaths,
|
|
32
33
|
resolveIdentityWithSocial,
|
|
@@ -2029,7 +2030,7 @@ export class LocalNodeService {
|
|
|
2029
2030
|
}
|
|
2030
2031
|
await this.profileRepo.set(this.profile);
|
|
2031
2032
|
|
|
2032
|
-
this.directory =
|
|
2033
|
+
this.directory = createEmptyDirectoryState();
|
|
2033
2034
|
this.messageGovernance = {
|
|
2034
2035
|
...this.defaultMessageGovernance(),
|
|
2035
2036
|
...(await this.socialMessageGovernanceRepo.get()),
|
|
@@ -2366,7 +2367,22 @@ export class LocalNodeService {
|
|
|
2366
2367
|
}
|
|
2367
2368
|
|
|
2368
2369
|
private async persistCache(): Promise<void> {
|
|
2369
|
-
|
|
2370
|
+
const persisted = createEmptyDirectoryState();
|
|
2371
|
+
if (this.profile) {
|
|
2372
|
+
const selfProfileRecord: SignedProfileRecord = {
|
|
2373
|
+
type: "profile",
|
|
2374
|
+
profile: this.profile,
|
|
2375
|
+
};
|
|
2376
|
+
this.directory = ingestProfileRecord(this.directory, selfProfileRecord);
|
|
2377
|
+
persisted.profiles[this.profile.agent_id] = this.profile;
|
|
2378
|
+
const selfLastSeenAt = this.directory.presence[this.profile.agent_id];
|
|
2379
|
+
if (typeof selfLastSeenAt === "number" && Number.isFinite(selfLastSeenAt)) {
|
|
2380
|
+
persisted.presence[this.profile.agent_id] = selfLastSeenAt;
|
|
2381
|
+
}
|
|
2382
|
+
const indexed = rebuildIndexForProfile(persisted, this.profile);
|
|
2383
|
+
persisted.index = indexed.index;
|
|
2384
|
+
}
|
|
2385
|
+
await this.cacheRepo.set(persisted);
|
|
2370
2386
|
}
|
|
2371
2387
|
|
|
2372
2388
|
private async persistSocialMessages(): Promise<void> {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
2026.3.19-
|
|
1
|
+
2026.3.19-15
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "silicaclaw-broadcast",
|
|
3
|
-
"version": "2026.3.19-
|
|
3
|
+
"version": "2026.3.19-15",
|
|
4
4
|
"display_name": "SilicaClaw Broadcast",
|
|
5
5
|
"description": "OpenClaw skill for reading SilicaClaw public broadcasts, publishing public broadcasts, and forwarding relevant updates to the owner through OpenClaw's native social channel.",
|
|
6
6
|
"entrypoints": {
|