diff --git a/lib/project-sessions.js b/lib/project-sessions.js
index b57ca77..2b9cece 100644
--- a/lib/project-sessions.js
+++ b/lib/project-sessions.js
@@ -170,17 +170,55 @@ function attachSessions(ctx) {
// Hidden client-side when active project is a Mate; we still answer here
// because the daemon shouldn't make UX decisions that the client already
// owns. UI gates the surface; daemon stays a data plane.
+ //
+ // Background refresh: respond immediately with the cached list, then kick
+ // off a refresh(). If the catalog changes, re-send to this client only.
+ // Cost is paid exactly when the user opens the agent picker — not on a timer.
if (msg.type === "list_agents") {
var favorites = [];
var recents = [];
try { favorites = agentsFavorites.listFavorites(); recents = agentsFavorites.listRecents(); }
catch (e) { /* favorites are best-effort */ }
+ var cachedAgents = agentsModule.getAll();
sendTo(ws, {
type: "agents_list",
- agents: agentsModule.getAll(),
+ agents: cachedAgents,
favorites: favorites,
recents: recents,
});
+ // Refresh in background; re-send only if the catalog changed.
+ var cachedJson = JSON.stringify(cachedAgents);
+ agentsModule.refresh().then(function () {
+ var freshAgents = agentsModule.getAll();
+ if (JSON.stringify(freshAgents) === cachedJson) return;
+ var favFresh = [];
+ var recFresh = [];
+ try { favFresh = agentsFavorites.listFavorites(); recFresh = agentsFavorites.listRecents(); }
+ catch (e) { /* best-effort */ }
+ sendTo(ws, { type: "agents_list", agents: freshAgents, favorites: favFresh, recents: recFresh });
+ }).catch(function (e) {
+ console.error("[project-sessions] background agent refresh failed:", e && e.message ? e.message : e);
+ });
+ return true;
+ }
+
+ // refresh_agents — explicit on-demand refresh triggered from the settings UI.
+ // Re-runs SDK discovery, then broadcasts the updated catalog to ALL connected
+ // clients. Responds immediately with ok:true (async broadcast follows).
+ if (msg.type === "refresh_agents") {
+ sendTo(ws, { type: "refresh_agents_result", ok: true });
+ agentsModule.refresh().then(function () {
+ var freshAgents = agentsModule.getAll();
+ var favFresh = [];
+ var recFresh = [];
+ try { favFresh = agentsFavorites.listFavorites(); recFresh = agentsFavorites.listRecents(); }
+ catch (e) { /* best-effort */ }
+ send({ type: "agents_list", agents: freshAgents, favorites: favFresh, recents: recFresh });
+ send({ type: "toast", level: "info", message: "Agent catalog refreshed — " + freshAgents.length + " agent" + (freshAgents.length === 1 ? "" : "s") + " found." });
+ }).catch(function (e) {
+ console.error("[project-sessions] refresh_agents failed:", e && e.message ? e.message : e);
+ send({ type: "toast", level: "error", message: "Agent catalog refresh failed." });
+ });
return true;
}
diff --git a/lib/public/index.html b/lib/public/index.html
index e709d02..fb69694 100644
--- a/lib/public/index.html
+++ b/lib/public/index.html
@@ -1124,6 +1124,17 @@
Server Status
-
+
Agent Catalog
+
+
+
+
Re-scans for installed agents and updates the picker for all connected users. Happens automatically when you open the agent picker — use this to push an update immediately.
+
+
+
+
+
+
diff --git a/lib/public/modules/app-messages.js b/lib/public/modules/app-messages.js
index c8f622c..eda628f 100644
--- a/lib/public/modules/app-messages.js
+++ b/lib/public/modules/app-messages.js
@@ -23,7 +23,7 @@ import { showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundE
import { handleFsList, handleFsRead, handleFileChanged, handleDirChanged, handleFileHistory, handleGitDiff, handleFileAt, refreshIfOpen, handleFsSearch } from './filebrowser.js';
import { getPendingNavigate, peekPendingNavigate } from './pending-navigate.js';
import { isProjectSettingsOpen, refreshProjectSettingsModels, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, handleProjectSharedEnv, handleProjectSharedEnvSaved, handleProjectOwnerChanged, updateLiteVisibility, handleLiteProjectStatus, handleLiteEnrollResult, handleLiteUnenrollResult } from './project-settings.js';
-import { updateSettingsModels, updateSettingsStats, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite, updateSsLiteVisibility } from './server-settings.js';
+import { updateSettingsModels, updateSettingsStats, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleAutoContinueChanged, handleRefreshAgentsResult, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite, updateSsLiteVisibility } from './server-settings.js';
import { handleTermList, handleTermCreated, sendTerminalCommand, handleTermOutput, handleTermResized, handleTermExited, handleTermClosed } from './terminal.js';
import { updateTerminalList, handleContextSourcesState } from './context-sources.js';
import { handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted } from './sticky-notes.js';
@@ -1348,6 +1348,10 @@ export function processMessage(msg) {
handleAutoContinueChanged(msg);
break;
+ case "refresh_agents_result":
+ handleRefreshAgentsResult(msg);
+ break;
+
case "restart_server_result":
handleRestartResult(msg);
break;
diff --git a/lib/public/modules/server-settings.js b/lib/public/modules/server-settings.js
index 36ab24b..de679af 100644
--- a/lib/public/modules/server-settings.js
+++ b/lib/public/modules/server-settings.js
@@ -248,6 +248,19 @@ export function initServerSettings(appCtx) {
});
}
+ // Refresh agent catalog
+ var refreshAgentsBtn = document.getElementById("settings-refresh-agents-btn");
+ if (refreshAgentsBtn) {
+ refreshAgentsBtn.addEventListener("click", function () {
+ var ws = ctx.ws;
+ if (ws && ws.readyState === 1) {
+ refreshAgentsBtn.disabled = true;
+ refreshAgentsBtn.innerHTML = 'Refreshing...';
+ ws.send(JSON.stringify({ type: "refresh_agents" }));
+ }
+ });
+ }
+
// Restart server
var restartBtn = document.getElementById("settings-restart-btn");
if (restartBtn) {
@@ -770,6 +783,16 @@ export function handleAutoContinueChanged(msg) {
// Auto-continue is now per-user; server broadcast no longer updates UI
}
+export function handleRefreshAgentsResult(msg) {
+ var btn = document.getElementById("settings-refresh-agents-btn");
+ if (!btn) return;
+ // Reset button regardless of ok — the toast (broadcast from server) carries
+ // the outcome. If not ok, we just unblock the button silently.
+ btn.disabled = false;
+ btn.innerHTML = 'Refresh agents';
+ refreshIcons();
+}
+
export function handleRestartResult(msg) {
var restartBtn = document.getElementById("settings-restart-btn");
var errorEl = document.getElementById("settings-restart-error");