clay-server 2.22.2 → 2.23.0-beta.1

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.
@@ -898,6 +898,115 @@
898
898
  cursor: not-allowed;
899
899
  }
900
900
 
901
+ /* --- Quick Debate Modal (overlay-based) --- */
902
+ .debate-modal-overlay {
903
+ position: fixed;
904
+ inset: 0;
905
+ z-index: 9998;
906
+ display: flex;
907
+ align-items: center;
908
+ justify-content: center;
909
+ }
910
+ .debate-modal-overlay .debate-modal-backdrop {
911
+ position: absolute;
912
+ inset: 0;
913
+ }
914
+ .quick-debate-modal {
915
+ position: relative;
916
+ z-index: 9999;
917
+ width: 420px;
918
+ max-width: 90vw;
919
+ background: var(--bg);
920
+ border: 1px solid var(--border);
921
+ border-radius: 16px;
922
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
923
+ overflow: hidden;
924
+ animation: ctxMenuAppear 0.15s ease-out;
925
+ }
926
+ .quick-debate-modal .debate-modal-header {
927
+ display: flex;
928
+ align-items: center;
929
+ justify-content: space-between;
930
+ padding: 16px 20px;
931
+ border-bottom: 1px solid var(--border);
932
+ }
933
+ .quick-debate-modal .debate-modal-header h3 {
934
+ margin: 0;
935
+ font-size: 15px;
936
+ font-weight: 700;
937
+ color: var(--text);
938
+ }
939
+ .quick-debate-modal .debate-modal-close-btn {
940
+ background: none;
941
+ border: none;
942
+ color: var(--text-muted);
943
+ font-size: 20px;
944
+ cursor: pointer;
945
+ padding: 0 4px;
946
+ }
947
+ .quick-debate-modal .debate-modal-field {
948
+ padding: 12px 20px 0;
949
+ }
950
+ .quick-debate-modal .debate-modal-field label {
951
+ display: block;
952
+ font-size: 13px;
953
+ font-weight: 600;
954
+ color: var(--text);
955
+ margin-bottom: 6px;
956
+ }
957
+ .quick-debate-topic {
958
+ width: 100%;
959
+ padding: 8px 12px;
960
+ border: 1px solid var(--border);
961
+ border-radius: 8px;
962
+ background: var(--bg-alt);
963
+ color: var(--text);
964
+ font-size: 13px;
965
+ font-family: inherit;
966
+ outline: none;
967
+ transition: border-color 0.15s;
968
+ box-sizing: border-box;
969
+ }
970
+ .quick-debate-topic:focus {
971
+ border-color: var(--accent);
972
+ }
973
+ .quick-debate-panel-list {
974
+ max-height: 240px;
975
+ overflow-y: auto;
976
+ }
977
+ .quick-debate-modal .debate-modal-actions {
978
+ display: flex;
979
+ justify-content: flex-end;
980
+ gap: 8px;
981
+ padding: 12px 20px;
982
+ border-top: 1px solid var(--border);
983
+ margin-top: 12px;
984
+ }
985
+ .debate-modal-cancel-btn {
986
+ padding: 8px 16px;
987
+ border: 1px solid var(--border);
988
+ border-radius: 8px;
989
+ background: var(--bg);
990
+ color: var(--text-muted);
991
+ font-size: 13px;
992
+ font-weight: 600;
993
+ cursor: pointer;
994
+ font-family: inherit;
995
+ }
996
+ .debate-modal-start-btn {
997
+ padding: 8px 20px;
998
+ border: none;
999
+ border-radius: 8px;
1000
+ background: var(--accent);
1001
+ color: #fff;
1002
+ font-size: 13px;
1003
+ font-weight: 600;
1004
+ cursor: pointer;
1005
+ font-family: inherit;
1006
+ transition: opacity 0.2s;
1007
+ }
1008
+ .debate-modal-start-btn:hover { opacity: 0.9; }
1009
+
901
1010
  /* --- Debate bottom bar (unified controls) --- */
902
1011
  #debate-bottom-bar {
903
1012
  border-top: 1px solid var(--border);
@@ -1037,3 +1146,157 @@
1037
1146
  .debate-bottom-end:hover {
1038
1147
  opacity: 0.85;
1039
1148
  }
1149
+
1150
+ /* --- Debate Brief Card (inline proposal from DM) --- */
1151
+ .debate-brief-card {
1152
+ max-width: var(--content-width);
1153
+ margin: 10px auto;
1154
+ padding: 0 20px;
1155
+ }
1156
+ .debate-brief-card-header {
1157
+ display: flex;
1158
+ align-items: center;
1159
+ gap: 8px;
1160
+ padding: 12px 16px;
1161
+ background: var(--accent-bg);
1162
+ border: 1px solid var(--accent-20);
1163
+ border-radius: 12px 12px 0 0;
1164
+ cursor: pointer;
1165
+ user-select: none;
1166
+ }
1167
+ .debate-brief-card.collapsed .debate-brief-card-header { border-radius: 12px; }
1168
+ .debate-brief-card-icon { display: inline-flex; color: var(--accent); }
1169
+ .debate-brief-card-icon .lucide { width: 16px; height: 16px; }
1170
+ .debate-brief-card-title { font-size: 14px; font-weight: 600; color: var(--accent); flex: 1; }
1171
+ .debate-brief-card-chevron { display: inline-flex; color: var(--text-muted); transition: transform 0.2s; }
1172
+ .debate-brief-card-chevron .lucide { width: 14px; height: 14px; }
1173
+ .debate-brief-card.collapsed .debate-brief-card-chevron { transform: rotate(-90deg); }
1174
+
1175
+ .debate-brief-card-body {
1176
+ padding: 16px 18px;
1177
+ background: rgba(var(--overlay-rgb), 0.02);
1178
+ border: 1px solid var(--border);
1179
+ border-top: none;
1180
+ font-size: 14px;
1181
+ line-height: 1.65;
1182
+ }
1183
+ .debate-brief-card.collapsed .debate-brief-card-body { display: none; }
1184
+
1185
+ .debate-brief-topic {
1186
+ font-size: 16px;
1187
+ font-weight: 600;
1188
+ color: var(--text);
1189
+ margin-bottom: 10px;
1190
+ }
1191
+ .debate-brief-context {
1192
+ font-size: 13px;
1193
+ color: var(--text-secondary);
1194
+ margin-bottom: 12px;
1195
+ line-height: 1.5;
1196
+ }
1197
+ .debate-brief-moderator {
1198
+ display: flex;
1199
+ align-items: center;
1200
+ gap: 6px;
1201
+ font-size: 13px;
1202
+ color: var(--text);
1203
+ margin-bottom: 8px;
1204
+ }
1205
+ .debate-brief-moderator .lucide { width: 14px; height: 14px; color: var(--accent); }
1206
+ .debate-brief-panelists-label {
1207
+ display: flex;
1208
+ align-items: center;
1209
+ gap: 6px;
1210
+ font-size: 13px;
1211
+ color: var(--text);
1212
+ margin-bottom: 6px;
1213
+ }
1214
+ .debate-brief-panelists-label .lucide { width: 14px; height: 14px; color: var(--accent); }
1215
+ .debate-brief-panelists {
1216
+ display: flex;
1217
+ flex-direction: column;
1218
+ gap: 4px;
1219
+ margin-bottom: 10px;
1220
+ padding-left: 20px;
1221
+ }
1222
+ .debate-brief-panelist {
1223
+ display: flex;
1224
+ align-items: baseline;
1225
+ gap: 8px;
1226
+ font-size: 13px;
1227
+ }
1228
+ .debate-brief-panelist-name {
1229
+ font-weight: 500;
1230
+ color: var(--text);
1231
+ }
1232
+ .debate-brief-panelist-role {
1233
+ color: var(--text-secondary);
1234
+ font-style: italic;
1235
+ }
1236
+ .debate-brief-special {
1237
+ display: flex;
1238
+ align-items: flex-start;
1239
+ gap: 6px;
1240
+ font-size: 12px;
1241
+ color: var(--text-secondary);
1242
+ margin-top: 8px;
1243
+ padding: 8px 10px;
1244
+ background: rgba(var(--overlay-rgb), 0.04);
1245
+ border-radius: 6px;
1246
+ }
1247
+ .debate-brief-special .lucide { width: 14px; height: 14px; flex-shrink: 0; margin-top: 1px; }
1248
+
1249
+ /* Actions */
1250
+ .debate-brief-actions {
1251
+ display: flex;
1252
+ align-items: center;
1253
+ gap: 8px;
1254
+ padding: 12px 16px;
1255
+ background: rgba(var(--overlay-rgb), 0.02);
1256
+ border: 1px solid var(--border);
1257
+ border-top: none;
1258
+ border-radius: 0 0 12px 12px;
1259
+ }
1260
+ .debate-brief-card.collapsed .debate-brief-actions { display: none; }
1261
+
1262
+ .debate-brief-start-btn {
1263
+ display: inline-flex;
1264
+ align-items: center;
1265
+ gap: 6px;
1266
+ padding: 8px 16px;
1267
+ font-size: 13px;
1268
+ font-weight: 600;
1269
+ font-family: inherit;
1270
+ color: #fff;
1271
+ background: var(--accent, #6366f1);
1272
+ border: none;
1273
+ border-radius: 8px;
1274
+ cursor: pointer;
1275
+ transition: opacity 0.15s;
1276
+ }
1277
+ .debate-brief-start-btn:hover { opacity: 0.85; }
1278
+ .debate-brief-start-btn .lucide { width: 14px; height: 14px; }
1279
+
1280
+ .debate-brief-cancel-btn {
1281
+ padding: 8px 16px;
1282
+ font-size: 13px;
1283
+ font-family: inherit;
1284
+ color: var(--text-secondary);
1285
+ background: none;
1286
+ border: 1px solid var(--border);
1287
+ border-radius: 8px;
1288
+ cursor: pointer;
1289
+ transition: all 0.15s;
1290
+ }
1291
+ .debate-brief-cancel-btn:hover { color: var(--text); border-color: var(--text-muted); }
1292
+
1293
+ .debate-brief-resolved-label {
1294
+ display: inline-flex;
1295
+ align-items: center;
1296
+ gap: 6px;
1297
+ font-size: 13px;
1298
+ color: var(--accent);
1299
+ font-weight: 500;
1300
+ }
1301
+ .debate-brief-resolved-label .lucide { width: 14px; height: 14px; }
1302
+ .debate-brief-resolved-label.debate-brief-cancelled { color: var(--text-secondary); }
@@ -631,3 +631,292 @@ export function closeDebateModal() {
631
631
  }
632
632
  selectedPanelists = [];
633
633
  }
634
+
635
+ // --- Quick Debate: start debate from DM context ---
636
+ var quickDebateEl = null;
637
+ var quickSelectedPanelists = [];
638
+
639
+ export function openQuickDebateModal(dmMessages) {
640
+ closeQuickDebateModal();
641
+
642
+ // Build DM context from recent messages (last 20, capped)
643
+ var dmContext = "";
644
+ if (dmMessages && dmMessages.length) {
645
+ var recent = dmMessages.slice(-20);
646
+ var parts = [];
647
+ for (var i = 0; i < recent.length; i++) {
648
+ var m = recent[i];
649
+ var speaker = m.isMate ? "Mate" : "User";
650
+ var text = m.text || "";
651
+ if (text.length > 500) text = text.substring(0, 500) + "...";
652
+ parts.push(speaker + ": " + text);
653
+ }
654
+ dmContext = parts.join("\n");
655
+ }
656
+
657
+ quickSelectedPanelists = [];
658
+
659
+ // Create modal overlay
660
+ quickDebateEl = document.createElement("div");
661
+ quickDebateEl.className = "debate-modal-overlay";
662
+
663
+ var html = '';
664
+ html += '<div class="debate-modal-backdrop"></div>';
665
+ html += '<div class="debate-modal-content quick-debate-modal">';
666
+ html += '<div class="debate-modal-header">';
667
+ html += '<h3>Quick Debate</h3>';
668
+ html += '<button class="debate-modal-close-btn">&times;</button>';
669
+ html += '</div>';
670
+
671
+ // Optional topic override
672
+ html += '<div class="debate-modal-field">';
673
+ html += '<label>Topic <span style="color:var(--text-tertiary);font-weight:normal">(optional, auto-detected from conversation)</span></label>';
674
+ html += '<input type="text" class="quick-debate-topic" placeholder="Leave blank to auto-detect..." maxlength="200" spellcheck="false">';
675
+ html += '</div>';
676
+
677
+ // Panelist selection
678
+ html += '<div class="debate-modal-field">';
679
+ html += '<label>Select Panelists</label>';
680
+ html += '<div class="quick-debate-panel-list"></div>';
681
+ html += '</div>';
682
+
683
+ html += '<div class="debate-modal-actions">';
684
+ html += '<button class="debate-modal-cancel-btn">Cancel</button>';
685
+ html += '<button class="debate-modal-start-btn">Start Debate</button>';
686
+ html += '</div>';
687
+
688
+ html += '</div>';
689
+ quickDebateEl.innerHTML = html;
690
+ document.body.appendChild(quickDebateEl);
691
+
692
+ // Populate panelist list
693
+ var panelList = quickDebateEl.querySelector(".quick-debate-panel-list");
694
+ var mates = ctx.matesList ? ctx.matesList() : [];
695
+ var currentMateId = ctx.currentMateId ? ctx.currentMateId() : null;
696
+ for (var j = 0; j < mates.length; j++) {
697
+ var mate = mates[j];
698
+ if (mate.status === "interviewing") continue;
699
+ if (mate.id === currentMateId) continue;
700
+ var item = createQuickPanelItem(mate);
701
+ panelList.appendChild(item);
702
+ }
703
+
704
+ // Events
705
+ quickDebateEl.querySelector(".debate-modal-backdrop").onclick = closeQuickDebateModal;
706
+ quickDebateEl.querySelector(".debate-modal-close-btn").onclick = closeQuickDebateModal;
707
+ quickDebateEl.querySelector(".debate-modal-cancel-btn").onclick = closeQuickDebateModal;
708
+
709
+ quickDebateEl.querySelector(".debate-modal-start-btn").onclick = function () {
710
+ if (quickSelectedPanelists.length === 0) return;
711
+ if (!currentMateId) return;
712
+
713
+ var topicInput = quickDebateEl.querySelector(".quick-debate-topic");
714
+ var topic = topicInput ? topicInput.value.trim() : "";
715
+
716
+ var debatePayload = {
717
+ type: "debate_start",
718
+ quickStart: true,
719
+ moderatorId: currentMateId,
720
+ topic: topic || "(auto-detect from conversation)",
721
+ dmContext: dmContext,
722
+ panelists: quickSelectedPanelists.map(function (id) {
723
+ return { mateId: id, role: "", brief: "" };
724
+ }),
725
+ };
726
+
727
+ // Create new session, then send debate_start
728
+ if (ctx.ws) {
729
+ var onMessage = function (evt) {
730
+ try {
731
+ var data = JSON.parse(evt.data);
732
+ if (data.type === "session_switched") {
733
+ ctx.ws.removeEventListener("message", onMessage);
734
+ ctx.ws.send(JSON.stringify(debatePayload));
735
+ }
736
+ } catch (e) {}
737
+ };
738
+ ctx.ws.addEventListener("message", onMessage);
739
+ ctx.ws.send(JSON.stringify({ type: "new_session" }));
740
+ }
741
+
742
+ closeQuickDebateModal();
743
+ };
744
+
745
+ // Focus topic input
746
+ var topicEl = quickDebateEl.querySelector(".quick-debate-topic");
747
+ if (topicEl) setTimeout(function () { topicEl.focus(); }, 50);
748
+
749
+ refreshIcons();
750
+ }
751
+
752
+ function createQuickPanelItem(mate) {
753
+ var item = document.createElement("div");
754
+ item.className = "debate-panel-item";
755
+ item.dataset.mateId = mate.id;
756
+
757
+ var cb = document.createElement("input");
758
+ cb.type = "checkbox";
759
+ item.appendChild(cb);
760
+
761
+ var avatarSrc = mateAvatarUrl(mate, 32);
762
+ var avi = document.createElement("img");
763
+ avi.className = "debate-panel-item-avatar";
764
+ avi.src = avatarSrc;
765
+ item.appendChild(avi);
766
+
767
+ var info = document.createElement("div");
768
+ info.className = "debate-panel-item-info";
769
+ var nameSpan = document.createElement("div");
770
+ nameSpan.className = "debate-panel-item-name";
771
+ nameSpan.textContent = (mate.profile && mate.profile.displayName) || mate.name || "Mate";
772
+ info.appendChild(nameSpan);
773
+ if (mate.bio) {
774
+ var bioSpan = document.createElement("div");
775
+ bioSpan.className = "debate-panel-item-bio";
776
+ bioSpan.textContent = mate.bio;
777
+ info.appendChild(bioSpan);
778
+ }
779
+ item.appendChild(info);
780
+
781
+ function toggle() {
782
+ var idx = quickSelectedPanelists.indexOf(mate.id);
783
+ if (idx === -1) {
784
+ quickSelectedPanelists.push(mate.id);
785
+ item.classList.add("selected");
786
+ cb.checked = true;
787
+ } else {
788
+ quickSelectedPanelists.splice(idx, 1);
789
+ item.classList.remove("selected");
790
+ cb.checked = false;
791
+ }
792
+ }
793
+
794
+ item.addEventListener("click", function (e) {
795
+ if (e.target === cb) return;
796
+ toggle();
797
+ });
798
+ cb.addEventListener("change", function () {
799
+ var idx = quickSelectedPanelists.indexOf(mate.id);
800
+ if (cb.checked && idx === -1) { quickSelectedPanelists.push(mate.id); item.classList.add("selected"); }
801
+ else if (!cb.checked && idx !== -1) { quickSelectedPanelists.splice(idx, 1); item.classList.remove("selected"); }
802
+ });
803
+
804
+ return item;
805
+ }
806
+
807
+ export function closeQuickDebateModal() {
808
+ if (quickDebateEl) {
809
+ quickDebateEl.remove();
810
+ quickDebateEl = null;
811
+ }
812
+ quickSelectedPanelists = [];
813
+ }
814
+
815
+ // --- Debate Brief Card (inline proposal from DM) ---
816
+
817
+ export function handleDebateBriefReady(msg) {
818
+ renderDebateBriefCard(msg, false);
819
+ }
820
+
821
+ export function renderDebateBriefReady(msg) {
822
+ renderDebateBriefCard(msg, true);
823
+ }
824
+
825
+ function renderDebateBriefCard(msg, resolved) {
826
+ var el = document.createElement("div");
827
+ el.className = "debate-brief-card" + (resolved ? " resolved" : "");
828
+
829
+ // Header
830
+ var header = document.createElement("div");
831
+ header.className = "debate-brief-card-header";
832
+ header.innerHTML =
833
+ '<span class="debate-brief-card-icon">' + iconHtml("message-circle") + '</span>' +
834
+ '<span class="debate-brief-card-title">Debate Proposal</span>' +
835
+ '<span class="debate-brief-card-chevron">' + iconHtml("chevron-down") + '</span>';
836
+
837
+ // Body
838
+ var body = document.createElement("div");
839
+ body.className = "debate-brief-card-body";
840
+
841
+ var topicHtml = '<div class="debate-brief-topic">' + escapeHtml(msg.topic || "Untitled") + '</div>';
842
+
843
+ if (msg.context) {
844
+ topicHtml += '<div class="debate-brief-context">' + escapeHtml(msg.context) + '</div>';
845
+ }
846
+
847
+ topicHtml += '<div class="debate-brief-moderator">' +
848
+ iconHtml("mic") + ' <strong>Moderator:</strong> ' + escapeHtml(msg.moderatorName || "Unknown") +
849
+ '</div>';
850
+
851
+ topicHtml += '<div class="debate-brief-panelists-label">' + iconHtml("users") + ' <strong>Panelists:</strong></div>';
852
+ topicHtml += '<div class="debate-brief-panelists">';
853
+ if (msg.panelists) {
854
+ for (var i = 0; i < msg.panelists.length; i++) {
855
+ var p = msg.panelists[i];
856
+ topicHtml += '<div class="debate-brief-panelist">';
857
+ topicHtml += '<span class="debate-brief-panelist-name">' + escapeHtml(p.name || "Unknown") + '</span>';
858
+ if (p.role) {
859
+ topicHtml += '<span class="debate-brief-panelist-role">' + escapeHtml(p.role) + '</span>';
860
+ }
861
+ topicHtml += '</div>';
862
+ }
863
+ }
864
+ topicHtml += '</div>';
865
+
866
+ if (msg.specialRequests) {
867
+ topicHtml += '<div class="debate-brief-special">' +
868
+ iconHtml("info") + ' ' + escapeHtml(msg.specialRequests) +
869
+ '</div>';
870
+ }
871
+
872
+ body.innerHTML = topicHtml;
873
+
874
+ // Actions
875
+ var actions = document.createElement("div");
876
+ actions.className = "debate-brief-actions";
877
+
878
+ if (resolved) {
879
+ actions.innerHTML = '<span class="debate-brief-resolved-label">' + iconHtml("check") + ' Debate started</span>';
880
+ } else {
881
+ var startBtn = document.createElement("button");
882
+ startBtn.className = "debate-brief-start-btn";
883
+ startBtn.innerHTML = iconHtml("play") + " Start Debate";
884
+
885
+ var cancelBtn = document.createElement("button");
886
+ cancelBtn.className = "debate-brief-cancel-btn";
887
+ cancelBtn.textContent = "Cancel";
888
+
889
+ startBtn.addEventListener("click", function () {
890
+ if (ctx.sendWs) {
891
+ ctx.sendWs({ type: "debate_confirm_brief" });
892
+ }
893
+ el.classList.add("resolved");
894
+ actions.innerHTML = '<span class="debate-brief-resolved-label">' + iconHtml("check") + ' Starting debate...</span>';
895
+ refreshIcons();
896
+ });
897
+
898
+ cancelBtn.addEventListener("click", function () {
899
+ if (ctx.sendWs) {
900
+ ctx.sendWs({ type: "debate_stop" });
901
+ }
902
+ el.classList.add("resolved");
903
+ actions.innerHTML = '<span class="debate-brief-resolved-label debate-brief-cancelled">' + iconHtml("x") + ' Cancelled</span>';
904
+ refreshIcons();
905
+ });
906
+
907
+ actions.appendChild(startBtn);
908
+ actions.appendChild(cancelBtn);
909
+ }
910
+
911
+ // Collapse toggle
912
+ header.addEventListener("click", function () {
913
+ el.classList.toggle("collapsed");
914
+ });
915
+
916
+ el.appendChild(header);
917
+ el.appendChild(body);
918
+ el.appendChild(actions);
919
+ ctx.addToMessages(el);
920
+ refreshIcons();
921
+ ctx.scrollToBottom();
922
+ }
@@ -2436,6 +2436,9 @@ function showMateCtxMenu(anchorEl, mate) {
2436
2436
  removeItem.addEventListener("click", function (e) {
2437
2437
  e.stopPropagation();
2438
2438
  closeUserCtxMenu();
2439
+ // Spawn dust particles at the mate icon position
2440
+ var iconRect = anchorEl.getBoundingClientRect();
2441
+ spawnDustParticles(iconRect.left + iconRect.width / 2, iconRect.top + iconRect.height / 2);
2439
2442
  if (ctx.sendWs) {
2440
2443
  ctx.sendWs({ type: "dm_remove_favorite", targetUserId: mate.id });
2441
2444
  }
package/lib/sdk-bridge.js CHANGED
@@ -708,11 +708,13 @@ function createSDKBridge(opts) {
708
708
  try { fs.chmodSync(socketPath, 0o777); } catch (e) {}
709
709
 
710
710
  // Spawn worker process as the target Linux user.
711
- // Inherit full env from daemon, override user-specific vars.
712
- var workerEnv = Object.assign({}, process.env, {
713
- HOME: userInfo.home,
714
- USER: linuxUser,
715
- LOGNAME: linuxUser,
711
+ // Build a minimal, isolated env (no daemon env leakage).
712
+ var workerEnv = require("./build-user-env").buildUserEnv({
713
+ uid: userInfo.uid,
714
+ gid: userInfo.gid,
715
+ home: userInfo.home,
716
+ user: linuxUser,
717
+ shell: userInfo.shell || "/bin/bash",
716
718
  });
717
719
 
718
720
  console.log("[sdk-bridge] Spawning worker: uid=" + userInfo.uid + " gid=" + userInfo.gid + " cwd=" + cwd + " socket=" + socketPath);
package/lib/server.js CHANGED
@@ -2931,9 +2931,28 @@ function createServer(opts) {
2931
2931
  });
2932
2932
  projects.set(slug, ctx);
2933
2933
  ctx.warmup();
2934
+ refreshMateProjectRegistries();
2934
2935
  return true;
2935
2936
  }
2936
2937
 
2938
+ // --- Refresh project registry on all mate CLAUDE.md files ---
2939
+ function refreshMateProjectRegistries() {
2940
+ var projList = [];
2941
+ projects.forEach(function (ctx) {
2942
+ var status = ctx.getStatus();
2943
+ if (!status.isMate && !status.isWorktree) {
2944
+ projList.push({ slug: status.slug, path: status.path, title: status.title || status.project, icon: status.icon });
2945
+ }
2946
+ });
2947
+ projects.forEach(function (ctx) {
2948
+ var status = ctx.getStatus();
2949
+ if (status.isMate) {
2950
+ var claudeMdPath = path.join(status.path, "CLAUDE.md");
2951
+ try { mates.enforceProjectRegistry(claudeMdPath, projList); } catch (e) {}
2952
+ }
2953
+ });
2954
+ }
2955
+
2937
2956
  // --- DM message handler (server-level, cross-project) ---
2938
2957
  function handleDmMessage(ws, msg) {
2939
2958
  if (!users.isMultiUser() || !ws._clayUser) return;
@@ -3149,12 +3168,14 @@ function createServer(opts) {
3149
3168
  } catch (e) {
3150
3169
  console.error("[server] Failed to ensure built-in mates:", e.message);
3151
3170
  }
3152
- // Ensure built-in mates are in favorites (unless user explicitly removed them)
3171
+ // Ensure core built-in mates are in favorites (unless user explicitly removed them)
3172
+ // Only auto-favorite the core 3: Ally (chief of staff), Arch (architect), Buzz (marketer)
3173
+ var coreMateKeys = ["ally", "arch", "buzz"];
3153
3174
  var mateList = mates.getAllMates(mateCtx5);
3154
3175
  var currentFavs = users.getDmFavorites(userId);
3155
3176
  var hiddenIds = users.getDmHidden(userId);
3156
3177
  for (var bfi = 0; bfi < mateList.length; bfi++) {
3157
- if (mateList[bfi].builtinKey && currentFavs.indexOf(mateList[bfi].id) === -1 && hiddenIds.indexOf(mateList[bfi].id) === -1) {
3178
+ if (mateList[bfi].builtinKey && coreMateKeys.indexOf(mateList[bfi].builtinKey) !== -1 && currentFavs.indexOf(mateList[bfi].id) === -1 && hiddenIds.indexOf(mateList[bfi].id) === -1) {
3158
3179
  users.addDmFavorite(userId, mateList[bfi].id);
3159
3180
  }
3160
3181
  }
@@ -3314,6 +3335,7 @@ function createServer(opts) {
3314
3335
  if (!ctx) return false;
3315
3336
  ctx.destroy();
3316
3337
  projects.delete(slug);
3338
+ refreshMateProjectRegistries();
3317
3339
  return true;
3318
3340
  }
3319
3341
 
package/lib/terminal.js CHANGED
@@ -5,14 +5,17 @@ try {
5
5
  pty = null;
6
6
  }
7
7
 
8
+ var { buildUserEnv } = require("./build-user-env");
9
+
8
10
  function createTerminal(cwd, cols, rows, osUserInfo) {
9
11
  if (!pty) return null;
10
12
 
11
- var shell = process.env.SHELL
13
+ // Determine shell: prefer target user's shell, then fallback
14
+ var shell = (osUserInfo && osUserInfo.shell)
12
15
  || (process.platform === "win32" ? process.env.COMSPEC || "cmd.exe" : "/bin/bash");
13
16
 
14
- // OS-level user isolation: spawn terminal as the mapped Linux user
15
- var termEnv = Object.assign({}, process.env, { TERM: "xterm-256color" });
17
+ // Build a minimal, isolated environment (no daemon env leakage)
18
+ var termEnv = buildUserEnv(osUserInfo);
16
19
  var spawnOpts = {
17
20
  name: "xterm-256color",
18
21
  cols: cols || 80,
@@ -24,12 +27,6 @@ function createTerminal(cwd, cols, rows, osUserInfo) {
24
27
  if (osUserInfo) {
25
28
  spawnOpts.uid = osUserInfo.uid;
26
29
  spawnOpts.gid = osUserInfo.gid;
27
- // Use the target user's shell and home
28
- termEnv.HOME = osUserInfo.home;
29
- termEnv.USER = osUserInfo.user;
30
- termEnv.LOGNAME = osUserInfo.user;
31
- // Use target user's shell if available
32
- if (osUserInfo.shell) shell = osUserInfo.shell;
33
30
  }
34
31
 
35
32
  var args = osUserInfo ? ["-l"] : [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.22.2",
3
+ "version": "2.23.0-beta.1",
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",