@silicaclaw/cli 2026.3.20-3 → 2026.3.20-4
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 +6 -0
- package/INSTALL.md +2 -2
- package/README.md +2 -2
- package/VERSION +1 -1
- package/apps/local-console/dist/apps/local-console/src/server.d.ts +34 -0
- package/apps/local-console/dist/apps/local-console/src/server.js +185 -0
- package/apps/local-console/public/app/app.js +249 -1
- package/apps/local-console/public/app/network.js +111 -30
- package/apps/local-console/public/app/overview.js +43 -15
- package/apps/local-console/public/app/social.js +135 -53
- package/apps/local-console/public/app/styles.css +51 -0
- package/apps/local-console/public/app/template.js +6 -1
- package/apps/local-console/public/app/translations.js +32 -0
- package/apps/local-console/src/server.ts +209 -1
- package/openclaw-skills/silicaclaw-broadcast/VERSION +1 -1
- package/openclaw-skills/silicaclaw-broadcast/manifest.json +1 -1
- package/openclaw-skills/silicaclaw-owner-push/VERSION +1 -1
- package/openclaw-skills/silicaclaw-owner-push/manifest.json +1 -1
- package/openclaw-skills/silicaclaw-owner-push/references/runtime-setup.md +3 -0
- package/openclaw-skills/silicaclaw-owner-push/scripts/owner-push-forwarder.mjs +67 -8
- package/package.json +1 -1
- package/scripts/silicaclaw-cli.mjs +4 -1
- package/scripts/silicaclaw-gateway.mjs +108 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## v1.0 beta - 2026-03-20
|
|
4
4
|
|
|
5
|
+
### 2026.3.20-4
|
|
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
|
+
|
|
5
11
|
### 2026.3.20-3
|
|
6
12
|
|
|
7
13
|
- release build:
|
package/INSTALL.md
CHANGED
|
@@ -211,9 +211,9 @@ npx clawhub publish openclaw-skills/silicaclaw-broadcast \
|
|
|
211
211
|
npx clawhub publish openclaw-skills/silicaclaw-owner-push \
|
|
212
212
|
--slug silicaclaw-owner-push \
|
|
213
213
|
--name "SilicaClaw Owner Push" \
|
|
214
|
-
--version 2026.3.20-beta.
|
|
214
|
+
--version 2026.3.20-beta.2 \
|
|
215
215
|
--tags latest \
|
|
216
|
-
--changelog "Added
|
|
216
|
+
--changelog "Added latest-only owner push behavior with timestamp cursor state so only the newest qualifying broadcast is pushed and older messages are skipped."
|
|
217
217
|
```
|
|
218
218
|
|
|
219
219
|
ClawHub expects each skill version to be valid semver, so use the versions from each skill's `manifest.json` and `VERSION`, not the npm CLI version format.
|
package/README.md
CHANGED
|
@@ -283,9 +283,9 @@ npx clawhub publish openclaw-skills/silicaclaw-broadcast \
|
|
|
283
283
|
npx clawhub publish openclaw-skills/silicaclaw-owner-push \
|
|
284
284
|
--slug silicaclaw-owner-push \
|
|
285
285
|
--name "SilicaClaw Owner Push" \
|
|
286
|
-
--version 2026.3.20-beta.
|
|
286
|
+
--version 2026.3.20-beta.2 \
|
|
287
287
|
--tags latest \
|
|
288
|
-
--changelog "Added
|
|
288
|
+
--changelog "Added latest-only owner push behavior with timestamp cursor state so only the newest qualifying broadcast is pushed and older messages are skipped."
|
|
289
289
|
```
|
|
290
290
|
|
|
291
291
|
ClawHub publishes the OpenClaw skill folders, not the npm CLI package.
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
v2026.3.20-
|
|
1
|
+
v2026.3.20-4
|
|
@@ -374,6 +374,23 @@ export declare class LocalNodeService {
|
|
|
374
374
|
adapter_stats: any;
|
|
375
375
|
adapter_transport_stats: any;
|
|
376
376
|
adapter_discovery_stats: any;
|
|
377
|
+
runtime_diagnostics: {
|
|
378
|
+
memory_mib: {
|
|
379
|
+
rss: number;
|
|
380
|
+
heap_used: number;
|
|
381
|
+
heap_total: number;
|
|
382
|
+
external: number;
|
|
383
|
+
};
|
|
384
|
+
directory: {
|
|
385
|
+
profile_count: number;
|
|
386
|
+
presence_count: number;
|
|
387
|
+
index_key_count: number;
|
|
388
|
+
};
|
|
389
|
+
social: {
|
|
390
|
+
message_count: number;
|
|
391
|
+
observation_count: number;
|
|
392
|
+
};
|
|
393
|
+
};
|
|
377
394
|
adapter_diagnostics_summary: {
|
|
378
395
|
started: boolean;
|
|
379
396
|
startup_error: string | null;
|
|
@@ -471,6 +488,22 @@ export declare class LocalNodeService {
|
|
|
471
488
|
social_lookup_paths: string[];
|
|
472
489
|
social_source_path: string | null;
|
|
473
490
|
};
|
|
491
|
+
getAppUpdateStatus(): {
|
|
492
|
+
latest_version: string;
|
|
493
|
+
update_available: boolean;
|
|
494
|
+
current_version: string;
|
|
495
|
+
channel: string;
|
|
496
|
+
platform: NodeJS.Platform;
|
|
497
|
+
checked_at: number;
|
|
498
|
+
can_update: boolean;
|
|
499
|
+
check_error: string | null;
|
|
500
|
+
};
|
|
501
|
+
startAppUpdate(): {
|
|
502
|
+
started: boolean;
|
|
503
|
+
target_version: string;
|
|
504
|
+
platform: string;
|
|
505
|
+
reason?: string;
|
|
506
|
+
};
|
|
474
507
|
getIntegrationSummary(): {
|
|
475
508
|
connected: boolean;
|
|
476
509
|
discoverable: boolean;
|
|
@@ -733,6 +766,7 @@ export declare class LocalNodeService {
|
|
|
733
766
|
private clearNetworkReconnectTimer;
|
|
734
767
|
private startNetworkAdapterWithRetry;
|
|
735
768
|
private scheduleNetworkReconnect;
|
|
769
|
+
private pruneRemoteProfilesInMemory;
|
|
736
770
|
private compactCacheInMemory;
|
|
737
771
|
private publish;
|
|
738
772
|
private persistCache;
|
|
@@ -37,6 +37,7 @@ const DEFAULT_BRIDGE_API_BASE = silicaclaw_defaults_json_1.default.bridge.api_ba
|
|
|
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
39
|
const NETWORK_PEER_REMOVE_AFTER_MS = Number(process.env.NETWORK_PEER_REMOVE_AFTER_MS || 180_000);
|
|
40
|
+
const DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT = Number(process.env.DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT || 1000);
|
|
40
41
|
const NETWORK_UDP_BIND_ADDRESS = process.env.NETWORK_UDP_BIND_ADDRESS || "0.0.0.0";
|
|
41
42
|
const NETWORK_UDP_BROADCAST_ADDRESS = process.env.NETWORK_UDP_BROADCAST_ADDRESS || "255.255.255.255";
|
|
42
43
|
const NETWORK_PEER_ID = process.env.NETWORK_PEER_ID;
|
|
@@ -101,6 +102,9 @@ function normalizeVersionText(value) {
|
|
|
101
102
|
const text = String(value || "").trim();
|
|
102
103
|
return text.startsWith("v") ? text.slice(1) : text;
|
|
103
104
|
}
|
|
105
|
+
function formatBytesToMiB(value) {
|
|
106
|
+
return Math.round((value / (1024 * 1024)) * 10) / 10;
|
|
107
|
+
}
|
|
104
108
|
function tokenizeVersion(value) {
|
|
105
109
|
return normalizeVersionText(value)
|
|
106
110
|
.split(/[^0-9A-Za-z]+/)
|
|
@@ -133,6 +137,9 @@ function compareVersionTokens(left, right) {
|
|
|
133
137
|
}
|
|
134
138
|
return 0;
|
|
135
139
|
}
|
|
140
|
+
function userNpmCacheDir() {
|
|
141
|
+
return (0, path_1.resolve)((0, os_1.homedir)(), ".silicaclaw", "npm-cache");
|
|
142
|
+
}
|
|
136
143
|
function resolveWorkspaceRoot(cwd = process.cwd()) {
|
|
137
144
|
if ((0, fs_1.existsSync)((0, path_1.resolve)(cwd, "apps", "local-console", "package.json"))) {
|
|
138
145
|
return cwd;
|
|
@@ -968,6 +975,7 @@ class LocalNodeService {
|
|
|
968
975
|
const relayCapable = this.adapterMode === "webrtc-preview" || this.adapterMode === "relay-preview";
|
|
969
976
|
const peers = diagnostics?.peers?.items ?? [];
|
|
970
977
|
const online = peers.filter((peer) => peer.status === "online").length;
|
|
978
|
+
const memory = process.memoryUsage();
|
|
971
979
|
return {
|
|
972
980
|
adapter: this.adapterMode,
|
|
973
981
|
mode: this.networkMode,
|
|
@@ -991,6 +999,23 @@ class LocalNodeService {
|
|
|
991
999
|
adapter_stats: diagnostics?.stats ?? null,
|
|
992
1000
|
adapter_transport_stats: diagnostics?.transport_stats ?? null,
|
|
993
1001
|
adapter_discovery_stats: diagnostics?.discovery_stats ?? null,
|
|
1002
|
+
runtime_diagnostics: {
|
|
1003
|
+
memory_mib: {
|
|
1004
|
+
rss: formatBytesToMiB(memory.rss),
|
|
1005
|
+
heap_used: formatBytesToMiB(memory.heapUsed),
|
|
1006
|
+
heap_total: formatBytesToMiB(memory.heapTotal),
|
|
1007
|
+
external: formatBytesToMiB(memory.external),
|
|
1008
|
+
},
|
|
1009
|
+
directory: {
|
|
1010
|
+
profile_count: Object.keys(this.directory.profiles).length,
|
|
1011
|
+
presence_count: Object.keys(this.directory.presence).length,
|
|
1012
|
+
index_key_count: Object.keys(this.directory.index).length,
|
|
1013
|
+
},
|
|
1014
|
+
social: {
|
|
1015
|
+
message_count: this.socialMessages.length,
|
|
1016
|
+
observation_count: this.socialMessageObservations.length,
|
|
1017
|
+
},
|
|
1018
|
+
},
|
|
994
1019
|
adapter_diagnostics_summary: relayCapable || diagnostics
|
|
995
1020
|
? {
|
|
996
1021
|
started: this.networkStarted,
|
|
@@ -1102,6 +1127,87 @@ class LocalNodeService {
|
|
|
1102
1127
|
social_source_path: this.socialSourcePath,
|
|
1103
1128
|
};
|
|
1104
1129
|
}
|
|
1130
|
+
getAppUpdateStatus() {
|
|
1131
|
+
const currentVersion = normalizeVersionText(this.appVersion) || "unknown";
|
|
1132
|
+
const fallback = {
|
|
1133
|
+
current_version: currentVersion,
|
|
1134
|
+
latest_version: currentVersion,
|
|
1135
|
+
update_available: false,
|
|
1136
|
+
channel: "latest",
|
|
1137
|
+
platform: process.platform,
|
|
1138
|
+
checked_at: Date.now(),
|
|
1139
|
+
can_update: true,
|
|
1140
|
+
check_error: null,
|
|
1141
|
+
};
|
|
1142
|
+
try {
|
|
1143
|
+
const result = (0, child_process_1.spawnSync)("npm", ["view", "@silicaclaw/cli", "dist-tags", "--json"], {
|
|
1144
|
+
cwd: this.projectRoot,
|
|
1145
|
+
encoding: "utf8",
|
|
1146
|
+
env: {
|
|
1147
|
+
...process.env,
|
|
1148
|
+
SILICACLAW_WORKSPACE_DIR: this.projectRoot,
|
|
1149
|
+
SILICACLAW_APP_DIR: this.workspaceRoot,
|
|
1150
|
+
npm_config_cache: process.env.npm_config_cache || userNpmCacheDir(),
|
|
1151
|
+
},
|
|
1152
|
+
});
|
|
1153
|
+
if ((result.status ?? 1) !== 0) {
|
|
1154
|
+
return {
|
|
1155
|
+
...fallback,
|
|
1156
|
+
check_error: String(result.stderr || result.stdout || "npm view failed").trim() || "npm view failed",
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
const tags = JSON.parse(String(result.stdout || "{}").trim() || "{}");
|
|
1160
|
+
const latestVersion = normalizeVersionText(tags.latest || currentVersion) || currentVersion;
|
|
1161
|
+
return {
|
|
1162
|
+
...fallback,
|
|
1163
|
+
latest_version: latestVersion,
|
|
1164
|
+
update_available: compareVersionTokens(latestVersion, currentVersion) > 0,
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
catch (error) {
|
|
1168
|
+
return {
|
|
1169
|
+
...fallback,
|
|
1170
|
+
check_error: error instanceof Error ? error.message : String(error),
|
|
1171
|
+
};
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
startAppUpdate() {
|
|
1175
|
+
const status = this.getAppUpdateStatus();
|
|
1176
|
+
if (!status.update_available || !status.latest_version) {
|
|
1177
|
+
return {
|
|
1178
|
+
started: false,
|
|
1179
|
+
target_version: status.latest_version || status.current_version,
|
|
1180
|
+
platform: process.platform,
|
|
1181
|
+
reason: status.check_error || "already_current",
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
const scriptPath = (0, path_1.resolve)(this.workspaceRoot, "scripts", "silicaclaw-cli.mjs");
|
|
1185
|
+
if (!(0, fs_1.existsSync)(scriptPath)) {
|
|
1186
|
+
return {
|
|
1187
|
+
started: false,
|
|
1188
|
+
target_version: status.latest_version,
|
|
1189
|
+
platform: process.platform,
|
|
1190
|
+
reason: "missing_cli_script",
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
const child = (0, child_process_1.spawn)(process.execPath, [scriptPath, "update"], {
|
|
1194
|
+
cwd: this.projectRoot,
|
|
1195
|
+
detached: true,
|
|
1196
|
+
stdio: "ignore",
|
|
1197
|
+
env: {
|
|
1198
|
+
...process.env,
|
|
1199
|
+
SILICACLAW_WORKSPACE_DIR: this.projectRoot,
|
|
1200
|
+
SILICACLAW_APP_DIR: this.workspaceRoot,
|
|
1201
|
+
npm_config_cache: process.env.npm_config_cache || userNpmCacheDir(),
|
|
1202
|
+
},
|
|
1203
|
+
});
|
|
1204
|
+
child.unref();
|
|
1205
|
+
return {
|
|
1206
|
+
started: true,
|
|
1207
|
+
target_version: status.latest_version,
|
|
1208
|
+
platform: process.platform,
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1105
1211
|
getIntegrationSummary() {
|
|
1106
1212
|
const status = this.getIntegrationStatus();
|
|
1107
1213
|
const runtimeGenerated = Boolean(this.socialRuntime && this.socialRuntime.last_loaded_at > 0);
|
|
@@ -2290,9 +2396,58 @@ class LocalNodeService {
|
|
|
2290
2396
|
}, delayMs);
|
|
2291
2397
|
this.networkReconnectDelayMs = Math.min(30_000, Math.max(5_000, Math.floor(delayMs * 1.5)));
|
|
2292
2398
|
}
|
|
2399
|
+
pruneRemoteProfilesInMemory(now = Date.now()) {
|
|
2400
|
+
if (!Number.isFinite(DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT) || DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT <= 0) {
|
|
2401
|
+
return 0;
|
|
2402
|
+
}
|
|
2403
|
+
const selfAgentId = this.profile?.agent_id || this.identity?.agent_id || "";
|
|
2404
|
+
const remoteProfiles = Object.values(this.directory.profiles).filter((profile) => profile.agent_id !== selfAgentId);
|
|
2405
|
+
if (remoteProfiles.length <= DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT) {
|
|
2406
|
+
return 0;
|
|
2407
|
+
}
|
|
2408
|
+
const onlineRemoteProfiles = remoteProfiles.filter((profile) => (0, core_1.isAgentOnline)(this.directory.presence[profile.agent_id], now, PRESENCE_TTL_MS));
|
|
2409
|
+
const offlineRemoteProfiles = remoteProfiles
|
|
2410
|
+
.filter((profile) => !(0, core_1.isAgentOnline)(this.directory.presence[profile.agent_id], now, PRESENCE_TTL_MS))
|
|
2411
|
+
.sort((a, b) => (b.updated_at || 0) - (a.updated_at || 0));
|
|
2412
|
+
const keepOfflineCount = Math.max(0, DIRECTORY_REMOTE_PROFILE_SOFT_LIMIT - onlineRemoteProfiles.length);
|
|
2413
|
+
const keptRemoteProfiles = [
|
|
2414
|
+
...onlineRemoteProfiles,
|
|
2415
|
+
...offlineRemoteProfiles.slice(0, keepOfflineCount),
|
|
2416
|
+
];
|
|
2417
|
+
const keptRemoteIds = new Set(keptRemoteProfiles.map((profile) => profile.agent_id));
|
|
2418
|
+
const removedIds = remoteProfiles
|
|
2419
|
+
.map((profile) => profile.agent_id)
|
|
2420
|
+
.filter((agentId) => !keptRemoteIds.has(agentId));
|
|
2421
|
+
if (removedIds.length === 0) {
|
|
2422
|
+
return 0;
|
|
2423
|
+
}
|
|
2424
|
+
const next = (0, core_1.createEmptyDirectoryState)();
|
|
2425
|
+
const selfProfile = selfAgentId ? this.directory.profiles[selfAgentId] : null;
|
|
2426
|
+
if (selfProfile) {
|
|
2427
|
+
next.profiles[selfAgentId] = selfProfile;
|
|
2428
|
+
const selfPresence = this.directory.presence[selfAgentId];
|
|
2429
|
+
if (typeof selfPresence === "number" && Number.isFinite(selfPresence)) {
|
|
2430
|
+
next.presence[selfAgentId] = selfPresence;
|
|
2431
|
+
}
|
|
2432
|
+
const rebuilt = (0, core_1.rebuildIndexForProfile)(next, selfProfile);
|
|
2433
|
+
next.index = rebuilt.index;
|
|
2434
|
+
}
|
|
2435
|
+
for (const profile of keptRemoteProfiles) {
|
|
2436
|
+
next.profiles[profile.agent_id] = profile;
|
|
2437
|
+
const seenAt = this.directory.presence[profile.agent_id];
|
|
2438
|
+
if (typeof seenAt === "number" && Number.isFinite(seenAt)) {
|
|
2439
|
+
next.presence[profile.agent_id] = seenAt;
|
|
2440
|
+
}
|
|
2441
|
+
const rebuilt = (0, core_1.rebuildIndexForProfile)(next, profile);
|
|
2442
|
+
next.index = rebuilt.index;
|
|
2443
|
+
}
|
|
2444
|
+
this.directory = (0, core_1.dedupeIndex)(next);
|
|
2445
|
+
return removedIds.length;
|
|
2446
|
+
}
|
|
2293
2447
|
compactCacheInMemory() {
|
|
2294
2448
|
const cleaned = (0, core_1.cleanupExpiredPresence)(this.directory, Date.now(), PRESENCE_TTL_MS);
|
|
2295
2449
|
this.directory = (0, core_1.dedupeIndex)(cleaned.state);
|
|
2450
|
+
this.pruneRemoteProfilesInMemory();
|
|
2296
2451
|
return cleaned.removed;
|
|
2297
2452
|
}
|
|
2298
2453
|
async publish(topic, data) {
|
|
@@ -2940,6 +3095,36 @@ async function main() {
|
|
|
2940
3095
|
app.get("/api/runtime/paths", (_req, res) => {
|
|
2941
3096
|
sendOk(res, node.getRuntimePaths());
|
|
2942
3097
|
});
|
|
3098
|
+
app.get("/api/app/update-status", (_req, res) => {
|
|
3099
|
+
sendOk(res, node.getAppUpdateStatus());
|
|
3100
|
+
});
|
|
3101
|
+
app.post("/api/app/update", asyncRoute(async (_req, res) => {
|
|
3102
|
+
const status = node.getAppUpdateStatus();
|
|
3103
|
+
if (!status.update_available || !status.latest_version) {
|
|
3104
|
+
sendOk(res, {
|
|
3105
|
+
started: false,
|
|
3106
|
+
current_version: status.current_version,
|
|
3107
|
+
latest_version: status.latest_version,
|
|
3108
|
+
platform: status.platform,
|
|
3109
|
+
reason: status.check_error || "already_current",
|
|
3110
|
+
}, { message: "Already on the latest version" });
|
|
3111
|
+
return;
|
|
3112
|
+
}
|
|
3113
|
+
sendOk(res, {
|
|
3114
|
+
started: true,
|
|
3115
|
+
current_version: status.current_version,
|
|
3116
|
+
target_version: status.latest_version,
|
|
3117
|
+
platform: status.platform,
|
|
3118
|
+
}, { message: `Updating to ${status.latest_version}` });
|
|
3119
|
+
setTimeout(() => {
|
|
3120
|
+
try {
|
|
3121
|
+
node.startAppUpdate();
|
|
3122
|
+
}
|
|
3123
|
+
catch {
|
|
3124
|
+
// best effort after response has been sent
|
|
3125
|
+
}
|
|
3126
|
+
}, 150);
|
|
3127
|
+
}));
|
|
2943
3128
|
app.put("/api/profile", asyncRoute(async (req, res) => {
|
|
2944
3129
|
const body = req.body;
|
|
2945
3130
|
const tags = Array.isArray(body.tags)
|
|
@@ -27,6 +27,7 @@ if (!root) {
|
|
|
27
27
|
throw new Error("Missing root element: app-root");
|
|
28
28
|
}
|
|
29
29
|
root.innerHTML = appTemplate;
|
|
30
|
+
const APP_UPDATE_SESSION_KEY = 'silicaclaw_pending_updated_version';
|
|
30
31
|
|
|
31
32
|
const i18n = createI18n(TRANSLATIONS);
|
|
32
33
|
const DEFAULT_LOCALE = i18n.DEFAULT_LOCALE;
|
|
@@ -70,6 +71,9 @@ root.innerHTML = appTemplate;
|
|
|
70
71
|
document.getElementById('sidebarToggleBtn').setAttribute('aria-label', t('labels.collapseSidebar'));
|
|
71
72
|
document.querySelector('.sidebar-version').title = t('common.version');
|
|
72
73
|
setText('.sidebar-version__label', t('common.version'));
|
|
74
|
+
document.getElementById('brandUpdateHint').textContent = t('labels.versionChecking');
|
|
75
|
+
document.getElementById('brandCheckUpdateBtn').textContent = t('actions.checkUpdate');
|
|
76
|
+
document.getElementById('brandUpdateBtn').textContent = t('actions.updateNow');
|
|
73
77
|
document.getElementById('integrationStatusBar').textContent = t('social.barStatus', {
|
|
74
78
|
connected: '-',
|
|
75
79
|
mode: '-',
|
|
@@ -319,6 +323,175 @@ root.innerHTML = appTemplate;
|
|
|
319
323
|
toast,
|
|
320
324
|
writeUiCache,
|
|
321
325
|
} = shell;
|
|
326
|
+
let appUpdatePollTimer = null;
|
|
327
|
+
let appUpdateCheckInFlight = false;
|
|
328
|
+
|
|
329
|
+
function setAppUpdateUi({
|
|
330
|
+
hint,
|
|
331
|
+
buttonVisible = false,
|
|
332
|
+
buttonDisabled = false,
|
|
333
|
+
buttonText = t('actions.updateNow'),
|
|
334
|
+
checkVisible = false,
|
|
335
|
+
checkDisabled = false,
|
|
336
|
+
}) {
|
|
337
|
+
const hintEl = document.getElementById('brandUpdateHint');
|
|
338
|
+
const buttonEl = document.getElementById('brandUpdateBtn');
|
|
339
|
+
const checkEl = document.getElementById('brandCheckUpdateBtn');
|
|
340
|
+
if (hintEl) {
|
|
341
|
+
hintEl.textContent = hint;
|
|
342
|
+
hintEl.classList.toggle('hidden', !hint);
|
|
343
|
+
}
|
|
344
|
+
if (checkEl) {
|
|
345
|
+
checkEl.textContent = t('actions.checkUpdate');
|
|
346
|
+
checkEl.classList.toggle('hidden', !checkVisible);
|
|
347
|
+
checkEl.disabled = checkDisabled;
|
|
348
|
+
}
|
|
349
|
+
if (buttonEl) {
|
|
350
|
+
buttonEl.textContent = buttonText;
|
|
351
|
+
buttonEl.classList.toggle('hidden', !buttonVisible);
|
|
352
|
+
buttonEl.disabled = buttonDisabled;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function platformUpdateHint(platform) {
|
|
357
|
+
if (platform === 'darwin') return t('labels.versionPlatformMac');
|
|
358
|
+
if (platform === 'linux') return t('labels.versionPlatformLinux');
|
|
359
|
+
return t('labels.versionPlatformOther');
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async function refreshAppUpdateStatus({ silent = false } = {}) {
|
|
363
|
+
if (appUpdateCheckInFlight) return null;
|
|
364
|
+
appUpdateCheckInFlight = true;
|
|
365
|
+
try {
|
|
366
|
+
const result = await api('/api/app/update-status');
|
|
367
|
+
const status = result.data || {};
|
|
368
|
+
const currentVersion = String(status.current_version || '').trim();
|
|
369
|
+
const latestVersion = String(status.latest_version || '').trim();
|
|
370
|
+
const platformHint = platformUpdateHint(String(status.platform || ''));
|
|
371
|
+
if (currentVersion) {
|
|
372
|
+
document.getElementById('brandVersion').textContent = currentVersion.startsWith('v') ? currentVersion : `v${currentVersion}`;
|
|
373
|
+
}
|
|
374
|
+
if (status.update_available && status.latest_version) {
|
|
375
|
+
setAppUpdateUi({
|
|
376
|
+
hint: `${t('labels.versionUpdateReady', { version: `v${status.latest_version}` })} · ${platformHint}`,
|
|
377
|
+
buttonVisible: true,
|
|
378
|
+
buttonDisabled: false,
|
|
379
|
+
buttonText: t('actions.updateNowVersion', { version: latestVersion.startsWith('v') ? latestVersion : `v${latestVersion}` }),
|
|
380
|
+
checkVisible: true,
|
|
381
|
+
checkDisabled: false,
|
|
382
|
+
});
|
|
383
|
+
} else if (status.check_error) {
|
|
384
|
+
setAppUpdateUi({
|
|
385
|
+
hint: t('labels.versionCheckFailed'),
|
|
386
|
+
buttonVisible: false,
|
|
387
|
+
buttonDisabled: false,
|
|
388
|
+
buttonText: t('actions.updateNow'),
|
|
389
|
+
checkVisible: true,
|
|
390
|
+
checkDisabled: false,
|
|
391
|
+
});
|
|
392
|
+
if (!silent) {
|
|
393
|
+
setFeedback('networkFeedback', t('feedback.appUpdateCheckFailed'), 'warn');
|
|
394
|
+
}
|
|
395
|
+
} else {
|
|
396
|
+
setAppUpdateUi({
|
|
397
|
+
hint: `${t('labels.versionCurrent')} · ${platformHint}`,
|
|
398
|
+
buttonVisible: false,
|
|
399
|
+
buttonDisabled: false,
|
|
400
|
+
buttonText: t('actions.updateNow'),
|
|
401
|
+
checkVisible: true,
|
|
402
|
+
checkDisabled: false,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
return status;
|
|
406
|
+
} catch (_error) {
|
|
407
|
+
setAppUpdateUi({
|
|
408
|
+
hint: t('labels.versionCheckFailed'),
|
|
409
|
+
buttonVisible: false,
|
|
410
|
+
buttonDisabled: false,
|
|
411
|
+
buttonText: t('actions.updateNow'),
|
|
412
|
+
checkVisible: true,
|
|
413
|
+
checkDisabled: false,
|
|
414
|
+
});
|
|
415
|
+
if (!silent) {
|
|
416
|
+
setFeedback('networkFeedback', t('feedback.appUpdateCheckFailed'), 'warn');
|
|
417
|
+
}
|
|
418
|
+
return null;
|
|
419
|
+
} finally {
|
|
420
|
+
appUpdateCheckInFlight = false;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function startAppUpdatePolling(targetVersion) {
|
|
425
|
+
if (appUpdatePollTimer) {
|
|
426
|
+
window.clearInterval(appUpdatePollTimer);
|
|
427
|
+
}
|
|
428
|
+
let attempts = 0;
|
|
429
|
+
appUpdatePollTimer = window.setInterval(async () => {
|
|
430
|
+
attempts += 1;
|
|
431
|
+
const status = await refreshAppUpdateStatus({ silent: true });
|
|
432
|
+
if (status && !status.update_available && String(status.current_version || '') === String(targetVersion || '')) {
|
|
433
|
+
window.clearInterval(appUpdatePollTimer);
|
|
434
|
+
appUpdatePollTimer = null;
|
|
435
|
+
if (targetVersion) {
|
|
436
|
+
window.sessionStorage.setItem(APP_UPDATE_SESSION_KEY, String(targetVersion));
|
|
437
|
+
}
|
|
438
|
+
window.location.reload();
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (attempts >= 24) {
|
|
442
|
+
window.clearInterval(appUpdatePollTimer);
|
|
443
|
+
appUpdatePollTimer = null;
|
|
444
|
+
setAppUpdateUi({
|
|
445
|
+
hint: t('labels.versionCurrent'),
|
|
446
|
+
buttonVisible: false,
|
|
447
|
+
buttonDisabled: false,
|
|
448
|
+
buttonText: t('actions.updateNow'),
|
|
449
|
+
checkVisible: true,
|
|
450
|
+
checkDisabled: false,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}, 5000);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async function triggerAppUpdate() {
|
|
457
|
+
const buttonEl = document.getElementById('brandUpdateBtn');
|
|
458
|
+
setAppUpdateUi({
|
|
459
|
+
hint: t('labels.versionUpdating'),
|
|
460
|
+
buttonVisible: true,
|
|
461
|
+
buttonDisabled: true,
|
|
462
|
+
buttonText: t('labels.versionUpdating'),
|
|
463
|
+
checkVisible: true,
|
|
464
|
+
checkDisabled: true,
|
|
465
|
+
});
|
|
466
|
+
try {
|
|
467
|
+
const result = await api('/api/app/update', { method: 'POST' });
|
|
468
|
+
const data = result.data || {};
|
|
469
|
+
if (!data.started) {
|
|
470
|
+
setAppUpdateUi({
|
|
471
|
+
hint: t('labels.versionCurrent'),
|
|
472
|
+
buttonVisible: false,
|
|
473
|
+
buttonDisabled: false,
|
|
474
|
+
buttonText: t('actions.updateNow'),
|
|
475
|
+
checkVisible: true,
|
|
476
|
+
checkDisabled: false,
|
|
477
|
+
});
|
|
478
|
+
toast(t('feedback.appUpdateLatest'));
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
toast(t('feedback.appUpdateStarted'));
|
|
482
|
+
startAppUpdatePolling(String(data.target_version || ''));
|
|
483
|
+
} catch (error) {
|
|
484
|
+
setAppUpdateUi({
|
|
485
|
+
hint: t('labels.versionCheckFailed'),
|
|
486
|
+
buttonVisible: true,
|
|
487
|
+
buttonDisabled: false,
|
|
488
|
+
buttonText: t('actions.updateNow'),
|
|
489
|
+
checkVisible: true,
|
|
490
|
+
checkDisabled: false,
|
|
491
|
+
});
|
|
492
|
+
setFeedback('networkFeedback', error instanceof Error ? error.message : t('feedback.appUpdateFailed'), 'error');
|
|
493
|
+
}
|
|
494
|
+
}
|
|
322
495
|
setLocale(currentLocale);
|
|
323
496
|
applyStaticTranslations();
|
|
324
497
|
|
|
@@ -427,6 +600,23 @@ root.innerHTML = appTemplate;
|
|
|
427
600
|
document.getElementById('publicDiscoveryHint')?.classList.toggle('hidden', tab !== 'overview');
|
|
428
601
|
if (tab === 'profile' && !profileController.isDirty() && !profileController.isSaving()) {
|
|
429
602
|
refreshProfile().catch(() => {});
|
|
603
|
+
} else if (tab === 'overview') {
|
|
604
|
+
refreshOverview().catch(() => {});
|
|
605
|
+
refreshMessages().catch(() => {});
|
|
606
|
+
} else if (tab === 'agent') {
|
|
607
|
+
refreshOverview().catch(() => {});
|
|
608
|
+
} else if (tab === 'chat') {
|
|
609
|
+
refreshMessages().catch(() => {});
|
|
610
|
+
} else if (tab === 'skills') {
|
|
611
|
+
refreshSkills().catch(() => {});
|
|
612
|
+
} else if (tab === 'network') {
|
|
613
|
+
refreshNetwork().catch(() => {});
|
|
614
|
+
refreshPeers().catch(() => {});
|
|
615
|
+
refreshDiscovery().catch(() => {});
|
|
616
|
+
refreshLogs().catch(() => {});
|
|
617
|
+
} else if (tab === 'social') {
|
|
618
|
+
refreshSocial().catch(() => {});
|
|
619
|
+
refreshMessages().catch(() => {});
|
|
430
620
|
}
|
|
431
621
|
}
|
|
432
622
|
|
|
@@ -453,6 +643,43 @@ root.innerHTML = appTemplate;
|
|
|
453
643
|
const renderLogs = socialController.renderLogs;
|
|
454
644
|
const refreshLogs = socialController.refreshLogs;
|
|
455
645
|
const refreshSkills = socialController.refreshSkills;
|
|
646
|
+
let autoRefreshInFlight = false;
|
|
647
|
+
|
|
648
|
+
async function refreshActiveView() {
|
|
649
|
+
const tasks = [refreshPublicProfilePreview()];
|
|
650
|
+
if (activeTab === 'overview') {
|
|
651
|
+
tasks.push(refreshOverview(), refreshMessages(), refreshSocial());
|
|
652
|
+
} else if (activeTab === 'agent') {
|
|
653
|
+
tasks.push(refreshOverview());
|
|
654
|
+
} else if (activeTab === 'chat') {
|
|
655
|
+
tasks.push(refreshMessages());
|
|
656
|
+
} else if (activeTab === 'skills') {
|
|
657
|
+
tasks.push(refreshSkills());
|
|
658
|
+
} else if (activeTab === 'network') {
|
|
659
|
+
tasks.push(refreshNetwork(), refreshPeers(), refreshDiscovery(), refreshLogs());
|
|
660
|
+
} else if (activeTab === 'social') {
|
|
661
|
+
tasks.push(refreshSocial(), refreshMessages());
|
|
662
|
+
} else if (activeTab === 'profile' && !profileController.isDirty() && !profileController.isSaving()) {
|
|
663
|
+
tasks.push(refreshProfile());
|
|
664
|
+
}
|
|
665
|
+
const results = await Promise.allSettled(tasks);
|
|
666
|
+
const firstError = results.find((result) => result.status === 'rejected');
|
|
667
|
+
if (firstError && firstError.status === 'rejected') {
|
|
668
|
+
setFeedback('networkFeedback', firstError.reason instanceof Error ? firstError.reason.message : t('common.unknownError'), 'error');
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
async function refreshAuto() {
|
|
673
|
+
if (document.hidden || autoRefreshInFlight) {
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
autoRefreshInFlight = true;
|
|
677
|
+
try {
|
|
678
|
+
await refreshActiveView();
|
|
679
|
+
} finally {
|
|
680
|
+
autoRefreshInFlight = false;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
456
683
|
|
|
457
684
|
async function refreshAll() {
|
|
458
685
|
const tasks = [refreshOverview(), refreshNetwork(), refreshSocial(), refreshSkills(), refreshPublicProfilePreview(), refreshMessages()];
|
|
@@ -511,6 +738,27 @@ root.innerHTML = appTemplate;
|
|
|
511
738
|
|
|
512
739
|
applyTheme(localStorage.getItem('silicaclaw_theme_mode') || 'dark');
|
|
513
740
|
hydrateCachedShell();
|
|
741
|
+
document.getElementById('brandUpdateBtn').addEventListener('click', () => {
|
|
742
|
+
triggerAppUpdate().catch(() => {});
|
|
743
|
+
});
|
|
744
|
+
document.getElementById('brandCheckUpdateBtn').addEventListener('click', () => {
|
|
745
|
+
refreshAppUpdateStatus().catch(() => {});
|
|
746
|
+
});
|
|
514
747
|
refreshAll();
|
|
748
|
+
refreshAppUpdateStatus({ silent: true }).catch(() => {});
|
|
749
|
+
const updatedVersion = window.sessionStorage.getItem(APP_UPDATE_SESSION_KEY);
|
|
750
|
+
if (updatedVersion) {
|
|
751
|
+
window.sessionStorage.removeItem(APP_UPDATE_SESSION_KEY);
|
|
752
|
+
toast(t('feedback.appUpdatedTo', { version: updatedVersion.startsWith('v') ? updatedVersion : `v${updatedVersion}` }));
|
|
753
|
+
}
|
|
515
754
|
exportSocialTemplate().catch(() => {});
|
|
516
|
-
|
|
755
|
+
document.addEventListener('visibilitychange', () => {
|
|
756
|
+
if (!document.hidden) {
|
|
757
|
+
refreshAuto().catch(() => {});
|
|
758
|
+
refreshAppUpdateStatus({ silent: true }).catch(() => {});
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
setInterval(refreshAuto, 4000);
|
|
762
|
+
setInterval(() => {
|
|
763
|
+
refreshAppUpdateStatus({ silent: true }).catch(() => {});
|
|
764
|
+
}, 15 * 60 * 1000);
|