clay-server 2.10.0 → 2.11.0-beta.10

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.
@@ -1651,7 +1651,7 @@ function showProjectCtxMenu(anchorEl, slug, name, icon, position) {
1651
1651
  settingsItem.addEventListener("click", function (e) {
1652
1652
  e.stopPropagation();
1653
1653
  closeProjectCtxMenu();
1654
- openProjectSettings(slug, { slug: slug, name: name, icon: icon });
1654
+ openProjectSettings(slug, { slug: slug, name: name, icon: icon, projectOwnerId: ctx.projectOwnerId });
1655
1655
  });
1656
1656
  menu.appendChild(settingsItem);
1657
1657
 
@@ -2087,7 +2087,8 @@ export function renderIconStrip(projects, currentSlug) {
2087
2087
  for (var i = 0; i < projects.length; i++) {
2088
2088
  var p = projects[i];
2089
2089
  var el = document.createElement("a");
2090
- el.className = "icon-strip-item" + (p.slug === currentSlug ? " active" : "");
2090
+ var isActive = p.slug === currentSlug && !currentDmUserId;
2091
+ el.className = "icon-strip-item" + (isActive ? " active" : "");
2091
2092
  el.href = "/p/" + p.slug + "/";
2092
2093
  el.dataset.slug = p.slug;
2093
2094
 
@@ -2144,7 +2145,7 @@ export function renderIconStrip(projects, currentSlug) {
2144
2145
  // Update home icon active state
2145
2146
  var homeIcon = document.querySelector(".icon-strip-home");
2146
2147
  if (homeIcon) {
2147
- if (!currentSlug || projects.length === 0) {
2148
+ if ((!currentSlug || projects.length === 0) && !currentDmUserId) {
2148
2149
  homeIcon.classList.add("active");
2149
2150
  } else {
2150
2151
  homeIcon.classList.remove("active");
@@ -2193,13 +2194,118 @@ function renderProjectList(projects, currentSlug) {
2193
2194
 
2194
2195
  export function getEmojiCategories() { return EMOJI_CATEGORIES; }
2195
2196
 
2197
+ // --- User strip (DM targets) ---
2198
+ var cachedAllUsers = [];
2199
+ var cachedOnlineUserIds = [];
2200
+ var currentDmUserId = null;
2201
+
2202
+ export function renderUserStrip(allUsers, onlineUserIds, myUserId) {
2203
+ cachedAllUsers = allUsers || [];
2204
+ cachedOnlineUserIds = onlineUserIds || [];
2205
+ var container = document.getElementById("icon-strip-users");
2206
+ if (!container) return;
2207
+
2208
+ // Filter out self, only show other users
2209
+ var others = cachedAllUsers.filter(function (u) { return u.id !== myUserId; });
2210
+
2211
+ // Hide section if no other users (single-user mode or alone)
2212
+ if (others.length === 0) {
2213
+ container.innerHTML = "";
2214
+ container.classList.add("hidden");
2215
+ return;
2216
+ }
2217
+
2218
+ container.classList.remove("hidden");
2219
+ container.innerHTML = "";
2220
+
2221
+ for (var i = 0; i < others.length; i++) {
2222
+ (function (u) {
2223
+ var el = document.createElement("div");
2224
+ el.className = "icon-strip-user";
2225
+ el.dataset.userId = u.id;
2226
+ if (u.id === currentDmUserId) el.classList.add("active");
2227
+ if (onlineUserIds.indexOf(u.id) !== -1) el.classList.add("online");
2228
+
2229
+ var pill = document.createElement("span");
2230
+ pill.className = "icon-strip-pill";
2231
+ el.appendChild(pill);
2232
+
2233
+ var avatar = document.createElement("img");
2234
+ avatar.className = "icon-strip-user-avatar";
2235
+ avatar.src = "https://api.dicebear.com/9.x/" + (u.avatarStyle || "thumbs") + "/svg?seed=" + encodeURIComponent(u.avatarSeed || u.username) + "&size=34";
2236
+ avatar.alt = u.displayName;
2237
+ el.appendChild(avatar);
2238
+
2239
+ var onlineDot = document.createElement("span");
2240
+ onlineDot.className = "icon-strip-user-online";
2241
+ el.appendChild(onlineDot);
2242
+
2243
+ var badge = document.createElement("span");
2244
+ badge.className = "icon-strip-user-badge";
2245
+ badge.dataset.userId = u.id;
2246
+ el.appendChild(badge);
2247
+
2248
+ // Tooltip
2249
+ el.addEventListener("mouseenter", function () { showIconTooltip(el, u.displayName); });
2250
+ el.addEventListener("mouseleave", hideIconTooltip);
2251
+
2252
+ // Click: open DM
2253
+ el.addEventListener("click", function () {
2254
+ if (ctx.openDm) ctx.openDm(u.id);
2255
+ });
2256
+
2257
+ container.appendChild(el);
2258
+ })(others[i]);
2259
+ }
2260
+
2261
+ // Invite button at bottom of user strip (hidden for now)
2262
+ // var inviteBtn = document.createElement("button");
2263
+ // inviteBtn.className = "icon-strip-invite";
2264
+ // inviteBtn.innerHTML = iconHtml("user-plus");
2265
+ // inviteBtn.addEventListener("click", function () { triggerShare(); });
2266
+ // inviteBtn.addEventListener("mouseenter", function () { showIconTooltip(inviteBtn, "Invite"); });
2267
+ // inviteBtn.addEventListener("mouseleave", hideIconTooltip);
2268
+ // container.appendChild(inviteBtn);
2269
+ refreshIcons();
2270
+ }
2271
+
2272
+ export function setCurrentDmUser(userId) {
2273
+ currentDmUserId = userId;
2274
+ // Update active state on user icons immediately
2275
+ var container = document.getElementById("icon-strip-users");
2276
+ if (!container) return;
2277
+ var items = container.querySelectorAll(".icon-strip-user");
2278
+ for (var i = 0; i < items.length; i++) {
2279
+ if (items[i].dataset.userId === userId) {
2280
+ items[i].classList.add("active");
2281
+ } else {
2282
+ items[i].classList.remove("active");
2283
+ }
2284
+ }
2285
+ }
2286
+
2287
+ export function updateDmBadge(userId, count) {
2288
+ var badge = document.querySelector('.icon-strip-user-badge[data-user-id="' + userId + '"]');
2289
+ if (!badge) return;
2290
+ if (count > 0) {
2291
+ badge.textContent = count > 99 ? "99+" : String(count);
2292
+ badge.classList.add("has-unread");
2293
+ } else {
2294
+ badge.textContent = "";
2295
+ badge.classList.remove("has-unread");
2296
+ }
2297
+ }
2298
+
2196
2299
  export function initIconStrip(_ctx) {
2197
2300
  var addBtn = document.getElementById("icon-strip-add");
2198
2301
  if (addBtn) {
2199
2302
  addBtn.addEventListener("click", function () {
2200
- // Reuse existing add-project modal
2201
- var modal = _ctx.$("add-project-modal");
2202
- if (modal) modal.classList.remove("hidden");
2303
+ if (_ctx.openAddProjectModal) {
2304
+ _ctx.openAddProjectModal();
2305
+ } else {
2306
+ var modal = _ctx.$("add-project-modal");
2307
+ if (modal) modal.classList.remove("hidden");
2308
+ }
2203
2309
  });
2204
2310
  addBtn.addEventListener("mouseenter", function () { showIconTooltip(addBtn, "Add project"); });
2205
2311
  addBtn.addEventListener("mouseleave", hideIconTooltip);
@@ -225,6 +225,11 @@ function createXtermForTab(tab) {
225
225
  xterm.loadAddon(fitAddon);
226
226
  }
227
227
 
228
+ // Web links addon: make URLs clickable
229
+ if (typeof WebLinksAddon !== "undefined") {
230
+ xterm.loadAddon(new WebLinksAddon.WebLinksAddon());
231
+ }
232
+
228
233
  // Create a container div for this tab's terminal
229
234
  var bodyEl = document.createElement("div");
230
235
  bodyEl.className = "terminal-tab-body";
@@ -239,16 +244,35 @@ function createXtermForTab(tab) {
239
244
  }
240
245
  });
241
246
 
242
- // Ctrl+V paste for Firefox (Firefox blocks xterm.js clipboard access)
243
- bodyEl.addEventListener("keydown", function (e) {
247
+ // Cmd/Ctrl+C copy and Cmd/Ctrl+V paste: intercept before xterm swallows the event
248
+ xterm.attachCustomKeyEventHandler(function (e) {
249
+ if (e.type !== "keydown") return true;
250
+ // Cmd/Ctrl+C: copy selection if any, otherwise send SIGINT
251
+ if ((e.ctrlKey || e.metaKey) && e.key === "c") {
252
+ var sel = xterm.getSelection();
253
+ if (sel) {
254
+ if (navigator.clipboard && navigator.clipboard.writeText) {
255
+ navigator.clipboard.writeText(sel).catch(function () {});
256
+ }
257
+ return false; // prevent xterm from handling
258
+ }
259
+ // No selection on macOS Cmd+C: do nothing (not SIGINT)
260
+ if (e.metaKey) return false;
261
+ }
262
+ // Cmd/Ctrl+V: let browser handle paste event
244
263
  if ((e.ctrlKey || e.metaKey) && e.key === "v") {
264
+ return false; // let browser fire paste event
265
+ }
266
+ return true;
267
+ });
268
+
269
+ // Handle paste via browser paste event (works for Cmd+V, Ctrl+V, right-click paste)
270
+ bodyEl.addEventListener("paste", function (e) {
271
+ var text = (e.clipboardData || window.clipboardData).getData("text");
272
+ if (text) {
245
273
  e.preventDefault();
246
- if (navigator.clipboard && navigator.clipboard.readText) {
247
- navigator.clipboard.readText().then(function (text) {
248
- if (text && ctx.ws && ctx.connected) {
249
- ctx.ws.send(JSON.stringify({ type: "term_input", id: tab.id, data: text }));
250
- }
251
- }).catch(function () { /* permission denied or not available */ });
274
+ if (ctx.ws && ctx.connected) {
275
+ ctx.ws.send(JSON.stringify({ type: "term_input", id: tab.id, data: text }));
252
276
  }
253
277
  }
254
278
  });
@@ -598,10 +622,24 @@ function showTermCtxMenu(e, tab) {
598
622
  var menu = document.createElement("div");
599
623
  menu.className = "term-ctx-menu";
600
624
 
601
- // Copy
625
+ // Copy selection
626
+ var sel = tab.xterm ? tab.xterm.getSelection() : "";
627
+ if (sel) {
628
+ var copySelItem = document.createElement("button");
629
+ copySelItem.className = "term-ctx-item";
630
+ copySelItem.innerHTML = iconHtml("copy") + " <span>Copy</span>";
631
+ copySelItem.addEventListener("click", function (ev) {
632
+ ev.stopPropagation();
633
+ closeTermCtxMenu();
634
+ if (sel) copyToClipboard(sel);
635
+ });
636
+ menu.appendChild(copySelItem);
637
+ }
638
+
639
+ // Copy entire console
602
640
  var copyItem = document.createElement("button");
603
641
  copyItem.className = "term-ctx-item";
604
- copyItem.innerHTML = iconHtml("clipboard-copy") + " <span>Copy Terminal</span>";
642
+ copyItem.innerHTML = iconHtml("clipboard-copy") + " <span>Copy Console</span>";
605
643
  copyItem.addEventListener("click", function (ev) {
606
644
  ev.stopPropagation();
607
645
  closeTermCtxMenu();
@@ -693,6 +693,207 @@ export function markPermissionCancelled(requestId) {
693
693
  delete pendingPermissions[requestId];
694
694
  }
695
695
 
696
+ // --- MCP elicitation rendering ---
697
+
698
+ var pendingElicitations = {};
699
+
700
+ export function renderElicitationRequest(msg) {
701
+ if (pendingElicitations[msg.requestId]) return;
702
+ ctx.finalizeAssistantBlock();
703
+ stopThinking();
704
+ closeToolGroup();
705
+
706
+ var container = document.createElement("div");
707
+ container.className = "permission-container elicitation-container";
708
+ container.dataset.requestId = msg.requestId;
709
+
710
+ // Header
711
+ var header = document.createElement("div");
712
+ header.className = "permission-header";
713
+ header.innerHTML =
714
+ '<span class="permission-icon">' + iconHtml("key") + '</span>' +
715
+ '<span class="permission-title">' + escapeHtml(msg.serverName || "MCP Server") + ' requests input</span>';
716
+
717
+ // Body
718
+ var body = document.createElement("div");
719
+ body.className = "permission-body";
720
+
721
+ if (msg.message) {
722
+ var messageEl = document.createElement("div");
723
+ messageEl.className = "permission-reason";
724
+ messageEl.textContent = msg.message;
725
+ body.appendChild(messageEl);
726
+ }
727
+
728
+ // Form fields (form mode) or URL button (url mode)
729
+ var formData = {};
730
+
731
+ if (msg.mode === "url" && msg.url) {
732
+ var urlInfo = document.createElement("div");
733
+ urlInfo.className = "elicitation-url-info";
734
+ urlInfo.style.cssText = "margin-top: 8px; font-size: 12px; color: var(--text-muted);";
735
+ urlInfo.textContent = "Opens: " + msg.url;
736
+ body.appendChild(urlInfo);
737
+ } else if (msg.requestedSchema && msg.requestedSchema.properties) {
738
+ var formEl = document.createElement("div");
739
+ formEl.className = "elicitation-form";
740
+ formEl.style.cssText = "margin-top: 8px; display: flex; flex-direction: column; gap: 8px;";
741
+
742
+ var props = msg.requestedSchema.properties;
743
+ var required = msg.requestedSchema.required || [];
744
+ var propNames = Object.keys(props);
745
+ for (var i = 0; i < propNames.length; i++) {
746
+ var propName = propNames[i];
747
+ var prop = props[propName];
748
+ var isRequired = required.indexOf(propName) !== -1;
749
+
750
+ var fieldWrapper = document.createElement("div");
751
+ fieldWrapper.style.cssText = "display: flex; flex-direction: column; gap: 2px;";
752
+
753
+ var label = document.createElement("label");
754
+ label.style.cssText = "font-size: 12px; font-weight: 500; color: var(--text-secondary);";
755
+ label.textContent = propName + (isRequired ? " *" : "");
756
+ if (prop.description) {
757
+ label.title = prop.description;
758
+ }
759
+
760
+ var input;
761
+ if (prop.type === "boolean") {
762
+ input = document.createElement("input");
763
+ input.type = "checkbox";
764
+ input.dataset.propName = propName;
765
+ input.dataset.propType = "boolean";
766
+ } else if (prop.enum) {
767
+ input = document.createElement("select");
768
+ input.dataset.propName = propName;
769
+ input.dataset.propType = "enum";
770
+ input.style.cssText = "padding: 4px 8px; border-radius: 4px; border: 1px solid var(--border); background: var(--input-bg); color: var(--text-primary); font-size: 13px;";
771
+ for (var ei = 0; ei < prop.enum.length; ei++) {
772
+ var opt = document.createElement("option");
773
+ opt.value = prop.enum[ei];
774
+ opt.textContent = prop.enum[ei];
775
+ input.appendChild(opt);
776
+ }
777
+ } else {
778
+ input = document.createElement("input");
779
+ input.type = prop.type === "number" || prop.type === "integer" ? "number" : "text";
780
+ input.dataset.propName = propName;
781
+ input.dataset.propType = prop.type || "string";
782
+ input.placeholder = prop.description || propName;
783
+ input.style.cssText = "padding: 4px 8px; border-radius: 4px; border: 1px solid var(--border); background: var(--input-bg); color: var(--text-primary); font-size: 13px;";
784
+ }
785
+
786
+ fieldWrapper.appendChild(label);
787
+ fieldWrapper.appendChild(input);
788
+ formEl.appendChild(fieldWrapper);
789
+ }
790
+ body.appendChild(formEl);
791
+ }
792
+
793
+ // Actions
794
+ var actions = document.createElement("div");
795
+ actions.className = "permission-actions";
796
+
797
+ var acceptBtn = document.createElement("button");
798
+ acceptBtn.className = "permission-btn permission-allow";
799
+
800
+ if (msg.mode === "url" && msg.url) {
801
+ acceptBtn.textContent = "Open & Approve";
802
+ acceptBtn.addEventListener("click", function () {
803
+ window.open(msg.url, "_blank");
804
+ sendElicitationResponse(container, msg.requestId, "accept", {});
805
+ });
806
+ } else {
807
+ acceptBtn.textContent = "Submit";
808
+ acceptBtn.addEventListener("click", function () {
809
+ // Collect form values
810
+ var content = {};
811
+ var inputs = container.querySelectorAll("[data-prop-name]");
812
+ for (var j = 0; j < inputs.length; j++) {
813
+ var inp = inputs[j];
814
+ var name = inp.dataset.propName;
815
+ var pType = inp.dataset.propType;
816
+ if (pType === "boolean") {
817
+ content[name] = inp.checked;
818
+ } else if (pType === "number" || pType === "integer") {
819
+ content[name] = Number(inp.value);
820
+ } else {
821
+ content[name] = inp.value;
822
+ }
823
+ }
824
+ sendElicitationResponse(container, msg.requestId, "accept", content);
825
+ });
826
+ }
827
+
828
+ var denyBtn = document.createElement("button");
829
+ denyBtn.className = "permission-btn permission-deny";
830
+ denyBtn.textContent = "Deny";
831
+ denyBtn.addEventListener("click", function () {
832
+ sendElicitationResponse(container, msg.requestId, "reject", null);
833
+ });
834
+
835
+ actions.appendChild(acceptBtn);
836
+ actions.appendChild(denyBtn);
837
+
838
+ container.appendChild(header);
839
+ container.appendChild(body);
840
+ container.appendChild(actions);
841
+ ctx.addToMessages(container);
842
+
843
+ pendingElicitations[msg.requestId] = container;
844
+ refreshIcons();
845
+ ctx.setActivity(null);
846
+ ctx.scrollToBottom();
847
+ }
848
+
849
+ function sendElicitationResponse(container, requestId, action, content) {
850
+ if (container.classList.contains("resolved")) return;
851
+ container.classList.add("resolved");
852
+ if (ctx.stopUrgentBlink) ctx.stopUrgentBlink();
853
+
854
+ var label = action === "reject" ? "Denied" : "Submitted";
855
+ var resolvedClass = action === "reject" ? "resolved-denied" : "resolved-allowed";
856
+ container.classList.add(resolvedClass);
857
+
858
+ var actions = container.querySelector(".permission-actions");
859
+ if (actions) {
860
+ actions.innerHTML = '<span class="permission-decision-label">' + label + '</span>';
861
+ }
862
+
863
+ if (ctx.ws && ctx.connected) {
864
+ var msg = {
865
+ type: "elicitation_response",
866
+ requestId: requestId,
867
+ action: action,
868
+ };
869
+ if (action === "accept" && content) {
870
+ msg.content = content;
871
+ }
872
+ ctx.ws.send(JSON.stringify(msg));
873
+ }
874
+
875
+ delete pendingElicitations[requestId];
876
+ }
877
+
878
+ export function markElicitationResolved(requestId, action) {
879
+ var container = pendingElicitations[requestId];
880
+ if (!container) {
881
+ container = ctx.messagesEl.querySelector('.elicitation-container[data-request-id="' + requestId + '"]');
882
+ }
883
+ if (!container || container.classList.contains("resolved")) return;
884
+
885
+ container.classList.add("resolved");
886
+ var isDeny = action === "reject";
887
+ container.classList.add(isDeny ? "resolved-denied" : "resolved-allowed");
888
+
889
+ var label = isDeny ? "Denied" : "Submitted";
890
+ var actionsEl = container.querySelector(".permission-actions");
891
+ if (actionsEl) {
892
+ actionsEl.innerHTML = '<span class="permission-decision-label">' + label + '</span>';
893
+ }
894
+ delete pendingElicitations[requestId];
895
+ }
896
+
696
897
  // --- Plan mode rendering ---
697
898
  export function renderPlanBanner(type) {
698
899
  ctx.finalizeAssistantBlock();
@@ -1504,7 +1705,7 @@ function fmtDuration(ms) {
1504
1705
  return secs + "s";
1505
1706
  }
1506
1707
 
1507
- export function updateSubagentProgress(parentToolId, usage, lastToolName) {
1708
+ export function updateSubagentProgress(parentToolId, usage, lastToolName, summary) {
1508
1709
  var tool = tools[parentToolId];
1509
1710
  if (!tool || !tool.el) return;
1510
1711
  var progressEl = tool.el.querySelector(".subagent-progress");
@@ -1523,6 +1724,17 @@ export function updateSubagentProgress(parentToolId, usage, lastToolName) {
1523
1724
  }
1524
1725
  if (lastToolName) parts.push(lastToolName);
1525
1726
  progressEl.textContent = parts.join(" · ");
1727
+
1728
+ // AI-generated progress summary (agentProgressSummaries)
1729
+ if (summary) {
1730
+ var summaryEl = tool.el.querySelector(".subagent-summary");
1731
+ if (!summaryEl) {
1732
+ summaryEl = document.createElement("div");
1733
+ summaryEl.className = "subagent-summary";
1734
+ progressEl.parentNode.insertBefore(summaryEl, progressEl.nextSibling);
1735
+ }
1736
+ summaryEl.textContent = summary;
1737
+ }
1526
1738
  }
1527
1739
 
1528
1740
  export function initSubagentStop(parentToolId, taskId) {
@@ -1640,6 +1852,7 @@ export function resetToolState() {
1640
1852
  todoWidgetVisible = true;
1641
1853
  if (todoObserver) { todoObserver.disconnect(); todoObserver = null; }
1642
1854
  pendingPermissions = {};
1855
+ pendingElicitations = {};
1643
1856
  currentToolGroup = null;
1644
1857
  toolGroupCounter = 0;
1645
1858
  toolGroups = {};