clay-server 4.1.0-beta.7 → 4.1.0-beta.9

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/project.js CHANGED
@@ -634,6 +634,8 @@ function createProjectContext(opts) {
634
634
  var access = opts.getProjectAccess ? opts.getProjectAccess() : { visibility: "public", ownerId: projectOwnerId || null };
635
635
  return !usersModule.isMultiUser() || !!(freshActor && (!opts.canAccessProjectSlug || opts.canAccessProjectSlug(freshActor.id, slug)) && usersModule.canAccessSession(freshActor.id, session, access));
636
636
  },
637
+ onItemCancelled: function (item) { return _scheduledMessages && _scheduledMessages.onQueueCancelled(item); },
638
+ onItemConsumed: function (item) { return _scheduledMessages && _scheduledMessages.onQueueConsumed(item); },
637
639
  });
638
640
 
639
641
  var sdk = null;
@@ -1360,6 +1362,8 @@ function createProjectContext(opts) {
1360
1362
  isDriverOperatedSession: function (session) {
1361
1363
  return !isMate && _sessionPair.workerPermission.isDriverOperated(session);
1362
1364
  },
1365
+ pendingMessageQueue: _pendingMessageQueue,
1366
+ consumePendingMessage: function (session) { return _userMessage && _userMessage.consumePendingMessage(session); },
1363
1367
  });
1364
1368
 
1365
1369
  // --- Loop engine (delegated to project-loop.js) ---
@@ -1544,8 +1548,8 @@ function createProjectContext(opts) {
1544
1548
  }
1545
1549
 
1546
1550
  // --- Schedule / cancel a message (used by WS handler and auto-continue) ---
1547
- function scheduleMessage(session, text, resetsAt, actorUser, targetWs) {
1548
- return _scheduledMessages ? _scheduledMessages.schedule(session, text, resetsAt, actorUser, targetWs) : null;
1551
+ function scheduleMessage(session, text, resetsAt, actorUser, targetWs, attachments, requestId, transportAccountId) {
1552
+ return _scheduledMessages ? _scheduledMessages.schedule(session, text, resetsAt, actorUser, targetWs, attachments, requestId, transportAccountId) : null;
1549
1553
  }
1550
1554
 
1551
1555
  function cancelScheduledMessage(session, actorUser, targetWs, correlation) {
@@ -1996,6 +2000,7 @@ function createProjectContext(opts) {
1996
2000
  beginHumanPairTurn: function (session) { return _sessionPair.beginHumanTurn(session); },
1997
2001
  authorizePendingDispatch: function (session, actor, item) {
1998
2002
  if (!session || sm.sessions.get(session.localId) !== session || !item || item.projectSlug !== slug) return false;
2003
+ if (_scheduledMessages && !_scheduledMessages.authorizePending(item, session)) return false;
1999
2004
  if (!isMate && _sessionPair.workerPermission && _sessionPair.workerPermission.isDriverOperated(session)) return false;
2000
2005
  if (!usersModule.isMultiUser()) return item.actorId === "default" || !!(actor && String(actor.id) === item.actorId);
2001
2006
  var freshActor = actor && actor.id && usersModule.findUserById(actor.id);
package/lib/public/app.js CHANGED
@@ -68,7 +68,7 @@ import { initHomeSidebar } from './modules/home-sidebar.js';
68
68
  import { openHomeMateAction } from './modules/home-mate-chat.js';
69
69
  import { initHomeSurfaceBoot } from './modules/home-surface-boot.js';
70
70
  import { rememberHomePrimarySurface } from './modules/home-surface.js';
71
- import { initRateLimit, handleRateLimitEvent as _rlHandleRateLimitEvent, updateRateLimitUsage as _rlUpdateRateLimitUsage, addScheduledMessageBubble as _rlAddScheduledMessageBubble, removeScheduledMessageBubble as _rlRemoveScheduledMessageBubble, handleFastModeState as _rlHandleFastModeState, getScheduledMsgEl, resetRateLimitState } from './modules/app-rate-limit.js';
71
+ import { initRateLimit, handleRateLimitEvent as _rlHandleRateLimitEvent, updateRateLimitUsage as _rlUpdateRateLimitUsage, handleFastModeState as _rlHandleFastModeState, resetRateLimitState } from './modules/app-rate-limit.js';
72
72
  import { initCursors, handleRemoteCursorMove as _curHandleRemoteCursorMove, handleRemoteCursorLeave as _curHandleRemoteCursorLeave, handleRemoteSelection as _curHandleRemoteSelection, clearRemoteCursors as _curClearRemoteCursors, initCursorToggle } from './modules/app-cursors.js';
73
73
  import { initDefaultAi } from './modules/default-ai.js';
74
74
  import { initFavicon, updateFavicon as _favUpdateFavicon, setSendBtnMode as _favSetSendBtnMode, blinkIO as _favBlinkIO, blinkSessionDot as _favBlinkSessionDot, updateCrossProjectBlink as _favUpdateCrossProjectBlink, startUrgentBlink as _favStartUrgentBlink, stopUrgentBlink as _favStopUrgentBlink, setActivity as _favSetActivity } from './modules/app-favicon.js';
@@ -844,8 +844,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
844
844
  // --- Rate Limit / Scheduled Messages / Fast Mode (delegated to app-rate-limit.js) ---
845
845
  function handleRateLimitEvent(msg) { _rlHandleRateLimitEvent(msg); }
846
846
  function updateRateLimitUsage(msg) { _rlUpdateRateLimitUsage(msg); }
847
- function addScheduledMessageBubble(text, resetsAt) { _rlAddScheduledMessageBubble(text, resetsAt); }
848
- function removeScheduledMessageBubble() { _rlRemoveScheduledMessageBubble(); }
849
847
  function handleFastModeState(state) { _rlHandleFastModeState(state); }
850
848
 
851
849
  // --- Prompt suggestion chips (delegated to app-rendering.js) ---
@@ -1717,112 +1717,3 @@ body.tui-composer-active #config-chip-wrap {
1717
1717
  font-weight: 600;
1718
1718
  color: inherit;
1719
1719
  }
1720
-
1721
- /* Scheduled message bubble: accent-tinted user bubble */
1722
- .scheduled-msg-wrap .bubble {
1723
- background: color-mix(in srgb, var(--accent) 18%, var(--user-bubble, var(--bg-alt)));
1724
- border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
1725
- }
1726
-
1727
- .scheduled-msg-wrap .msg-user-rewind-btn,
1728
- .scheduled-msg-wrap .msg-actions { display: none !important; }
1729
-
1730
- .scheduled-msg-meta {
1731
- display: flex;
1732
- align-items: center;
1733
- gap: 6px;
1734
- padding-right: 4px;
1735
- font-size: 12px;
1736
- color: var(--accent);
1737
- }
1738
-
1739
- .scheduled-msg-icon svg,
1740
- .scheduled-msg-icon .lucide {
1741
- width: 14px;
1742
- height: 14px;
1743
- }
1744
-
1745
- .scheduled-msg-countdown {
1746
- flex: 1;
1747
- }
1748
-
1749
-
1750
- /* Channel mode: scheduled badge in header row */
1751
- .scheduled-msg-badge {
1752
- display: inline-flex;
1753
- align-items: center;
1754
- gap: 5px;
1755
- padding: 3px 10px;
1756
- border-radius: 12px;
1757
- font-size: 12px;
1758
- font-weight: 600;
1759
- line-height: 1;
1760
- color: var(--accent);
1761
- background: color-mix(in srgb, var(--accent) 12%, transparent);
1762
- vertical-align: middle;
1763
- }
1764
- .scheduled-msg-badge svg,
1765
- .scheduled-msg-badge .lucide {
1766
- width: 13px;
1767
- height: 13px;
1768
- }
1769
- /* Scheduled message action links (shared by both modes) */
1770
- .scheduled-msg-actions {
1771
- display: inline-flex;
1772
- align-items: center;
1773
- gap: 6px;
1774
- margin-left: 4px;
1775
- }
1776
- .scheduled-msg-send-now {
1777
- padding: 0;
1778
- border: none;
1779
- background: none;
1780
- color: var(--accent);
1781
- font-size: 12px;
1782
- font-weight: 500;
1783
- font-family: inherit;
1784
- cursor: pointer;
1785
- transition: text-decoration 0.15s;
1786
- }
1787
- .scheduled-msg-send-now:hover {
1788
- text-decoration: underline;
1789
- }
1790
- .scheduled-msg-sep {
1791
- color: var(--text-dimmer);
1792
- font-size: 12px;
1793
- user-select: none;
1794
- }
1795
- .scheduled-msg-cancel {
1796
- padding: 0;
1797
- border: none;
1798
- background: none;
1799
- color: var(--text-dimmer);
1800
- font-size: 12px;
1801
- font-weight: 500;
1802
- font-family: inherit;
1803
- cursor: pointer;
1804
- transition: color 0.15s;
1805
- }
1806
- .scheduled-msg-cancel:hover {
1807
- color: var(--error);
1808
- }
1809
-
1810
- /* Channel mode: strip bubble box styling, render as plain text */
1811
- body.wide-view .scheduled-msg-wrap .bubble {
1812
- background: none;
1813
- border: none;
1814
- box-shadow: none;
1815
- padding: 0;
1816
- border-radius: 0;
1817
- }
1818
-
1819
- /* Channel mode: header alignment */
1820
- body.wide-view .scheduled-msg-wrap .dm-bubble-header {
1821
- display: flex;
1822
- align-items: center;
1823
- gap: 6px;
1824
- }
1825
- body.wide-view .scheduled-msg-wrap .scheduled-msg-countdown {
1826
- font-size: inherit;
1827
- color: inherit;
1828
- }
@@ -134,6 +134,9 @@ button.pending-message-drag:active { cursor: grabbing; }
134
134
  .pending-message-meta { display: flex; gap: 8px; margin-top: 3px; color: var(--text-dimmer); font-size: 9px; line-height: 1.2; }
135
135
  .pending-message-meta span + span::before { content: "·"; margin-right: 8px; }
136
136
  .pending-message-sending { color: var(--accent); font-weight: 650; }
137
+ .pending-message-schedule-meta { display: flex; align-items: center; gap: 4px; margin-top: 4px; color: var(--accent); font-size: 10px; font-variant-numeric: tabular-nums; }
138
+ .pending-message-schedule-meta .lucide { width: 11px; height: 11px; }
139
+ .pending-message-chip.is-future { border-color: color-mix(in srgb, var(--accent) 35%, var(--border-subtle)); }
137
140
 
138
141
  .pending-message-actions { display: flex; align-items: center; gap: 1px; padding-right: 5px; }
139
142
  .pending-message-actions button { cursor: pointer; }
@@ -28,7 +28,7 @@ import { syncPaneTitles, maybeRestoreSplitGroup, openGroup } from './split-view.
28
28
  import { showPairDialog, handlePairCreated, handleSplitDelegation, showWorkerDelegationNotice, hideWorkerDelegationNotice } from './split-pair-ui.js';
29
29
  import { handleSessionActionMessage } from './session-actions.js';
30
30
  import { renderWorkerProposal, updateWorkerProposal } from './worker-proposal.js';
31
- import { handleInputSync, autoResize, builtinCommands, setScheduleBtnDisabled, sendShellResultToAgent, handleAcceptedOrdinaryMessage } from './input.js';
31
+ import { handleInputSync, autoResize, builtinCommands, setScheduleBtnDisabled, sendShellResultToAgent, handleAcceptedOrdinaryMessage, handleScheduleMessageResult } from './input.js';
32
32
  import { startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, closeToolGroup, removeToolFromGroup, resetToolState, getTools, getPlanContent, setPlanContent, renderPlanBanner, renderPlanCard, getTodoTools, handleTodoWrite, handleTaskCreate, handleTaskUpdate, applyDeadSessionTodoCompaction, isPlanFilePath, enableMainInput, addTurnMeta, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, initSubagentStop, updateSubagentProgress, updateSubagentTaskStatus, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionCancelled, markPermissionResolved, renderElicitationRequest, markElicitationResolved, renderUserDialogRequest, markUserDialogResolved, updateThinkingTokens } from './tools.js';
33
33
  import { finishThinkingTurn, resumeThinkingAfterReplay } from './thinking-lifecycle.js';
34
34
  import { showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './notifications.js';
@@ -73,8 +73,7 @@ import { handleToolsState, handleToolInstalled, handleToolRemoved, handleToolSto
73
73
  import { handleHomeDockState } from './home-dock.js';
74
74
  import { handleToolFrameUrlState } from './home-tool-frame.js';
75
75
  import { enterDmMode, exitDmMode, handleMateCreatedInApp, updateMateIconStatus, appendDmMessage, showDmTypingIndicator, buildMateInterviewPrompt } from './app-dm.js';
76
- import { handleRateLimitEvent, updateRateLimitUsage, addScheduledMessageBubble, removeScheduledMessageBubble, handleFastModeState } from './app-rate-limit.js';
77
- import { applyScheduledMessageQueued, applyScheduledMessageState, applyScheduledMessageTerminal } from './scheduled-message-state.js';
76
+ import { handleRateLimitEvent, updateRateLimitUsage, handleFastModeState } from './app-rate-limit.js';
78
77
  import { handleRemoteCursorMove, handleRemoteCursorLeave, handleRemoteSelection, clearRemoteCursors } from './app-cursors.js';
79
78
  import { showLoopBanner, updateLoopBanner, updateLoopInputVisibility, showRalphApprovalBar, updateRalphApprovalStatus, openRalphPreviewModal, showExecModal, updateExecModalStatus } from './app-loop-ui.js';
80
79
  import { handleAutonomousRunMessage, syncAutonomousRunForSession } from './autonomous-run.js';
@@ -98,6 +97,10 @@ export function processMessage(msg) {
98
97
  if (handleAutonomousRunMessage(msg)) return;
99
98
  if (handleLoopInterviewMessage(msg)) return;
100
99
  if (handleScheduledTaskMessage(msg)) return;
100
+ if (msg && msg.type === "schedule_message_result") {
101
+ handleScheduleMessageResult(msg);
102
+ return;
103
+ }
101
104
  if (msg.type === "access_revoked") {
102
105
  window.location.assign("/");
103
106
  return;
@@ -1411,28 +1414,6 @@ export function processMessage(msg) {
1411
1414
  updateRateLimitUsage(msg);
1412
1415
  break;
1413
1416
 
1414
- case "scheduled_message_queued":
1415
- applyScheduledMessageQueued(msg, { add: addScheduledMessageBubble, setDisabled: setScheduleBtnDisabled });
1416
- break;
1417
-
1418
- case "scheduled_message_sent":
1419
- applyScheduledMessageTerminal(msg, { remove: removeScheduledMessageBubble, setDisabled: setScheduleBtnDisabled });
1420
- setStatus("processing");
1421
- break;
1422
-
1423
- case "scheduled_message_cancelled":
1424
- applyScheduledMessageTerminal(msg, { remove: removeScheduledMessageBubble, setDisabled: setScheduleBtnDisabled });
1425
- break;
1426
-
1427
- case "scheduled_message_state":
1428
- applyScheduledMessageState(msg, {
1429
- remove: removeScheduledMessageBubble,
1430
- setDisabled: setScheduleBtnDisabled,
1431
- add: addScheduledMessageBubble,
1432
- interrupted: function () { addSystemMessage("A scheduled message was interrupted during server recovery and was not sent again automatically."); },
1433
- });
1434
- break;
1435
-
1436
1417
  case "auto_continue_scheduled":
1437
1418
  // Scheduler auto-continue, just show info
1438
1419
  break;
@@ -377,6 +377,7 @@ export function resetClientState() {
377
377
  store.set({ currentFullText: "" });
378
378
  resetToolState();
379
379
  clearPendingImages();
380
+ store.set({ pendingScheduleAdmissions: {}, pendingScheduleAdmission: null });
380
381
  resetShellCommand();
381
382
  clearAllMentionActive();
382
383
  setActivityEl(null);
@@ -3,10 +3,6 @@
3
3
 
4
4
  import { iconHtml, refreshIcons } from './icons.js';
5
5
  import { store } from './store.js';
6
- import { scheduledMessageControl } from './scheduled-message-state.js';
7
- import { getWs } from './ws-ref.js';
8
- import { addToMessages, scrollToBottom } from './app-rendering.js';
9
- import { userAvatarUrl } from './avatar.js';
10
6
  import { setScheduleDelayMs, clearScheduleDelay } from './input.js';
11
7
 
12
8
  // --- Module-owned state ---
@@ -17,8 +13,6 @@ var rateLimitResetTimer = null;
17
13
  var rateLimitUsageEl = null;
18
14
  var rateLimitResetState = {};
19
15
  var rateLimitTickTimer = null;
20
- var scheduledMsgEl = null;
21
- var scheduledCountdownTimer = null;
22
16
  var fastModeIndicatorEl = null;
23
17
 
24
18
  // --- Internal helpers ---
@@ -299,178 +293,6 @@ export function updateRateLimitUsage(msg) {
299
293
  }
300
294
  }
301
295
 
302
- export function addScheduledMessageBubble(text, resetsAt, correlation) {
303
- removeScheduledMessageBubble();
304
- var isChannel = document.body.classList.contains("wide-view");
305
- var wrap = document.createElement("div");
306
- wrap.className = "msg-user scheduled-msg-wrap";
307
- wrap.id = "scheduled-msg-bubble";
308
-
309
- var countdownEl;
310
- var cancelBtn;
311
-
312
- if (isChannel) {
313
- // Channel mode: avatar + header with scheduled badge + message
314
- var _me = store.get('cachedAllUsers').find(function (u) { return u.id === store.get('myUserId'); });
315
- var _myName = document.body.dataset.myDisplayName || (_me && (_me.displayName || _me.username)) || "Me";
316
-
317
- var avi = document.createElement("img");
318
- avi.className = "dm-bubble-avatar dm-bubble-avatar-me";
319
- avi.src = document.body.dataset.myAvatarUrl || userAvatarUrl(_me || { id: store.get('myUserId') }, 36);
320
- wrap.appendChild(avi);
321
-
322
- var content = document.createElement("div");
323
- content.className = "dm-bubble-content";
324
-
325
- var header = document.createElement("div");
326
- header.className = "dm-bubble-header";
327
-
328
- var nameSpan = document.createElement("span");
329
- nameSpan.className = "dm-bubble-name";
330
- nameSpan.textContent = _myName;
331
- header.appendChild(nameSpan);
332
-
333
- var badge = document.createElement("span");
334
- badge.className = "scheduled-msg-badge";
335
- badge.innerHTML = iconHtml("clock");
336
- countdownEl = document.createElement("span");
337
- countdownEl.className = "scheduled-msg-countdown";
338
- badge.appendChild(countdownEl);
339
- header.appendChild(badge);
340
-
341
- var actions = document.createElement("span");
342
- actions.className = "scheduled-msg-actions";
343
-
344
- var sendNowBtn = document.createElement("button");
345
- sendNowBtn.className = "scheduled-msg-send-now";
346
- sendNowBtn.textContent = "Send now";
347
- sendNowBtn.addEventListener("click", function () {
348
- var ws = getWs();
349
- if (ws && ws.readyState === 1) {
350
- ws.send(JSON.stringify(scheduledMessageControl("send_scheduled_now", correlation)));
351
- }
352
- });
353
- actions.appendChild(sendNowBtn);
354
-
355
- var sep = document.createElement("span");
356
- sep.className = "scheduled-msg-sep";
357
- sep.textContent = "\u00b7";
358
- actions.appendChild(sep);
359
-
360
- cancelBtn = document.createElement("button");
361
- cancelBtn.className = "scheduled-msg-cancel";
362
- cancelBtn.textContent = "Cancel";
363
- cancelBtn.addEventListener("click", function () {
364
- var ws = getWs();
365
- if (ws && ws.readyState === 1) {
366
- ws.send(JSON.stringify(scheduledMessageControl("cancel_scheduled_message", correlation)));
367
- }
368
- });
369
- actions.appendChild(cancelBtn);
370
-
371
- header.appendChild(actions);
372
-
373
- content.appendChild(header);
374
-
375
- var bubble = document.createElement("div");
376
- bubble.className = "bubble scheduled-msg-bubble";
377
- var textEl = document.createElement("span");
378
- textEl.textContent = text;
379
- bubble.appendChild(textEl);
380
- content.appendChild(bubble);
381
-
382
- wrap.appendChild(content);
383
- } else {
384
- // Bubble mode: original layout
385
- var bubble = document.createElement("div");
386
- bubble.className = "bubble scheduled-msg-bubble";
387
-
388
- var textEl = document.createElement("span");
389
- textEl.textContent = text;
390
- bubble.appendChild(textEl);
391
-
392
- var metaEl = document.createElement("div");
393
- metaEl.className = "scheduled-msg-meta";
394
-
395
- var clockIcon = document.createElement("span");
396
- clockIcon.className = "scheduled-msg-icon";
397
- clockIcon.innerHTML = iconHtml("clock");
398
- metaEl.appendChild(clockIcon);
399
-
400
- countdownEl = document.createElement("span");
401
- countdownEl.className = "scheduled-msg-countdown";
402
- metaEl.appendChild(countdownEl);
403
-
404
- var sendNowBtn2 = document.createElement("button");
405
- sendNowBtn2.className = "scheduled-msg-send-now";
406
- sendNowBtn2.textContent = "Send now";
407
- sendNowBtn2.addEventListener("click", function () {
408
- var ws = getWs();
409
- if (ws && ws.readyState === 1) {
410
- ws.send(JSON.stringify(scheduledMessageControl("send_scheduled_now", correlation)));
411
- }
412
- });
413
- metaEl.appendChild(sendNowBtn2);
414
-
415
- var sep2 = document.createElement("span");
416
- sep2.className = "scheduled-msg-sep";
417
- sep2.textContent = "\u00b7";
418
- metaEl.appendChild(sep2);
419
-
420
- cancelBtn = document.createElement("button");
421
- cancelBtn.className = "scheduled-msg-cancel";
422
- cancelBtn.textContent = "Cancel";
423
- cancelBtn.addEventListener("click", function () {
424
- var ws = getWs();
425
- if (ws && ws.readyState === 1) {
426
- ws.send(JSON.stringify(scheduledMessageControl("cancel_scheduled_message", correlation)));
427
- }
428
- });
429
- metaEl.appendChild(cancelBtn);
430
-
431
- wrap.appendChild(bubble);
432
- wrap.appendChild(metaEl);
433
- }
434
-
435
- addToMessages(wrap);
436
- scheduledMsgEl = wrap;
437
- scrollToBottom();
438
-
439
- // Start countdown
440
- function updateCountdown() {
441
- var remaining = resetsAt - Date.now();
442
- if (remaining <= 0) {
443
- countdownEl.textContent = "Sending...";
444
- if (scheduledCountdownTimer) { clearInterval(scheduledCountdownTimer); scheduledCountdownTimer = null; }
445
- return;
446
- }
447
- var hrs = Math.floor(remaining / 3600000);
448
- var mins = Math.floor((remaining % 3600000) / 60000);
449
- var secs = Math.floor((remaining % 60000) / 1000);
450
- var timeStr = "";
451
- if (hrs > 0) timeStr += hrs + "h ";
452
- if (mins > 0 || hrs > 0) timeStr += mins + "m ";
453
- timeStr += secs + "s";
454
-
455
- var sendDate = new Date(resetsAt);
456
- var absTime = sendDate.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
457
- countdownEl.textContent = "Sends at " + absTime + " (" + timeStr + ")";
458
- }
459
- updateCountdown();
460
- scheduledCountdownTimer = setInterval(updateCountdown, 1000);
461
- }
462
-
463
- export function removeScheduledMessageBubble() {
464
- if (scheduledMsgEl) {
465
- scheduledMsgEl.remove();
466
- scheduledMsgEl = null;
467
- }
468
- if (scheduledCountdownTimer) {
469
- clearInterval(scheduledCountdownTimer);
470
- scheduledCountdownTimer = null;
471
- }
472
- }
473
-
474
296
  export function handleFastModeState(state) {
475
297
  var statusArea = document.querySelector(".title-bar-content .status");
476
298
  if (!statusArea) return;
@@ -498,12 +320,8 @@ export function handleFastModeState(state) {
498
320
  refreshIcons();
499
321
  }
500
322
 
501
- export function getScheduledMsgEl() { return scheduledMsgEl; }
502
-
503
323
  export function resetRateLimitState() {
504
324
  clearRateLimitIndicator();
505
- store.set({ scheduledMessageCorrelation: null });
506
- removeScheduledMessageBubble();
507
325
  if (rateLimitCountdownTimer) { clearInterval(rateLimitCountdownTimer); rateLimitCountdownTimer = null; }
508
326
  if (fastModeIndicatorEl) { fastModeIndicatorEl.remove(); fastModeIndicatorEl = null; }
509
327
  }
@@ -145,8 +145,6 @@ export function addToMessages(element) {
145
145
  var messagesEl = getMessagesEl();
146
146
  if (prependAnchor) messagesEl.insertBefore(element, prependAnchor);
147
147
  else messagesEl.appendChild(element);
148
- var scheduled = document.getElementById("scheduled-msg-bubble");
149
- if (scheduled && element !== scheduled && scheduled.parentNode === messagesEl) messagesEl.appendChild(scheduled);
150
148
  }
151
149
 
152
150
  export function scrollToBottom() {
@@ -11,6 +11,7 @@ import { showToast } from './utils.js';
11
11
  import { isShellCommandMode, submitShellCommand } from './shell-command.js';
12
12
  import { showPasteModal } from './paste-modal.js';
13
13
  import { prepareAutonomousPayload } from './autonomous-run.js';
14
+ export { setScheduleBtnDisabled } from './scheduled-message-state.js';
14
15
 
15
16
  var ctx;
16
17
 
@@ -25,6 +26,7 @@ var isComposing = false;
25
26
  var isRemoteInput = false;
26
27
  var scheduleDelayMs = 0; // 0 = no schedule, >0 = delay in ms
27
28
  var _setScheduleDelayFn = null; // set by initInput
29
+ var scheduleRequestCounter = 0;
28
30
 
29
31
  export function hasSendableContent() {
30
32
  return !!(
@@ -58,19 +60,6 @@ export function clearScheduleDelay() {
58
60
  }
59
61
  }
60
62
 
61
- export function setScheduleBtnDisabled(disabled) {
62
- var btn = document.getElementById("schedule-btn");
63
- if (!btn) return;
64
- btn.disabled = disabled;
65
- if (disabled) {
66
- btn.style.opacity = "0.3";
67
- btn.style.pointerEvents = "none";
68
- } else {
69
- btn.style.opacity = "";
70
- btn.style.pointerEvents = "";
71
- }
72
- }
73
-
74
63
  export var builtinCommands = [
75
64
  { name: "clear", desc: "Clear conversation" },
76
65
  { name: "context", desc: "Context window usage" },
@@ -94,6 +83,24 @@ function commitVendorForTurn() {
94
83
  store.set({ vendorSelectionLocked: false, sessionHasHistory: true });
95
84
  }
96
85
 
86
+ function scheduleAdmissionMap() {
87
+ return store.get("pendingScheduleAdmissions") || {};
88
+ }
89
+
90
+ function scheduleDraftMatches(left, right) {
91
+ return !!left && !!right && JSON.stringify(left) === JSON.stringify(right);
92
+ }
93
+
94
+ function scheduleContextMatches(left, right) {
95
+ return !!left && !!right && left.projectSlug === right.projectSlug && String(left.sessionId) === String(right.sessionId) && left.accountId === right.accountId;
96
+ }
97
+
98
+ store.subscribe(function (state, previous) {
99
+ if (state.currentSlug === previous.currentSlug && state.activeSessionId === previous.activeSessionId &&
100
+ state.myUserId === previous.myUserId && state.connected === previous.connected) return;
101
+ if (Object.keys(scheduleAdmissionMap()).length > 0) store.set({ pendingScheduleAdmissions: {}, pendingScheduleAdmission: null });
102
+ });
103
+
97
104
  function showPreThinkingForTurn() {
98
105
  if (ctx.isMateDm && ctx.isMateDm()) {
99
106
  ctx.showMatePreThinking();
@@ -295,13 +302,37 @@ export function sendMessage() {
295
302
  // Scheduled message: queue message with timer delay
296
303
  if (scheduleDelayMs > 0) {
297
304
  var resetsAt = Date.now() + scheduleDelayMs;
298
- ctx.ws.send(JSON.stringify({ type: "schedule_message", text: text || "", resetsAt: resetsAt }));
299
- clearScheduleDelay();
300
- ctx.inputEl.value = "";
301
- sendInputSync();
302
- clearPendingImages();
303
- autoResize();
304
- ctx.inputEl.focus();
305
+ var scheduleContext = {
306
+ projectSlug: store.get("currentSlug"),
307
+ sessionId: store.get("activeSessionId"),
308
+ accountId: store.get("myUserId") || "default",
309
+ };
310
+ var scheduleDraft = { text: ctx.inputEl.value, images: images, pastes: pastes, files: files, delay: scheduleDelayMs };
311
+ var admissions = scheduleAdmissionMap();
312
+ var scheduleRequestId = null;
313
+ var admissionIds = Object.keys(admissions);
314
+ for (var admissionIndex = 0; admissionIndex < admissionIds.length; admissionIndex++) {
315
+ var candidate = admissions[admissionIds[admissionIndex]];
316
+ if (candidate.context.projectSlug === scheduleContext.projectSlug && String(candidate.context.sessionId) === String(scheduleContext.sessionId) &&
317
+ candidate.context.accountId === scheduleContext.accountId && scheduleDraftMatches(candidate.draft, scheduleDraft)) {
318
+ scheduleRequestId = candidate.requestId;
319
+ break;
320
+ }
321
+ }
322
+ if (!scheduleRequestId) {
323
+ scheduleRequestCounter++;
324
+ scheduleRequestId = "schedule-" + Date.now().toString(36) + "-" + scheduleRequestCounter;
325
+ admissions = Object.assign({}, admissions);
326
+ admissions[scheduleRequestId] = { requestId: scheduleRequestId, context: scheduleContext, draft: scheduleDraft };
327
+ var boundedIds = Object.keys(admissions);
328
+ while (boundedIds.length > 8) delete admissions[boundedIds.shift()];
329
+ }
330
+ store.set({ pendingScheduleAdmissions: admissions, pendingScheduleAdmission: admissions[scheduleRequestId] });
331
+ ctx.ws.send(JSON.stringify({
332
+ type: "schedule_message", requestId: scheduleRequestId,
333
+ projectSlug: scheduleContext.projectSlug, sessionId: scheduleContext.sessionId, accountId: scheduleContext.accountId,
334
+ text: text || "", images: images, pastes: pastes, resetsAt: resetsAt,
335
+ }));
305
336
  return;
306
337
  }
307
338
 
@@ -348,6 +379,36 @@ export function handleAcceptedOrdinaryMessage() {
348
379
  showPreThinkingForTurn();
349
380
  }
350
381
 
382
+ export function handleScheduleMessageResult(msg) {
383
+ var admissions = scheduleAdmissionMap();
384
+ var pending = admissions[msg && msg.requestId];
385
+ var currentContext = { projectSlug: store.get("currentSlug"), sessionId: store.get("activeSessionId"), accountId: store.get("myUserId") || "default" };
386
+ if (!pending || !msg || msg.requestId !== pending.requestId) return false;
387
+ if (!store.get("connected") || !scheduleContextMatches(pending.context, currentContext) ||
388
+ msg.projectSlug !== currentContext.projectSlug || String(msg.sessionId) !== String(currentContext.sessionId) || msg.accountId !== currentContext.accountId) {
389
+ admissions = Object.assign({}, admissions);
390
+ delete admissions[msg.requestId];
391
+ store.set({ pendingScheduleAdmissions: admissions, pendingScheduleAdmission: null });
392
+ return true;
393
+ }
394
+ if (!msg.ok) {
395
+ showToast(msg.error || "Could not schedule the message.", "error");
396
+ return true;
397
+ }
398
+ admissions = Object.assign({}, admissions);
399
+ delete admissions[msg.requestId];
400
+ store.set({ pendingScheduleAdmissions: admissions, pendingScheduleAdmission: null });
401
+ var currentDraft = { text: ctx.inputEl.value, images: pendingImages, pastes: pendingPastes.map(function (p) { return p.text; }), files: pendingFiles, delay: scheduleDelayMs };
402
+ if (JSON.stringify(currentDraft) !== JSON.stringify(pending.draft)) return true;
403
+ clearScheduleDelay();
404
+ ctx.inputEl.value = "";
405
+ sendInputSync();
406
+ clearPendingImages();
407
+ autoResize();
408
+ ctx.inputEl.focus();
409
+ return true;
410
+ }
411
+
351
412
  export function sendTextMessage(text) {
352
413
  if (!ctx || !ctx.inputEl || typeof text !== "string" || !text.trim()) return false;
353
414
  clearPendingImages();