clay-server 2.17.0 → 2.18.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.
package/lib/public/app.js CHANGED
@@ -28,6 +28,8 @@ import { initTooltips, registerTooltip } from './modules/tooltip.js';
28
28
  import { initMateWizard, openMateWizard, closeMateWizard, handleMateCreated } from './modules/mate-wizard.js';
29
29
  import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } from './modules/command-palette.js';
30
30
  import { initLongPress } from './modules/longpress.js';
31
+ import { initMention, handleMentionStart, handleMentionStream, handleMentionDone, handleMentionError, handleMentionActivity, renderMentionUser, renderMentionResponse } from './modules/mention.js';
32
+ import { initDebate, handleDebateStarted, handleDebateTurn, handleDebateActivity, handleDebateStream, handleDebateTurnDone, handleDebateCommentQueued, handleDebateCommentInjected, handleDebateEnded, handleDebateError, renderDebateStarted, renderDebateTurnDone, renderDebateEnded, renderDebateCommentInjected, openDebateModal, closeDebateModal } from './modules/debate.js';
31
33
 
32
34
  // --- Base path for multi-project routing ---
33
35
  var slugMatch = location.pathname.match(/^\/p\/([a-z0-9_-]+)/);
@@ -61,6 +63,7 @@ import { initLongPress } from './modules/longpress.js';
61
63
 
62
64
  // --- DM Mode ---
63
65
  var dmMode = false;
66
+ var pendingMateDmRestore = false; // suppress regular history while restoring mate DM
64
67
  var dmKey = null;
65
68
  var dmTargetUser = null;
66
69
  var dmUnread = {}; // { otherUserId: count }
@@ -693,9 +696,9 @@ import { initLongPress } from './modules/longpress.js';
693
696
  }
694
697
  }
695
698
 
696
- // Hide user-island (my avatar behind it becomes visible)
699
+ // Hide user-island in human DM, keep visible in Mate DM
697
700
  var userIsland = document.getElementById("user-island");
698
- if (userIsland) userIsland.classList.add("dm-hidden");
701
+ if (userIsland && !isMate) userIsland.classList.add("dm-hidden");
699
702
 
700
703
  // Render DM messages
701
704
  messagesEl.innerHTML = "";
@@ -740,6 +743,7 @@ import { initLongPress } from './modules/longpress.js';
740
743
  function exitDmMode() {
741
744
  if (!dmMode) return;
742
745
  dmMode = false;
746
+ pendingMateDmRestore = false;
743
747
  dmKey = null;
744
748
  dmTargetUser = null;
745
749
  setCurrentDmUser(null);
@@ -904,6 +908,13 @@ import { initLongPress } from './modules/longpress.js';
904
908
  var mateSessionDot = document.querySelector(".mate-session-item.active .session-processing");
905
909
  if (mateSessionDot) mateSessionDot.style.display = "none";
906
910
  }
911
+ // Handle skill_installed in mate DM context (for skill install modal)
912
+ if (msg.type === "skill_installed") {
913
+ handleSkillInstalled(msg);
914
+ if (msg.success) knownInstalledSkills[msg.skill] = true;
915
+ handleSkillInstallWs(msg);
916
+ }
917
+
907
918
  // Intercept session_list for mate sidebar
908
919
  if (msg.type === "init" && msg.sessions) {
909
920
  renderMateSessionList(msg.sessions);
@@ -2121,18 +2132,9 @@ import { initLongPress } from './modules/longpress.js';
2121
2132
  if (!activityEl) {
2122
2133
  activityEl = document.createElement("div");
2123
2134
  activityEl.className = "activity-inline";
2124
- var isMateDmActive = dmMode && dmTargetUser && dmTargetUser.isMate;
2125
- if (isMateDmActive) {
2126
- activityEl.classList.add("mate-activity");
2127
- var mateAvUrl = document.body.dataset.mateAvatarUrl || "";
2128
- activityEl.innerHTML =
2129
- '<img class="mate-activity-avatar" src="' + mateAvUrl + '" alt="">' +
2130
- '<span class="activity-text"></span>';
2131
- } else {
2132
- activityEl.innerHTML =
2133
- '<span class="activity-icon">' + iconHtml("sparkles") + '</span>' +
2134
- '<span class="activity-text"></span>';
2135
- }
2135
+ activityEl.innerHTML =
2136
+ '<span class="activity-icon">' + iconHtml("sparkles") + '</span>' +
2137
+ '<span class="activity-text"></span>';
2136
2138
  addToMessages(activityEl);
2137
2139
  refreshIcons();
2138
2140
  }
@@ -3690,10 +3692,27 @@ import { initLongPress } from './modules/longpress.js';
3690
3692
  ws.send(JSON.stringify({ type: "mate_list" }));
3691
3693
  } catch(e) {}
3692
3694
 
3693
- // Restore mate DM after hard refresh
3695
+ // Restore the last active session for this project (overrides server global)
3696
+ try {
3697
+ var savedSessionId = localStorage.getItem("clay_active_session_" + basePath);
3698
+ if (savedSessionId) {
3699
+ ws.send(JSON.stringify({ type: "switch_session", id: parseInt(savedSessionId, 10) }));
3700
+ }
3701
+ } catch(e) {}
3702
+
3703
+ // Restore mate DM after hard refresh or server restart
3694
3704
  try {
3695
3705
  var savedMateDm = localStorage.getItem("clay_active_mate_dm");
3696
- if (savedMateDm && !dmMode) {
3706
+ if (savedMateDm) {
3707
+ // If dmMode is stale (server restarted while in mate DM), clean up first
3708
+ if (dmMode) {
3709
+ dmMode = false;
3710
+ savedMainWs = null;
3711
+ mateWs = null;
3712
+ document.body.classList.remove("mate-dm-active");
3713
+ }
3714
+ pendingMateDmRestore = true;
3715
+ messagesEl.innerHTML = ""; // prevent regular history flash
3697
3716
  openDm(savedMateDm);
3698
3717
  }
3699
3718
  } catch(e) {}
@@ -3733,8 +3752,18 @@ import { initLongPress } from './modules/longpress.js';
3733
3752
  };
3734
3753
 
3735
3754
  ws.onmessage = function (event) {
3736
- // If this WS is stashed while in mate DM, ignore its messages
3737
- if (savedMainWs === this) return;
3755
+ // If this WS is stashed while in mate DM, only allow skill_installed through
3756
+ if (savedMainWs === this) {
3757
+ try {
3758
+ var stashedMsg = JSON.parse(event.data);
3759
+ if (stashedMsg.type === "skill_installed") {
3760
+ handleSkillInstalled(stashedMsg);
3761
+ if (stashedMsg.success) knownInstalledSkills[stashedMsg.skill] = true;
3762
+ handleSkillInstallWs(stashedMsg);
3763
+ }
3764
+ } catch (e) {}
3765
+ return;
3766
+ }
3738
3767
 
3739
3768
  // Backup: if we're receiving messages, we're connected
3740
3769
  if (!connected) {
@@ -3751,6 +3780,15 @@ import { initLongPress } from './modules/longpress.js';
3751
3780
  }
3752
3781
 
3753
3782
  function processMessage(msg) {
3783
+ // Suppress regular project messages while restoring mate DM
3784
+ if (pendingMateDmRestore) {
3785
+ if (msg.type === "dm_history" || msg.type === "dm_list" || msg.type === "mate_list" || msg.type === "mate_created" || msg.type === "info") {
3786
+ // Let these through
3787
+ } else {
3788
+ return; // skip regular session messages
3789
+ }
3790
+ }
3791
+
3754
3792
  switch (msg.type) {
3755
3793
  case "history_meta":
3756
3794
  historyFrom = msg.from;
@@ -4043,6 +4081,8 @@ import { initLongPress } from './modules/longpress.js';
4043
4081
  }
4044
4082
  activeSessionId = msg.id;
4045
4083
  cliSessionId = msg.cliSessionId || null;
4084
+ // Persist active session per project so reconnect restores it
4085
+ try { localStorage.setItem("clay_active_session_" + basePath, String(msg.id)); } catch(e) {}
4046
4086
  clearRemoteCursors();
4047
4087
  resetClientState();
4048
4088
  updateRalphBars();
@@ -4515,6 +4555,7 @@ import { initLongPress } from './modules/longpress.js';
4515
4555
 
4516
4556
  // --- DM ---
4517
4557
  case "dm_history":
4558
+ pendingMateDmRestore = false; // DM data arrived, resume normal processing
4518
4559
  // Attach projectSlug to targetUser for mate DMs
4519
4560
  if (msg.projectSlug && msg.targetUser) {
4520
4561
  msg.targetUser.projectSlug = msg.projectSlug;
@@ -4638,6 +4679,83 @@ import { initLongPress } from './modules/longpress.js';
4638
4679
  showToast(msg.error || "Mate operation failed", "error");
4639
4680
  break;
4640
4681
 
4682
+ // --- @Mention ---
4683
+ case "mention_start":
4684
+ handleMentionStart(msg);
4685
+ break;
4686
+
4687
+ case "mention_activity":
4688
+ handleMentionActivity(msg);
4689
+ break;
4690
+
4691
+ case "mention_stream":
4692
+ handleMentionStream(msg);
4693
+ break;
4694
+
4695
+ case "mention_done":
4696
+ handleMentionDone(msg);
4697
+ break;
4698
+
4699
+ case "mention_error":
4700
+ handleMentionError(msg);
4701
+ if (msg.error) showToast("@Mention: " + msg.error, "error");
4702
+ break;
4703
+
4704
+ case "mention_user":
4705
+ // History replay: render mention user message from another client
4706
+ renderMentionUser(msg);
4707
+ break;
4708
+
4709
+ case "mention_response":
4710
+ // History replay: render mention response from another client
4711
+ renderMentionResponse(msg);
4712
+ break;
4713
+
4714
+ // --- Debate ---
4715
+ case "debate_preparing":
4716
+ showDebateSticky("preparing", msg);
4717
+ break;
4718
+
4719
+ case "debate_started":
4720
+ showDebateSticky("live", msg);
4721
+ handleDebateStarted(msg);
4722
+ break;
4723
+
4724
+ case "debate_turn":
4725
+ handleDebateTurn(msg);
4726
+ if (msg.round) updateDebateRound(msg.round);
4727
+ break;
4728
+
4729
+ case "debate_activity":
4730
+ handleDebateActivity(msg);
4731
+ break;
4732
+
4733
+ case "debate_stream":
4734
+ handleDebateStream(msg);
4735
+ break;
4736
+
4737
+ case "debate_turn_done":
4738
+ handleDebateTurnDone(msg);
4739
+ break;
4740
+
4741
+ case "debate_comment_queued":
4742
+ handleDebateCommentQueued(msg);
4743
+ break;
4744
+
4745
+ case "debate_comment_injected":
4746
+ handleDebateCommentInjected(msg);
4747
+ break;
4748
+
4749
+ case "debate_ended":
4750
+ showDebateSticky("ended", msg);
4751
+ handleDebateEnded(msg);
4752
+ break;
4753
+
4754
+ case "debate_error":
4755
+ handleDebateError(msg);
4756
+ if (msg.error) showToast("Debate: " + msg.error, "error");
4757
+ break;
4758
+
4641
4759
  case "daemon_config":
4642
4760
  updateDaemonConfig(msg.config);
4643
4761
  break;
@@ -4958,6 +5076,28 @@ import { initLongPress } from './modules/longpress.js';
4958
5076
  showMatePreThinking: function () { showMatePreThinking(); },
4959
5077
  });
4960
5078
 
5079
+ // --- @Mention module ---
5080
+ initMention({
5081
+ get ws() { return ws; },
5082
+ get connected() { return connected; },
5083
+ inputEl: inputEl,
5084
+ messagesEl: messagesEl,
5085
+ matesList: function () { return cachedMatesList || []; },
5086
+ scrollToBottom: scrollToBottom,
5087
+ addUserMessage: addUserMessage,
5088
+ addCopyHandler: addCopyHandler,
5089
+ });
5090
+
5091
+ // --- Debate module ---
5092
+ initDebate({
5093
+ get ws() { return ws; },
5094
+ messagesEl: messagesEl,
5095
+ scrollToBottom: scrollToBottom,
5096
+ addCopyHandler: addCopyHandler,
5097
+ matesList: function () { return cachedMatesList || []; },
5098
+ currentMateId: function () { return (dmTargetUser && dmTargetUser.isMate) ? dmTargetUser.id : null; },
5099
+ });
5100
+
4961
5101
  // --- STT module (voice input via Web Speech API) ---
4962
5102
  initSTT({
4963
5103
  inputEl: inputEl,
@@ -5449,6 +5589,13 @@ import { initLongPress } from './modules/longpress.js';
5449
5589
  updateLoopBanner(loopIteration, loopMaxIterations, "running");
5450
5590
  }
5451
5591
  }
5592
+
5593
+ // Restore debate sticky on session switch
5594
+ if (debateStickyState && debateStickyState.phase) {
5595
+ showDebateSticky(debateStickyState.phase, debateStickyState.msg);
5596
+ } else {
5597
+ showDebateSticky("hide", null);
5598
+ }
5452
5599
  }
5453
5600
 
5454
5601
  // --- Skill install dialog (generic) ---
@@ -5677,6 +5824,24 @@ import { initLongPress } from './modules/longpress.js';
5677
5824
  }, cb);
5678
5825
  }
5679
5826
 
5827
+ function requireClayDebateSetup(cb) {
5828
+ requireSkills({
5829
+ title: "Skill Installation Required",
5830
+ reason: "The Debate Setup skill is required to start a debate.",
5831
+ skills: [{ name: "clay-debate-setup", url: "https://github.com/chadbyte/clay-debate-setup", scope: "global" }]
5832
+ }, cb);
5833
+ }
5834
+
5835
+ // Debate button in mate sidebar
5836
+ var debateBtn = document.getElementById("mate-debate-btn");
5837
+ if (debateBtn) {
5838
+ debateBtn.addEventListener("click", function () {
5839
+ requireClayDebateSetup(function () {
5840
+ openDebateModal();
5841
+ });
5842
+ });
5843
+ }
5844
+
5680
5845
  // --- Ralph Wizard ---
5681
5846
 
5682
5847
  var wizardMode = "draft"; // "draft" or "own"
@@ -6124,6 +6289,130 @@ import { initLongPress } from './modules/longpress.js';
6124
6289
  }
6125
6290
  }
6126
6291
 
6292
+ // --- Debate Sticky Banner ---
6293
+ var debateStickyState = null;
6294
+ var debateHandRaiseOpen = false;
6295
+
6296
+ function showDebateSticky(phase, msg) {
6297
+ if (phase === "ended" || phase === "hide") {
6298
+ debateStickyState = null;
6299
+ } else {
6300
+ debateStickyState = { phase: phase, msg: msg };
6301
+ }
6302
+ var stickyEl = document.getElementById("debate-sticky");
6303
+ if (!stickyEl) return;
6304
+
6305
+ if (phase === "ended" || phase === "hide") {
6306
+ stickyEl.classList.add("hidden");
6307
+ stickyEl.innerHTML = "";
6308
+ debateHandRaiseOpen = false;
6309
+ return;
6310
+ }
6311
+
6312
+ var topic = (msg && msg.topic) || "Debate";
6313
+ var truncTopic = topic.length > 30 ? topic.slice(0, 30) + "\u2026" : topic;
6314
+
6315
+ if (phase === "preparing") {
6316
+ stickyEl.innerHTML =
6317
+ '<div class="debate-sticky-inner">' +
6318
+ '<div class="debate-sticky-header">' +
6319
+ '<span class="debate-sticky-icon">' + iconHtml("mic") + '</span>' +
6320
+ '<span class="debate-sticky-label">' + escapeHtml(truncTopic) + '</span>' +
6321
+ '<span class="debate-sticky-status" id="debate-sticky-status">Setting up\u2026</span>' +
6322
+ '<button class="debate-sticky-action debate-sticky-cancel" title="Cancel debate">' + iconHtml("x") + '</button>' +
6323
+ '</div>' +
6324
+ '</div>';
6325
+ stickyEl.classList.remove("hidden");
6326
+ stickyEl.className = "debate-sticky preparing";
6327
+ refreshIcons();
6328
+
6329
+ stickyEl.querySelector(".debate-sticky-cancel").addEventListener("click", function (e) {
6330
+ e.stopPropagation();
6331
+ if (ws && ws.readyState === 1) {
6332
+ ws.send(JSON.stringify({ type: "debate_stop" }));
6333
+ }
6334
+ stickyEl.classList.add("hidden");
6335
+ stickyEl.innerHTML = "";
6336
+ });
6337
+ } else if (phase === "live") {
6338
+ stickyEl.innerHTML =
6339
+ '<div class="debate-sticky-inner">' +
6340
+ '<div class="debate-sticky-header">' +
6341
+ '<span class="debate-sticky-icon">' + iconHtml("mic") + '</span>' +
6342
+ '<span class="debate-sticky-label">' + escapeHtml(truncTopic) + '</span>' +
6343
+ '<span class="debate-sticky-status" id="debate-sticky-status">Live</span>' +
6344
+ '<span class="debate-sticky-round" id="debate-sticky-round">R1</span>' +
6345
+ '<button class="debate-sticky-action debate-sticky-hand" title="Raise hand">' + iconHtml("hand") + '</button>' +
6346
+ '<button class="debate-sticky-action debate-sticky-stop" title="Stop debate">' + iconHtml("square") + '</button>' +
6347
+ '</div>' +
6348
+ '<div class="debate-sticky-hand-input hidden" id="debate-sticky-hand-input">' +
6349
+ '<textarea id="debate-sticky-comment" rows="1" placeholder="Your comment\u2026"></textarea>' +
6350
+ '<button class="debate-sticky-send" id="debate-sticky-send">Send</button>' +
6351
+ '<button class="debate-sticky-send-cancel" id="debate-sticky-send-cancel">Cancel</button>' +
6352
+ '</div>' +
6353
+ '</div>';
6354
+ stickyEl.classList.remove("hidden");
6355
+ stickyEl.className = "debate-sticky live";
6356
+ refreshIcons();
6357
+ debateHandRaiseOpen = false;
6358
+
6359
+ stickyEl.querySelector(".debate-sticky-hand").addEventListener("click", function (e) {
6360
+ e.stopPropagation();
6361
+ toggleDebateHandRaise();
6362
+ });
6363
+
6364
+ stickyEl.querySelector(".debate-sticky-stop").addEventListener("click", function (e) {
6365
+ e.stopPropagation();
6366
+ if (ws && ws.readyState === 1) {
6367
+ ws.send(JSON.stringify({ type: "debate_stop" }));
6368
+ }
6369
+ });
6370
+
6371
+ var sendBtn = document.getElementById("debate-sticky-send");
6372
+ var cancelBtn = document.getElementById("debate-sticky-send-cancel");
6373
+ var commentInput = document.getElementById("debate-sticky-comment");
6374
+
6375
+ if (sendBtn) sendBtn.addEventListener("click", function () { sendDebateStickyComment(); });
6376
+ if (cancelBtn) cancelBtn.addEventListener("click", function () { toggleDebateHandRaise(false); });
6377
+ if (commentInput) {
6378
+ commentInput.addEventListener("keydown", function (e) {
6379
+ if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendDebateStickyComment(); }
6380
+ if (e.key === "Escape") { toggleDebateHandRaise(false); }
6381
+ });
6382
+ }
6383
+ }
6384
+ }
6385
+
6386
+ function toggleDebateHandRaise(forceState) {
6387
+ var inputWrap = document.getElementById("debate-sticky-hand-input");
6388
+ var commentInput = document.getElementById("debate-sticky-comment");
6389
+ if (!inputWrap) return;
6390
+ var show = typeof forceState === "boolean" ? forceState : !debateHandRaiseOpen;
6391
+ debateHandRaiseOpen = show;
6392
+ if (show) {
6393
+ inputWrap.classList.remove("hidden");
6394
+ if (commentInput) { commentInput.value = ""; commentInput.focus(); }
6395
+ } else {
6396
+ inputWrap.classList.add("hidden");
6397
+ }
6398
+ }
6399
+
6400
+ function sendDebateStickyComment() {
6401
+ var commentInput = document.getElementById("debate-sticky-comment");
6402
+ if (!commentInput) return;
6403
+ var text = commentInput.value.trim();
6404
+ if (!text) return;
6405
+ if (ws && ws.readyState === 1) {
6406
+ ws.send(JSON.stringify({ type: "debate_comment", text: text }));
6407
+ }
6408
+ toggleDebateHandRaise(false);
6409
+ }
6410
+
6411
+ function updateDebateRound(round) {
6412
+ var roundEl = document.getElementById("debate-sticky-round");
6413
+ if (roundEl) roundEl.textContent = "R" + round;
6414
+ }
6415
+
6127
6416
  // --- Ralph Preview Modal ---
6128
6417
  function openRalphPreviewModal() {
6129
6418
  var modal = document.getElementById("ralph-preview-modal");
@@ -201,6 +201,7 @@ html, body {
201
201
  flex-direction: column;
202
202
  height: 100%;
203
203
  height: 100dvh;
204
+ position: relative;
204
205
  }
205
206
 
206
207
  #layout-body {
@@ -629,6 +629,11 @@
629
629
  #input-area {
630
630
  padding-bottom: calc(var(--safe-bottom) + 56px + 8px);
631
631
  }
632
+
633
+ /* When keyboard is open, tab bar is hidden behind keyboard — remove the offset */
634
+ body.keyboard-open #input-area {
635
+ padding-bottom: 8px;
636
+ }
632
637
  }
633
638
 
634
639
  /* ==========================================================================
@@ -1958,10 +1958,7 @@ body.mate-dm-active .mate-permission.resolved .permission-decision-label {
1958
1958
  color: var(--text-dimmer);
1959
1959
  }
1960
1960
 
1961
- /* --- Mate Activity: avatar + text (hidden, dots row is enough) --- */
1962
- body.mate-dm-active .activity-inline {
1963
- display: none;
1964
- }
1961
+ /* --- Mate Activity: avatar + text --- */
1965
1962
 
1966
1963
  /* --- Mate AskUserQuestion: avatar + content layout (matches msg-assistant) --- */
1967
1964
  body.mate-dm-active .mate-ask-user {
@@ -2005,8 +2002,10 @@ body.mate-dm-active .ask-user-container:not(.mate-ask-user),
2005
2002
  body.mate-dm-active .subagent-log,
2006
2003
  body.mate-dm-active .conflict-msg,
2007
2004
  body.mate-dm-active .context-overflow-msg,
2008
- body.mate-dm-active .sys-msg,
2009
- body.mate-dm-active .activity-inline {
2005
+ body.mate-dm-active .sys-msg {
2006
+ display: none;
2007
+ }
2008
+ body.mate-dm-active .activity-inline:not(.mention-activity-bar) {
2010
2009
  padding-left: 60px;
2011
2010
  margin-left: 0;
2012
2011
  margin-right: 0;