clay-server 2.34.1-beta.1 → 2.34.1-beta.3

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.
@@ -103,30 +103,12 @@ function onConnected() {
103
103
 
104
104
  // Session restore is now server-driven (user-presence.json).
105
105
  // Mate DM restore is also server-driven via "restore_mate_dm" message.
106
- // Fallback: if server doesn't restore DM within 2s, try localStorage
107
- var savedDm = null;
108
- try { savedDm = localStorage.getItem("clay-active-dm"); } catch (e) {}
109
- if (savedDm && !store.get('dmMode') && !store.get('mateProjectSlug')) {
110
- var dmFallbackTimer = setTimeout(function () {
111
- if (!store.get('dmMode') && savedDm) {
112
- console.log("[dm-restore] Server did not restore DM, using localStorage fallback:", savedDm);
113
- openDm(savedDm);
114
- }
115
- }, 2000);
116
- // Cancel fallback if server restores DM first
117
- var patchedOnce = false;
118
- var checkRestore = function (evt) {
119
- try {
120
- var d = JSON.parse(evt.data);
121
- if (d.type === "restore_mate_dm" && !patchedOnce) {
122
- patchedOnce = true;
123
- clearTimeout(dmFallbackTimer);
124
- }
125
- } catch (e) {}
126
- };
127
- ws.addEventListener("message", checkRestore);
128
- setTimeout(function () { ws.removeEventListener("message", checkRestore); }, 3000);
129
- }
106
+ // Previously there was a 2s localStorage fallback that auto-called
107
+ // openDm(savedDm) on every reconnect. That fallback re-opened stale
108
+ // mate DMs on every refresh / project switch and was the root cause
109
+ // of the skill-install modal popping unprompted. Server-driven restore
110
+ // is authoritative drop the client-side fallback entirely.
111
+ try { localStorage.removeItem("clay-active-dm"); } catch (e) {}
130
112
  // Safety: clear returningFromMateDm after initial messages settle
131
113
  if (store.get('returningFromMateDm')) {
132
114
  setTimeout(function () {
@@ -21,7 +21,6 @@ import { closeTerminal } from './terminal.js';
21
21
  import { openMobileSheet, setMobileSheetMateData } from './sidebar-mobile.js';
22
22
  import { getProfileLang } from './profile.js';
23
23
  import { isSchedulerOpen, closeScheduler } from './scheduler.js';
24
- import { requireClayMateInterview } from './app-skills-install.js';
25
24
  import { syncResizeHandles } from './sidebar.js';
26
25
 
27
26
  var MATE_ONBOARDING_KEY = "clay-mate-onboarding-shown";
@@ -77,13 +76,15 @@ export function openDm(targetUserId) {
77
76
  if (!ws || ws.readyState !== 1) return;
78
77
  // Persist DM state for refresh recovery
79
78
  try { localStorage.setItem("clay-active-dm", targetUserId); } catch (e) {}
80
- // Check mate skill updates before opening mate DM
79
+ // Opening an existing mate DM does not require the clay-mate-interview
80
+ // skill — that skill is only used during new mate creation / reshaping.
81
+ // Showing onboarding + gating a skill version check here caused the
82
+ // "Skill Installation Required" modal to pop on every refresh / project
83
+ // switch via the localStorage DM-restore fallback in app-connection.js.
81
84
  if (typeof targetUserId === "string" && targetUserId.indexOf("mate_") === 0) {
82
85
  showMateOnboarding(function () {
83
- requireClayMateInterview(function () {
84
- var ws2 = getWs();
85
- if (ws2) ws2.send(JSON.stringify({ type: "dm_open", targetUserId: targetUserId }));
86
- });
86
+ var ws2 = getWs();
87
+ if (ws2) ws2.send(JSON.stringify({ type: "dm_open", targetUserId: targetUserId }));
87
88
  });
88
89
  return;
89
90
  }
@@ -10,7 +10,7 @@ import { iconHtml, refreshIcons } from './icons.js';
10
10
  import { userAvatarUrl } from './avatar.js';
11
11
  import { closeToolGroup } from './tools.js';
12
12
  import { showImageModal, showPasteModal } from './app-misc.js';
13
- import { sendMessage } from './input.js';
13
+ import { sendMessage, hasSendableContent } from './input.js';
14
14
  import { getChatLayout } from './theme.js';
15
15
  import { getScheduledMsgEl } from './app-rate-limit.js';
16
16
 
@@ -547,9 +547,11 @@ export function getGhostSuggestion() {
547
547
 
548
548
  export function showSuggestionChips(suggestion) {
549
549
  if (!suggestion || store.get('processing')) return;
550
- var inputEl = getInputEl();
551
- // Only show ghost text if input is empty
552
- if (inputEl && inputEl.value.trim()) return;
550
+ // Only show ghost text when there is no sendable content — typed text,
551
+ // pending pastes, pending images, or pending files all suppress the
552
+ // suggestion so Enter can't accidentally send it instead of the user's
553
+ // actual attached content.
554
+ if (hasSendableContent()) return;
553
555
  _ghostSuggestionText = suggestion;
554
556
  var ghostEl = document.getElementById("ghost-suggestion");
555
557
  if (!ghostEl) return;
@@ -197,10 +197,14 @@ export function requireSkills(opts, cb) {
197
197
  .then(function (res) { return res.json(); })
198
198
  .then(function (data) {
199
199
  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).
200
204
  var actionable = [];
201
205
  for (var i = 0; i < results.length; i++) {
202
206
  var r = results[i];
203
- if (r.status === "missing" || r.status === "outdated") {
207
+ if (r.status === "missing") {
204
208
  var orig = null;
205
209
  for (var j = 0; j < opts.skills.length; j++) {
206
210
  if (opts.skills[j].name === r.name) { orig = opts.skills[j]; break; }
@@ -360,6 +360,9 @@ function renderInputPreviews() {
360
360
  return;
361
361
  }
362
362
  bar.classList.add("visible");
363
+ // Hide any ghost suggestion as soon as attached content appears — Enter
364
+ // must not silently swallow the user's paste/image/file.
365
+ if (ctx && ctx.hideSuggestionChips) ctx.hideSuggestionChips();
363
366
 
364
367
  // Image thumbnails
365
368
  for (var i = 0; i < pendingImages.length; i++) {
@@ -1040,9 +1043,13 @@ export function initInput(_ctx) {
1040
1043
  return;
1041
1044
  }
1042
1045
  e.preventDefault();
1043
- // If input is empty but ghost suggestion is showing, adopt it
1046
+ // If input has no sendable content but ghost suggestion is showing, adopt it.
1047
+ // Use hasSendableContent() instead of checking inputEl.value alone so that
1048
+ // pending images, pastes, or files block the ghost-text adoption — otherwise
1049
+ // pressing Enter with only a pasted image/block queued would send the
1050
+ // suggestion instead of the user's actual content.
1044
1051
  var ghost = ctx.getGhostSuggestion ? ctx.getGhostSuggestion() : "";
1045
- if (!ctx.inputEl.value.trim() && ghost) {
1052
+ if (!hasSendableContent() && ghost) {
1046
1053
  ctx.inputEl.value = ghost;
1047
1054
  if (ctx.hideSuggestionChips) ctx.hideSuggestionChips();
1048
1055
  }
@@ -87,6 +87,36 @@ function attachPreferences(deps) {
87
87
  }
88
88
 
89
89
  // --- Deleted built-in mate keys tracking ---
90
+ //
91
+ // In single-user mode there is no users.json, so the user row lookup
92
+ // below returns nothing and the key is silently dropped. That made
93
+ // "Remove mate" in the sidebar picker a no-op: the key was never
94
+ // persisted, ensureBuiltinMates re-created the mate on next mate_list,
95
+ // and the user could not actually get rid of built-in mates.
96
+ //
97
+ // Fallback: when the user record isn't found (single-user mode), read
98
+ // and write deletedBuiltinKeys on daemon.json via lib/config.js. This
99
+ // preserves multi-user behavior (users.json row still wins) while
100
+ // giving single-user deploys a place to persist the setting.
101
+
102
+ function loadSingleUserDeletedKeys() {
103
+ try {
104
+ var config = require("./config");
105
+ var cfg = config.loadConfig() || {};
106
+ return Array.isArray(cfg.deletedBuiltinKeys) ? cfg.deletedBuiltinKeys : [];
107
+ } catch (e) {
108
+ return [];
109
+ }
110
+ }
111
+
112
+ function saveSingleUserDeletedKeys(keys) {
113
+ try {
114
+ var config = require("./config");
115
+ var cfg = config.loadConfig() || {};
116
+ cfg.deletedBuiltinKeys = keys;
117
+ config.saveConfig(cfg);
118
+ } catch (e) {}
119
+ }
90
120
 
91
121
  function getDeletedBuiltinKeys(userId) {
92
122
  var data = loadUsers();
@@ -95,7 +125,7 @@ function attachPreferences(deps) {
95
125
  return data.users[i].deletedBuiltinKeys || [];
96
126
  }
97
127
  }
98
- return [];
128
+ return loadSingleUserDeletedKeys();
99
129
  }
100
130
 
101
131
  function addDeletedBuiltinKey(userId, key) {
@@ -110,6 +140,12 @@ function attachPreferences(deps) {
110
140
  return;
111
141
  }
112
142
  }
143
+ // Single-user fallback
144
+ var keys = loadSingleUserDeletedKeys();
145
+ if (keys.indexOf(key) === -1) {
146
+ keys.push(key);
147
+ saveSingleUserDeletedKeys(keys);
148
+ }
113
149
  }
114
150
 
115
151
  function removeDeletedBuiltinKey(userId, key) {
@@ -124,6 +160,12 @@ function attachPreferences(deps) {
124
160
  return;
125
161
  }
126
162
  }
163
+ // Single-user fallback
164
+ var keys = loadSingleUserDeletedKeys();
165
+ var filtered = keys.filter(function (k) { return k !== key; });
166
+ if (filtered.length !== keys.length) {
167
+ saveSingleUserDeletedKeys(filtered);
168
+ }
127
169
  }
128
170
 
129
171
  // --- Per-user chat layout setting ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.34.1-beta.1",
3
+ "version": "2.34.1-beta.3",
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",
@@ -38,7 +38,7 @@
38
38
  "dependencies": {
39
39
  "@anthropic-ai/claude-agent-sdk": "^0.2.112",
40
40
  "@lydell/node-pty": "^1.2.0-beta.3",
41
- "@openai/codex": "^0.121.0",
41
+ "@openai/codex": "^0.124.0",
42
42
  "imapflow": "^1.3.1",
43
43
  "nodemailer": "^6.10.1",
44
44
  "qrcode-terminal": "^0.12.0",