clay-server 2.34.1-beta.3 → 2.34.1-beta.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.
@@ -356,6 +356,16 @@ export function processMessage(msg) {
356
356
  break;
357
357
 
358
358
  case "model_info": {
359
+ // Drop stale model_info from a vendor that doesn't match the active
360
+ // session's vendor. On high-latency connections, the server's default-
361
+ // adapter model_info can arrive after session_switched has already
362
+ // bound the session to a different vendor. Applying it would replace
363
+ // currentModels with the wrong vendor's list and trigger app-panels
364
+ // to request models for the "wrong" vendor, which feeds back into a
365
+ // ping-pong loop of vendor flapping. See issue #336.
366
+ var _curV = store.get('currentVendor');
367
+ if (msg.vendor && _curV && msg.vendor !== _curV) break;
368
+
359
369
  var _modelVal = msg.model;
360
370
  if (_modelVal && typeof _modelVal === "object") _modelVal = _modelVal.value || _modelVal.displayName || "";
361
371
  var _miUpdate = { currentModels: msg.models || [] };
@@ -530,8 +540,12 @@ export function processMessage(msg) {
530
540
  if (!store.get('vendorSelectionLocked') || msg.hasHistory) {
531
541
  store.set({ currentVendor: msg.vendor });
532
542
  }
543
+ // Sessions with history have their vendor structurally bound to
544
+ // the session: lock so a late-arriving default-adapter model_info
545
+ // can't flip the UI back. Previously this branch unlocked, which
546
+ // is what allowed the feedback loop in issue #336.
533
547
  if (msg.hasHistory) {
534
- store.set({ vendorSelectionLocked: false });
548
+ store.set({ vendorSelectionLocked: true });
535
549
  }
536
550
  } else if (msg.hasHistory) {
537
551
  // Existing session without explicit vendor: reset to claude
@@ -18,6 +18,11 @@ var pendingSkillInstalls = [];
18
18
  var skillInstallCallback = null;
19
19
  var skillInstalling = false;
20
20
  var skillInstallDone = false;
21
+ // True when the modal contains only "outdated" skills (no "missing"). In that
22
+ // case the user is allowed to skip the update and continue with the original
23
+ // action; the dismissal is remembered for the rest of the browser session so
24
+ // we don't re-prompt on every reconnect / DM open.
25
+ var skillInstallSkippable = false;
21
26
 
22
27
  export function initSkillInstall() {
23
28
  skillInstallModal = document.getElementById("skill-install-modal");
@@ -28,8 +33,8 @@ export function initSkillInstall() {
28
33
  skillInstallCancel = document.getElementById("skill-install-cancel");
29
34
  skillInstallStatus = document.getElementById("skill-install-status");
30
35
 
31
- skillInstallCancel.addEventListener("click", hideSkillInstallModal);
32
- skillInstallModal.querySelector(".confirm-backdrop").addEventListener("click", hideSkillInstallModal);
36
+ skillInstallCancel.addEventListener("click", onSkillInstallDismiss);
37
+ skillInstallModal.querySelector(".confirm-backdrop").addEventListener("click", onSkillInstallDismiss);
33
38
 
34
39
  skillInstallOk.addEventListener("click", function () {
35
40
  if (skillInstallDone) {
@@ -106,6 +111,11 @@ function renderSkillInstallDialog(opts, missing) {
106
111
  if (hasMissing && hasOutdated) btnLabel = "Install / Update";
107
112
  skillInstallOk.textContent = btnLabel;
108
113
  skillInstallOk.className = "confirm-btn confirm-delete";
114
+ // When only outdated skills are pending, the feature still works on the
115
+ // current version. Let the user skip and continue; surface that as a
116
+ // distinct cancel label so it's clear this won't abort the action.
117
+ skillInstallSkippable = hasOutdated && !hasMissing;
118
+ skillInstallCancel.textContent = skillInstallSkippable ? "Skip" : "Cancel";
109
119
  skillInstallModal.classList.remove("hidden");
110
120
  }
111
121
 
@@ -115,6 +125,45 @@ function hideSkillInstallModal() {
115
125
  pendingSkillInstalls = [];
116
126
  skillInstalling = false;
117
127
  skillInstallDone = false;
128
+ skillInstallSkippable = false;
129
+ }
130
+
131
+ // Cancel/backdrop click handler. For "missing" skills the feature genuinely
132
+ // cannot run without them, so dismiss = drop the callback (existing behavior).
133
+ // For "outdated"-only prompts the feature still works on the old version, so
134
+ // dismiss = remember the choice for this browser session and proceed with cb.
135
+ function onSkillInstallDismiss() {
136
+ if (skillInstallSkippable) {
137
+ rememberOutdatedDismissal(pendingSkillInstalls);
138
+ var proceedCb = skillInstallCallback;
139
+ skillInstallCallback = null;
140
+ hideSkillInstallModal();
141
+ if (proceedCb) proceedCb();
142
+ return;
143
+ }
144
+ hideSkillInstallModal();
145
+ }
146
+
147
+ function dismissalKey(name, remoteVersion) {
148
+ return "clay-skill-update-dismissed:" + name + ":" + (remoteVersion || "");
149
+ }
150
+
151
+ function rememberOutdatedDismissal(items) {
152
+ try {
153
+ for (var i = 0; i < items.length; i++) {
154
+ var it = items[i];
155
+ if (it.status !== "outdated") continue;
156
+ sessionStorage.setItem(dismissalKey(it.name, it.remoteVersion), "1");
157
+ }
158
+ } catch (e) {}
159
+ }
160
+
161
+ function isOutdatedDismissed(name, remoteVersion) {
162
+ try {
163
+ return sessionStorage.getItem(dismissalKey(name, remoteVersion)) === "1";
164
+ } catch (e) {
165
+ return false;
166
+ }
118
167
  }
119
168
 
120
169
  function updateSkillInstallProgress(done, total) {
@@ -197,29 +246,31 @@ export function requireSkills(opts, cb) {
197
246
  .then(function (res) { return res.json(); })
198
247
  .then(function (data) {
199
248
  var results = data.results || [];
200
- // Only "missing" skills block the feature. "outdated" skills already
201
- // function an available update should not hard-gate with a modal
202
- // every time a user opens a DM or refreshes the page. Callers can
203
- // surface outdated versions elsewhere (e.g. settings / notifications).
249
+ // "missing" skills hard-block: the feature cannot run without them.
250
+ // "outdated" skills are surfaced too because skills look like a single
251
+ // user-facing feature but call vendor-specific tools (codex vs claude)
252
+ // internally version drift breaks that consistency. The modal is
253
+ // skippable for outdated-only cases, and a session-scoped dismissal
254
+ // suppresses re-prompts so reconnect/refresh doesn't re-fire it.
204
255
  var actionable = [];
205
256
  for (var i = 0; i < results.length; i++) {
206
257
  var r = results[i];
207
- if (r.status === "missing") {
208
- var orig = null;
209
- for (var j = 0; j < opts.skills.length; j++) {
210
- if (opts.skills[j].name === r.name) { orig = opts.skills[j]; break; }
211
- }
212
- if (!orig) continue;
213
- actionable.push({
214
- name: r.name,
215
- url: orig.url,
216
- scope: orig.scope || "global",
217
- installed: false,
218
- status: r.status,
219
- installedVersion: r.installedVersion,
220
- remoteVersion: r.remoteVersion,
221
- });
258
+ if (r.status !== "missing" && r.status !== "outdated") continue;
259
+ if (r.status === "outdated" && isOutdatedDismissed(r.name, r.remoteVersion)) continue;
260
+ var orig = null;
261
+ for (var j = 0; j < opts.skills.length; j++) {
262
+ if (opts.skills[j].name === r.name) { orig = opts.skills[j]; break; }
222
263
  }
264
+ if (!orig) continue;
265
+ actionable.push({
266
+ name: r.name,
267
+ url: orig.url,
268
+ scope: orig.scope || "global",
269
+ installed: false,
270
+ status: r.status,
271
+ installedVersion: r.installedVersion,
272
+ remoteVersion: r.remoteVersion,
273
+ });
223
274
  }
224
275
  if (actionable.length === 0) { cb(); return; }
225
276
  pendingSkillInstalls = actionable;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.34.1-beta.3",
3
+ "version": "2.34.1-beta.4",
4
4
  "description": "Self-hosted Claude Code in your browser. Multi-session, multi-user, push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",