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/bin/cli.js +1 -1
- package/lib/project-pending-message-queue.js +71 -7
- package/lib/project-scheduled-messages.js +235 -52
- package/lib/project-user-message.js +25 -5
- package/lib/project.js +7 -2
- package/lib/public/app.js +1 -3
- package/lib/public/css/input.css +0 -109
- package/lib/public/css/pending-message-queue.css +3 -0
- package/lib/public/modules/app-messages.js +6 -25
- package/lib/public/modules/app-projects.js +1 -0
- package/lib/public/modules/app-rate-limit.js +0 -182
- package/lib/public/modules/chat-render-runtime.js +0 -2
- package/lib/public/modules/input.js +81 -20
- package/lib/public/modules/pending-message-queue.js +43 -1
- package/lib/public/modules/scheduled-message-state.js +12 -36
- package/lib/session-pair-mcp-server.js +5 -4
- package/package.json +1 -1
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,
|
|
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) ---
|
package/lib/public/css/input.css
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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();
|