clay-server 2.19.0 → 2.20.0-beta.2

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.
Files changed (41) hide show
  1. package/README.md +51 -91
  2. package/bin/cli.js +49 -14
  3. package/lib/builtin-mates.js +360 -0
  4. package/lib/cli-sessions.js +3 -3
  5. package/lib/config.js +30 -4
  6. package/lib/daemon.js +28 -5
  7. package/lib/mates.js +169 -7
  8. package/lib/notes.js +20 -0
  9. package/lib/os-users.js +71 -2
  10. package/lib/project.js +903 -228
  11. package/lib/public/app.js +249 -69
  12. package/lib/public/css/icon-strip.css +55 -2
  13. package/lib/public/css/input.css +50 -30
  14. package/lib/public/css/mates.css +316 -2
  15. package/lib/public/css/mention.css +23 -0
  16. package/lib/public/css/mobile-nav.css +198 -0
  17. package/lib/public/css/overlays.css +23 -0
  18. package/lib/public/css/rewind.css +32 -0
  19. package/lib/public/css/title-bar.css +3 -0
  20. package/lib/public/css/user-settings.css +2 -2
  21. package/lib/public/index.html +65 -14
  22. package/lib/public/mates/ally.png +0 -0
  23. package/lib/public/mates/sage.jpg +0 -0
  24. package/lib/public/mates/scout.png +0 -0
  25. package/lib/public/modules/command-palette.js +44 -4
  26. package/lib/public/modules/filebrowser.js +2 -0
  27. package/lib/public/modules/input.js +158 -16
  28. package/lib/public/modules/mate-knowledge.js +2 -0
  29. package/lib/public/modules/mate-memory.js +353 -0
  30. package/lib/public/modules/mention.js +77 -2
  31. package/lib/public/modules/notifications.js +0 -8
  32. package/lib/public/modules/server-settings.js +11 -4
  33. package/lib/public/modules/sidebar.js +507 -21
  34. package/lib/public/modules/theme.js +10 -12
  35. package/lib/public/modules/tools.js +84 -12
  36. package/lib/public/modules/user-settings.js +45 -12
  37. package/lib/sdk-bridge.js +122 -61
  38. package/lib/server.js +209 -13
  39. package/lib/sessions.js +4 -4
  40. package/lib/users.js +89 -0
  41. package/package.json +1 -1
package/lib/public/app.js CHANGED
@@ -5,16 +5,17 @@ import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, closeMermaidM
5
5
  import { initSidebar, renderSessionList, handleSearchResults, updateSessionPresence, updatePageTitle, populateCliSessionList, renderIconStrip, renderSidebarPresence, initIconStrip, getEmojiCategories, renderUserStrip, setCurrentDmUser, updateDmBadge, updateSessionBadge, updateProjectBadge, closeDmUserPicker, spawnDustParticles, openMobileSheet, setMobileSheetMateData } from './modules/sidebar.js';
6
6
  import { initMateSidebar, showMateSidebar, hideMateSidebar, renderMateSessionList, updateMateSidebarProfile, handleMateSearchResults } from './modules/mate-sidebar.js';
7
7
  import { initMateKnowledge, requestKnowledgeList, renderKnowledgeList, handleKnowledgeContent, hideKnowledge } from './modules/mate-knowledge.js';
8
+ import { initMateMemory, renderMemoryList, hideMemory } from './modules/mate-memory.js';
8
9
  import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid, addRewindButton } from './modules/rewind.js';
9
10
  import { initNotifications, showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './modules/notifications.js';
10
- import { initInput, clearPendingImages, handleInputSync, autoResize, builtinCommands, sendMessage, hasSendableContent } from './modules/input.js';
11
+ import { initInput, clearPendingImages, handleInputSync, autoResize, builtinCommands, sendMessage, hasSendableContent, setScheduleBtnDisabled } from './modules/input.js';
11
12
  import { initQrCode, triggerShare } from './modules/qrcode.js';
12
13
  import { initFileBrowser, loadRootDirectory, refreshTree, handleFsList, handleFsRead, handleDirChanged, refreshIfOpen, handleFileChanged, handleFileHistory, handleGitDiff, handleFileAt, getPendingNavigate, closeFileViewer, resetFileBrowser } from './modules/filebrowser.js';
13
14
  import { initTerminal, openTerminal, closeTerminal, resetTerminals, handleTermList, handleTermCreated, handleTermOutput, handleTermExited, handleTermClosed, sendTerminalCommand } from './modules/terminal.js';
14
15
  import { initStickyNotes, handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, openArchive, closeArchive, isArchiveOpen, hideNotes, showNotes, isNotesVisible } from './modules/sticky-notes.js';
15
16
  import { initTheme, getThemeColor, getComputedVar, onThemeChange, getCurrentTheme } from './modules/theme.js';
16
17
  import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionResolved, markPermissionCancelled, renderElicitationRequest, markElicitationResolved, renderPlanBanner, renderPlanCard, handleTodoWrite, handleTaskCreate, handleTaskUpdate, startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, addTurnMeta, enableMainInput, getTools, getPlanContent, setPlanContent, isPlanFilePath, getTodoTools, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, updateSubagentProgress, initSubagentStop, closeToolGroup, removeToolFromGroup } from './modules/tools.js';
17
- import { initServerSettings, updateSettingsStats, updateSettingsModels, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './modules/server-settings.js';
18
+ import { initServerSettings, updateSettingsStats, updateSettingsModels, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './modules/server-settings.js';
18
19
  import { initProjectSettings, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, isProjectSettingsOpen, handleProjectSharedEnv, handleProjectSharedEnvSaved, handleProjectOwnerChanged } from './modules/project-settings.js';
19
20
  import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modules/skills.js';
20
21
  import { initScheduler, resetScheduler, handleLoopRegistryUpdated, handleScheduleRunStarted, handleScheduleRunFinished, handleLoopScheduled, openSchedulerToTab, isSchedulerOpen, closeScheduler, enterCraftingMode, exitCraftingMode, handleLoopRegistryFiles, getUpcomingSchedules } from './modules/scheduler.js';
@@ -74,6 +75,7 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
74
75
  var cachedDmConversations = [];
75
76
  var dmRemovedUsers = {}; // { userId: true } - users explicitly removed from favorites
76
77
  var cachedMatesList = []; // Cached list of mates for user strip
78
+ var cachedAvailableBuiltins = []; // Deleted built-in mates available for re-add
77
79
 
78
80
  // --- Mate WS (separate connection to mate project) ---
79
81
  var mateWs = null;
@@ -762,14 +764,16 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
762
764
  if (resizeHandle) resizeHandle.classList.remove("dm-mode");
763
765
  hideMateSidebar();
764
766
  hideKnowledge();
767
+ hideMemory();
765
768
  if (isSchedulerOpen()) closeScheduler();
766
769
  disconnectMateWs();
767
770
  // Restore terminal button
768
771
  var termBtn = document.getElementById("terminal-toggle-btn");
769
772
  if (termBtn) termBtn.style.display = "";
770
- // Re-request session list from main project to refresh state
773
+ // Re-request session list and notes from main project to refresh state
771
774
  if (ws && ws.readyState === 1) {
772
775
  ws.send(JSON.stringify({ type: "switch_session", id: activeSessionId }));
776
+ ws.send(JSON.stringify({ type: "note_list_request" }));
773
777
  }
774
778
 
775
779
  // Reset DM header
@@ -832,15 +836,74 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
832
836
  }
833
837
  })();
834
838
 
835
- function handleMateCreatedInApp(mate) {
839
+ function handleMateCreatedInApp(mate, msg) {
836
840
  if (!mate) return;
837
841
  cachedMatesList.push(mate);
842
+ if (msg && msg.availableBuiltins) cachedAvailableBuiltins = msg.availableBuiltins;
843
+ if (msg && msg.dmFavorites) cachedDmFavorites = msg.dmFavorites;
838
844
  renderUserStrip(cachedAllUsers, cachedOnlineIds, myUserId, cachedDmFavorites, cachedDmConversations, dmUnread, dmRemovedUsers, cachedMatesList);
839
- // Store pending interview data so we can auto-send after DM opens
840
- pendingMateInterview = mate;
845
+ // Built-in mates handle their own onboarding via CLAUDE.md, skip auto-interview
846
+ if (!mate.builtinKey) {
847
+ pendingMateInterview = mate;
848
+ }
841
849
  openDm(mate.id);
842
850
  }
843
851
 
852
+ function renderAvailableBuiltins(builtins) {
853
+ // Append deleted built-in mates to the mates list in the picker
854
+ var matesList = document.querySelector(".dm-mates-list");
855
+ if (!matesList) return;
856
+ if (!builtins || builtins.length === 0) return;
857
+
858
+ for (var i = 0; i < builtins.length; i++) {
859
+ (function (b) {
860
+ var item = document.createElement("div");
861
+ item.className = "dm-user-picker-item dm-user-picker-builtin-item";
862
+ item.style.opacity = "0.5";
863
+
864
+ var av = document.createElement("img");
865
+ av.className = "dm-user-picker-avatar";
866
+ av.src = b.avatarCustom || "";
867
+ av.alt = b.displayName;
868
+ item.appendChild(av);
869
+
870
+ var nameWrap = document.createElement("div");
871
+ nameWrap.style.cssText = "flex:1;min-width:0;";
872
+ var nameEl = document.createElement("span");
873
+ nameEl.className = "dm-user-picker-name";
874
+ nameEl.textContent = b.displayName;
875
+ nameWrap.appendChild(nameEl);
876
+ var bioEl = document.createElement("div");
877
+ bioEl.style.cssText = "font-size:11px;color:var(--text-dimmer);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;";
878
+ bioEl.textContent = "Deleted";
879
+ nameWrap.appendChild(bioEl);
880
+ item.appendChild(nameWrap);
881
+
882
+ var addBtn = document.createElement("button");
883
+ addBtn.style.cssText = "border:none;background:none;cursor:pointer;padding:2px 6px;color:var(--accent, #6366f1);font-size:12px;font-weight:600;";
884
+ addBtn.textContent = "+ Add";
885
+ addBtn.title = "Re-add " + b.displayName;
886
+ addBtn.addEventListener("click", function (e) {
887
+ e.stopPropagation();
888
+ if (ws && ws.readyState === 1) {
889
+ ws.send(JSON.stringify({ type: "mate_readd_builtin", builtinKey: b.key }));
890
+ }
891
+ closeDmUserPicker();
892
+ });
893
+ item.appendChild(addBtn);
894
+
895
+ item.addEventListener("click", function () {
896
+ if (ws && ws.readyState === 1) {
897
+ ws.send(JSON.stringify({ type: "mate_readd_builtin", builtinKey: b.key }));
898
+ }
899
+ closeDmUserPicker();
900
+ });
901
+
902
+ matesList.appendChild(item);
903
+ })(builtins[i]);
904
+ }
905
+ }
906
+
844
907
  var pendingMateInterview = null;
845
908
 
846
909
  function buildMateInterviewPrompt(mate) {
@@ -923,6 +986,7 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
923
986
  // Intercept session_list for mate sidebar
924
987
  if (msg.type === "init" && msg.sessions) {
925
988
  renderMateSessionList(msg.sessions);
989
+ handlePaletteSessionSwitch();
926
990
  // Override title bar with mate name (not session/project title)
927
991
  if (dmTargetUser && dmTargetUser.isMate) {
928
992
  var mateDN = dmTargetUser.displayName || "New Mate";
@@ -952,6 +1016,13 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
952
1016
  if (msg.type === "knowledge_saved" || msg.type === "knowledge_deleted" || msg.type === "knowledge_promoted" || msg.type === "knowledge_depromoted") {
953
1017
  return; // list update follows separately
954
1018
  }
1019
+ if (msg.type === "memory_list") {
1020
+ renderMemoryList(msg.entries, msg.summary);
1021
+ return;
1022
+ }
1023
+ if (msg.type === "memory_deleted") {
1024
+ return; // list update follows separately
1025
+ }
955
1026
  // On done: scan DOM for [[MATE_READY: name]], update name, strip marker
956
1027
  if (msg.type === "done" && dmTargetUser && dmTargetUser.isMate) {
957
1028
  // Ensure last message is fully visible after rendering settles
@@ -1874,11 +1945,13 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
1874
1945
  getHistoryFrom: function () { return historyFrom; },
1875
1946
  get permissions() { return myPermissions; },
1876
1947
  get projectList() { return cachedProjects || []; },
1948
+ availableBuiltins: function () { return cachedAvailableBuiltins || []; },
1877
1949
  };
1878
1950
  initSidebar(sidebarCtx);
1879
1951
  initIconStrip(sidebarCtx);
1880
1952
  initMateSidebar(function () { return mateWs; });
1881
1953
  initMateKnowledge(function () { return mateWs; });
1954
+ initMateMemory(function () { return mateWs; }, { onShow: function () { hideKnowledge(); hideNotes(); } });
1882
1955
  initMateWizard(
1883
1956
  function (msg) { if (ws && ws.readyState === 1) ws.send(JSON.stringify(msg)); },
1884
1957
  function (mate) { handleMateCreatedInApp(mate); }
@@ -1886,9 +1959,10 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
1886
1959
 
1887
1960
  initCommandPalette({
1888
1961
  switchProject: function (slug) { switchProject(slug); },
1889
- currentSlug: function () { return currentSlug; },
1962
+ currentSlug: function () { return mateProjectSlug || currentSlug; },
1890
1963
  projectList: function () { return cachedProjects || []; },
1891
1964
  matesList: function () { return cachedMatesList || []; },
1965
+ availableBuiltins: function () { return cachedAvailableBuiltins || []; },
1892
1966
  allUsers: function () { return cachedAllUsers || []; },
1893
1967
  dmConversations: function () { return cachedDmConversations || []; },
1894
1968
  myUserId: function () { return myUserId; },
@@ -2178,10 +2252,7 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
2178
2252
  removeMatePreThinking();
2179
2253
  var mateName = dmTargetUser ? (dmTargetUser.displayName || "Mate") : "Mate";
2180
2254
  var mateAvatar = document.body.dataset.mateAvatarUrl || "";
2181
- matePreThinkingTimer = setTimeout(function () {
2182
- matePreThinkingTimer = null;
2183
- doShowMatePreThinking(mateName, mateAvatar);
2184
- }, 1000);
2255
+ doShowMatePreThinking(mateName, mateAvatar);
2185
2256
  }
2186
2257
  function doShowMatePreThinking(mateName, mateAvatar) {
2187
2258
  matePreThinkingEl = document.createElement("div");
@@ -2189,9 +2260,10 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
2189
2260
  matePreThinkingEl.innerHTML =
2190
2261
  '<img class="dm-bubble-avatar dm-bubble-avatar-mate" src="' + escapeHtml(mateAvatar) + '" alt="" style="display:block">' +
2191
2262
  '<div class="dm-bubble-content">' +
2192
- '<div class="mate-thinking-row" style="display:flex">' +
2193
- '<span class="dm-bubble-name">' + escapeHtml(mateName) + '</span>' +
2194
- '<span class="mate-thinking-dots"><span></span><span></span><span></span></span>' +
2263
+ '<div class="dm-bubble-header"><span class="dm-bubble-name">' + escapeHtml(mateName) + '</span></div>' +
2264
+ '<div class="activity-inline mate-pre-activity">' +
2265
+ '<span class="activity-icon">' + iconHtml("sparkles") + '</span>' +
2266
+ '<span class="activity-text">' + randomThinkingVerb() + '...</span>' +
2195
2267
  '</div>' +
2196
2268
  '</div>';
2197
2269
  if (activityEl && activityEl.parentNode) {
@@ -2199,6 +2271,7 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
2199
2271
  } else {
2200
2272
  addToMessages(matePreThinkingEl);
2201
2273
  }
2274
+ refreshIcons();
2202
2275
  scrollToBottom();
2203
2276
  }
2204
2277
  function removeMatePreThinking() {
@@ -2841,6 +2914,9 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
2841
2914
  function addToMessages(el) {
2842
2915
  if (prependAnchor) messagesEl.insertBefore(el, prependAnchor);
2843
2916
  else messagesEl.appendChild(el);
2917
+ if (scheduledMsgEl && el !== scheduledMsgEl && scheduledMsgEl.parentNode === messagesEl) {
2918
+ messagesEl.appendChild(scheduledMsgEl);
2919
+ }
2844
2920
  }
2845
2921
 
2846
2922
  var newMsgBtn = $("new-msg-btn");
@@ -3479,6 +3555,98 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
3479
3555
  }
3480
3556
  }
3481
3557
 
3558
+ // --- Rate Limit Usage (top bar link to check usage + reset time) ---
3559
+
3560
+ var rateLimitUsageEl = null;
3561
+ var rateLimitResetState = {};
3562
+ var rateLimitTickTimer = null;
3563
+
3564
+ function formatResetTime(resetsAt) {
3565
+ if (!resetsAt) return "";
3566
+ var d = new Date(resetsAt);
3567
+ var now = new Date();
3568
+ var diff = resetsAt - now.getTime();
3569
+ if (diff <= 0) return "";
3570
+ var hrs = Math.floor(diff / 3600000);
3571
+ var mins = Math.floor((diff % 3600000) / 60000);
3572
+ if (hrs > 0) return hrs + "h " + mins + "m";
3573
+ return mins + "m";
3574
+ }
3575
+
3576
+ function rateLimitTypeShortLabel(type) {
3577
+ if (type === "five_hour") return "5h";
3578
+ if (type === "seven_day") return "7d";
3579
+ if (type === "seven_day_opus") return "7d opus";
3580
+ if (type === "seven_day_sonnet") return "7d sonnet";
3581
+ return type || "";
3582
+ }
3583
+
3584
+ function updateRateLimitUsage(msg) {
3585
+ if (msg.rateLimitType && msg.resetsAt) {
3586
+ rateLimitResetState[msg.rateLimitType] = { resetsAt: msg.resetsAt, status: msg.status };
3587
+ }
3588
+
3589
+ var topBarActions = document.querySelector("#top-bar .top-bar-actions");
3590
+ if (!topBarActions) return;
3591
+
3592
+ if (!rateLimitUsageEl) {
3593
+ rateLimitUsageEl = document.createElement("a");
3594
+ rateLimitUsageEl.id = "rate-limit-usage-link";
3595
+ rateLimitUsageEl.className = "top-bar-pill pill-dim usage-check-link";
3596
+ rateLimitUsageEl.href = "https://claude.ai/settings/usage";
3597
+ rateLimitUsageEl.target = "_blank";
3598
+ rateLimitUsageEl.rel = "noopener";
3599
+ rateLimitUsageEl.title = "Check usage on claude.ai";
3600
+ var ref = document.getElementById("skip-perms-pill");
3601
+ topBarActions.insertBefore(rateLimitUsageEl, ref);
3602
+ }
3603
+
3604
+ // Build label from available reset times
3605
+ var parts = [];
3606
+ var types = ["five_hour", "seven_day", "seven_day_opus", "seven_day_sonnet"];
3607
+ for (var i = 0; i < types.length; i++) {
3608
+ var entry = rateLimitResetState[types[i]];
3609
+ if (!entry || !entry.resetsAt) continue;
3610
+ var timeStr = formatResetTime(entry.resetsAt);
3611
+ if (!timeStr) continue;
3612
+ parts.push(rateLimitTypeShortLabel(types[i]) + " resets " + timeStr);
3613
+ }
3614
+
3615
+ var label = parts.length > 0 ? parts.join(" · ") : "Check usage";
3616
+ rateLimitUsageEl.innerHTML = iconHtml("activity") + '<span>' + label + '</span>' + iconHtml("external-link");
3617
+ refreshIcons();
3618
+
3619
+ // Start or stop live countdown tick
3620
+ if (parts.length > 0 && !rateLimitTickTimer) {
3621
+ rateLimitTickTimer = setInterval(tickRateLimitUsage, 30000);
3622
+ } else if (parts.length === 0 && rateLimitTickTimer) {
3623
+ clearInterval(rateLimitTickTimer);
3624
+ rateLimitTickTimer = null;
3625
+ }
3626
+ }
3627
+
3628
+ function tickRateLimitUsage() {
3629
+ if (!rateLimitUsageEl) return;
3630
+ var parts = [];
3631
+ var types = ["five_hour", "seven_day", "seven_day_opus", "seven_day_sonnet"];
3632
+ for (var i = 0; i < types.length; i++) {
3633
+ var entry = rateLimitResetState[types[i]];
3634
+ if (!entry || !entry.resetsAt) continue;
3635
+ var timeStr = formatResetTime(entry.resetsAt);
3636
+ if (!timeStr) { delete rateLimitResetState[types[i]]; continue; }
3637
+ parts.push(rateLimitTypeShortLabel(types[i]) + " resets " + timeStr);
3638
+ }
3639
+ if (parts.length === 0) {
3640
+ rateLimitUsageEl.innerHTML = iconHtml("activity") + '<span>Check usage</span>' + iconHtml("external-link");
3641
+ refreshIcons();
3642
+ if (rateLimitTickTimer) { clearInterval(rateLimitTickTimer); rateLimitTickTimer = null; }
3643
+ return;
3644
+ }
3645
+ var label = parts.join(" · ");
3646
+ rateLimitUsageEl.innerHTML = iconHtml("activity") + '<span>' + label + '</span>' + iconHtml("external-link");
3647
+ refreshIcons();
3648
+ }
3649
+
3482
3650
  function handleRateLimitEvent(msg) {
3483
3651
  var isRejected = msg.status === "rejected";
3484
3652
  var typeLabel = rateLimitTypeLabel(msg.rateLimitType);
@@ -3493,13 +3661,12 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
3493
3661
  popoverText = typeLabel + " limit exceeded";
3494
3662
  updateRateLimitIndicator(msg);
3495
3663
  startRateLimitCountdown(null, msg.resetsAt, null);
3496
- // Track for schedule mode
3664
+ // Track rate limit reset time
3497
3665
  rateLimitResetsAt = msg.resetsAt;
3498
3666
  if (rateLimitResetTimer) clearTimeout(rateLimitResetTimer);
3499
3667
  rateLimitResetTimer = setTimeout(function () {
3500
3668
  rateLimitResetsAt = null;
3501
3669
  rateLimitResetTimer = null;
3502
- exitScheduleMode();
3503
3670
  }, msg.resetsAt - Date.now() + 1000);
3504
3671
  } else {
3505
3672
  var pct = msg.utilization ? Math.round(msg.utilization * 100) : null;
@@ -3510,32 +3677,6 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
3510
3677
  showRateLimitPopover(popoverText, isRejected);
3511
3678
  }
3512
3679
 
3513
- // --- Schedule mode (rate limit) ---
3514
-
3515
- var scheduleModeActive = false;
3516
-
3517
- function enterScheduleMode() {
3518
- if (scheduleModeActive) return;
3519
- scheduleModeActive = true;
3520
- var inputRow = document.getElementById("input-row");
3521
- if (inputRow) inputRow.classList.add("input-rate-limited");
3522
- if (inputEl) {
3523
- inputEl.dataset.originalPlaceholder = inputEl.placeholder;
3524
- inputEl.placeholder = "Schedule message after limit resets...";
3525
- }
3526
- }
3527
-
3528
- function exitScheduleMode() {
3529
- if (!scheduleModeActive) return;
3530
- scheduleModeActive = false;
3531
- var inputRow = document.getElementById("input-row");
3532
- if (inputRow) inputRow.classList.remove("input-rate-limited");
3533
- if (inputEl && inputEl.dataset.originalPlaceholder) {
3534
- inputEl.placeholder = inputEl.dataset.originalPlaceholder;
3535
- delete inputEl.dataset.originalPlaceholder;
3536
- }
3537
- }
3538
-
3539
3680
  // --- Scheduled message in chat history ---
3540
3681
 
3541
3682
  var scheduledMsgEl = null;
@@ -3544,15 +3685,15 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
3544
3685
  function addScheduledMessageBubble(text, resetsAt) {
3545
3686
  removeScheduledMessageBubble();
3546
3687
  var wrap = document.createElement("div");
3547
- wrap.className = "scheduled-msg-wrap";
3688
+ wrap.className = "msg-user scheduled-msg-wrap";
3548
3689
  wrap.id = "scheduled-msg-bubble";
3549
3690
 
3550
3691
  var bubble = document.createElement("div");
3551
- bubble.className = "scheduled-msg-bubble";
3692
+ bubble.className = "bubble scheduled-msg-bubble";
3552
3693
 
3553
- var textEl = document.createElement("div");
3554
- textEl.className = "scheduled-msg-text";
3694
+ var textEl = document.createElement("span");
3555
3695
  textEl.textContent = text;
3696
+ bubble.appendChild(textEl);
3556
3697
 
3557
3698
  var metaEl = document.createElement("div");
3558
3699
  metaEl.className = "scheduled-msg-meta";
@@ -3569,7 +3710,7 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
3569
3710
  var cancelBtn = document.createElement("button");
3570
3711
  cancelBtn.className = "scheduled-msg-cancel";
3571
3712
  cancelBtn.title = "Cancel scheduled message";
3572
- cancelBtn.innerHTML = iconHtml("x");
3713
+ cancelBtn.textContent = "\u00d7";
3573
3714
  cancelBtn.addEventListener("click", function () {
3574
3715
  if (ws && ws.readyState === 1) {
3575
3716
  ws.send(JSON.stringify({ type: "cancel_scheduled_message" }));
@@ -3577,10 +3718,9 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
3577
3718
  });
3578
3719
  metaEl.appendChild(cancelBtn);
3579
3720
 
3580
- bubble.appendChild(textEl);
3581
- bubble.appendChild(metaEl);
3582
3721
  wrap.appendChild(bubble);
3583
- messagesEl.appendChild(wrap);
3722
+ wrap.appendChild(metaEl);
3723
+ addToMessages(wrap);
3584
3724
  scheduledMsgEl = wrap;
3585
3725
  scrollToBottom();
3586
3726
 
@@ -3606,8 +3746,6 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
3606
3746
  }
3607
3747
  updateCountdown();
3608
3748
  scheduledCountdownTimer = setInterval(updateCountdown, 1000);
3609
-
3610
- refreshIcons(wrap);
3611
3749
  }
3612
3750
 
3613
3751
  function removeScheduledMessageBubble() {
@@ -3621,6 +3759,7 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
3621
3759
  }
3622
3760
  }
3623
3761
 
3762
+
3624
3763
  // --- Fast Mode State ---
3625
3764
 
3626
3765
  var fastModeIndicatorEl = null;
@@ -3745,6 +3884,7 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
3745
3884
  }
3746
3885
  resetFileBrowser();
3747
3886
  closeArchive();
3887
+ hideMemory();
3748
3888
  if (isSchedulerOpen()) closeScheduler();
3749
3889
  resetScheduler(slug);
3750
3890
  currentSlug = slug;
@@ -3854,6 +3994,7 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
3854
3994
  if (savedMainWs === this) return;
3855
3995
 
3856
3996
  if (connectTimeoutId) { clearTimeout(connectTimeoutId); connectTimeoutId = null; }
3997
+ closeDmUserPicker();
3857
3998
  setStatus("disconnected");
3858
3999
  processing = false;
3859
4000
  setActivity(null);
@@ -4016,6 +4157,8 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
4016
4157
  updatePageTitle();
4017
4158
  if (msg.version) {
4018
4159
  setPaletteVersion(msg.version);
4160
+ var serverVersionEl = document.getElementById("settings-server-version");
4161
+ if (serverVersionEl) serverVersionEl.textContent = msg.version;
4019
4162
  }
4020
4163
  if (msg.projectOwnerId !== undefined) currentProjectOwnerId = msg.projectOwnerId;
4021
4164
  if (msg.osUsers !== undefined) isOsUsers = !!msg.osUsers;
@@ -4062,6 +4205,29 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
4062
4205
  }
4063
4206
  break;
4064
4207
 
4208
+ case "up_to_date":
4209
+ var utdBtn = $("settings-update-check");
4210
+ if (utdBtn) {
4211
+ utdBtn.innerHTML = "";
4212
+ var utdIcon = document.createElement("i");
4213
+ utdIcon.setAttribute("data-lucide", "check");
4214
+ utdBtn.appendChild(utdIcon);
4215
+ utdBtn.appendChild(document.createTextNode(" Up to date (v" + msg.version + ")"));
4216
+ utdBtn.disabled = true;
4217
+ refreshIcons();
4218
+ setTimeout(function () {
4219
+ utdBtn.innerHTML = "";
4220
+ var rwIcon = document.createElement("i");
4221
+ rwIcon.setAttribute("data-lucide", "refresh-cw");
4222
+ utdBtn.appendChild(rwIcon);
4223
+ utdBtn.appendChild(document.createTextNode(" Check for updates"));
4224
+ utdBtn.disabled = false;
4225
+ utdBtn.classList.remove("settings-btn-update-available");
4226
+ refreshIcons();
4227
+ }, 3000);
4228
+ }
4229
+ break;
4230
+
4065
4231
  case "update_started":
4066
4232
  var updNowBtn = $("update-now");
4067
4233
  if (updNowBtn) {
@@ -4285,14 +4451,16 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
4285
4451
  case "status":
4286
4452
  if (msg.status === "processing") {
4287
4453
  setStatus("processing");
4288
- setActivity(randomThinkingVerb() + "...");
4454
+ if (!(dmMode && dmTargetUser && dmTargetUser.isMate)) {
4455
+ setActivity(randomThinkingVerb() + "...");
4456
+ }
4289
4457
  }
4290
4458
  break;
4291
4459
 
4292
4460
  case "compacting":
4293
4461
  if (msg.active) {
4294
4462
  setActivity("Compacting conversation...");
4295
- } else {
4463
+ } else if (!(dmMode && dmTargetUser && dmTargetUser.isMate)) {
4296
4464
  setActivity(randomThinkingVerb() + "...");
4297
4465
  }
4298
4466
  break;
@@ -4308,7 +4476,9 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
4308
4476
 
4309
4477
  case "thinking_stop":
4310
4478
  stopThinking(msg.duration);
4311
- setActivity(randomThinkingVerb() + "...");
4479
+ if (!(dmMode && dmTargetUser && dmTargetUser.isMate)) {
4480
+ setActivity(randomThinkingVerb() + "...");
4481
+ }
4312
4482
  break;
4313
4483
 
4314
4484
  case "delta":
@@ -4393,7 +4563,7 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
4393
4563
  break;
4394
4564
 
4395
4565
  case "ask_user_answered":
4396
- markAskUserAnswered(msg.toolId);
4566
+ markAskUserAnswered(msg.toolId, msg.answers);
4397
4567
  stopUrgentBlink();
4398
4568
  break;
4399
4569
 
@@ -4488,10 +4658,6 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
4488
4658
  if (isNotifAlertEnabled() && !window._pushSubscription) showDoneNotification();
4489
4659
  if (isNotifSoundEnabled()) playDoneSound();
4490
4660
  }
4491
- // Enter schedule mode if rate limited
4492
- if (rateLimitResetsAt && rateLimitResetsAt > Date.now() && msg.code === 1) {
4493
- enterScheduleMode();
4494
- }
4495
4661
  break;
4496
4662
 
4497
4663
  case "stderr":
@@ -4522,23 +4688,25 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
4522
4688
  handleRateLimitEvent(msg);
4523
4689
  break;
4524
4690
 
4691
+ case "rate_limit_usage":
4692
+ updateRateLimitUsage(msg);
4693
+ break;
4694
+
4525
4695
  case "scheduled_message_queued":
4526
4696
  addScheduledMessageBubble(msg.text, msg.resetsAt);
4527
- exitScheduleMode();
4697
+ setScheduleBtnDisabled(true);
4528
4698
  break;
4529
4699
 
4530
4700
  case "scheduled_message_sent":
4531
4701
  removeScheduledMessageBubble();
4702
+ setScheduleBtnDisabled(false);
4532
4703
  processing = true;
4533
4704
  setStatus("processing");
4534
4705
  break;
4535
4706
 
4536
4707
  case "scheduled_message_cancelled":
4537
4708
  removeScheduledMessageBubble();
4538
- // Re-enter schedule mode if still rate limited
4539
- if (rateLimitResetsAt && rateLimitResetsAt > Date.now()) {
4540
- enterScheduleMode();
4541
- }
4709
+ setScheduleBtnDisabled(false);
4542
4710
  break;
4543
4711
 
4544
4712
  case "auto_continue_scheduled":
@@ -4809,11 +4977,12 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
4809
4977
  break;
4810
4978
 
4811
4979
  case "mate_created":
4812
- handleMateCreatedInApp(msg.mate);
4980
+ handleMateCreatedInApp(msg.mate, msg);
4813
4981
  break;
4814
4982
 
4815
4983
  case "mate_deleted":
4816
4984
  cachedMatesList = cachedMatesList.filter(function (m) { return m.id !== msg.mateId; });
4985
+ if (msg.availableBuiltins) cachedAvailableBuiltins = msg.availableBuiltins;
4817
4986
  renderUserStrip(cachedAllUsers, cachedOnlineIds, myUserId, cachedDmFavorites, cachedDmConversations, dmUnread, dmRemovedUsers, cachedMatesList);
4818
4987
  // Clean up background WS for deleted mate
4819
4988
  var delSlug = "mate-" + msg.mateId;
@@ -4856,9 +5025,14 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
4856
5025
 
4857
5026
  case "mate_list":
4858
5027
  cachedMatesList = msg.mates || [];
5028
+ cachedAvailableBuiltins = msg.availableBuiltins || [];
4859
5029
  renderUserStrip(cachedAllUsers, cachedOnlineIds, myUserId, cachedDmFavorites, cachedDmConversations, dmUnread, dmRemovedUsers, cachedMatesList);
4860
5030
  break;
4861
5031
 
5032
+ case "mate_available_builtins":
5033
+ // Handled via mate_list.availableBuiltins now
5034
+ break;
5035
+
4862
5036
  case "mate_error":
4863
5037
  showToast(msg.error || "Mate operation failed", "error");
4864
5038
  break;
@@ -4987,6 +5161,11 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
4987
5161
  handleKeepAwakeChanged(msg);
4988
5162
  break;
4989
5163
 
5164
+ case "set_auto_continue_result":
5165
+ case "auto_continue_changed":
5166
+ handleAutoContinueChanged(msg);
5167
+ break;
5168
+
4990
5169
  case "restart_server_result":
4991
5170
  handleRestartResult(msg);
4992
5171
  break;
@@ -5293,9 +5472,6 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
5293
5472
  getMateName: function () { return dmTargetUser ? (dmTargetUser.displayName || "Mate") : "Mate"; },
5294
5473
  getMateAvatarUrl: function () { return document.body.dataset.mateAvatarUrl || ""; },
5295
5474
  showMatePreThinking: function () { showMatePreThinking(); },
5296
- isScheduleMode: function () { return scheduleModeActive; },
5297
- getRateLimitResetsAt: function () { return rateLimitResetsAt; },
5298
- exitScheduleMode: exitScheduleMode,
5299
5475
  });
5300
5476
 
5301
5477
  // --- @Mention module ---
@@ -5305,10 +5481,13 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
5305
5481
  inputEl: inputEl,
5306
5482
  messagesEl: messagesEl,
5307
5483
  matesList: function () { return cachedMatesList || []; },
5484
+ availableBuiltins: function () { return cachedAvailableBuiltins || []; },
5308
5485
  scrollToBottom: scrollToBottom,
5309
5486
  addUserMessage: addUserMessage,
5310
5487
  addCopyHandler: addCopyHandler,
5311
5488
  addToMessages: addToMessages,
5489
+ showImageModal: showImageModal,
5490
+ showPasteModal: showPasteModal,
5312
5491
  });
5313
5492
 
5314
5493
  // --- Debate module ---
@@ -5318,6 +5497,7 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
5318
5497
  scrollToBottom: scrollToBottom,
5319
5498
  addCopyHandler: addCopyHandler,
5320
5499
  matesList: function () { return cachedMatesList || []; },
5500
+ availableBuiltins: function () { return cachedAvailableBuiltins || []; },
5321
5501
  currentMateId: function () { return (dmTargetUser && dmTargetUser.isMate) ? dmTargetUser.id : null; },
5322
5502
  });
5323
5503