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
|
@@ -4,6 +4,7 @@ import { escapeHtml } from './utils.js';
|
|
|
4
4
|
import { refreshIcons } from './icons.js';
|
|
5
5
|
import { activeQueueItems, moveQueueId, matchesQueueContext, canApplyQueueRevision } from './pending-message-queue-model.js';
|
|
6
6
|
import { imageSrc, imageDraft, openImagePreview, chooseImages } from './pending-message-image-editor.js';
|
|
7
|
+
import { setScheduleBtnDisabled } from './scheduled-message-state.js';
|
|
7
8
|
|
|
8
9
|
var queueEl = null;
|
|
9
10
|
var initialized = false;
|
|
@@ -34,6 +35,21 @@ function isOwn(item) {
|
|
|
34
35
|
return !!item && String(item.actorId) === String(store.get('myUserId'));
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
export function hasFutureScheduledPending(items) {
|
|
39
|
+
items = items || [];
|
|
40
|
+
for (var i = 0; i < items.length; i++) {
|
|
41
|
+
if (items[i].state === "pending" && items[i].schedule && items[i].schedule.ready !== true) return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function syncScheduleButton() {
|
|
47
|
+
var target = context();
|
|
48
|
+
var queue = currentQueue();
|
|
49
|
+
var current = target.enabled && queue.projectSlug === target.projectSlug && sameId(queue.sessionId, target.sessionId);
|
|
50
|
+
setScheduleBtnDisabled(current && hasFutureScheduledPending(queue.items));
|
|
51
|
+
}
|
|
52
|
+
|
|
37
53
|
function isEditableItem(id) {
|
|
38
54
|
var items = currentQueue().items || [];
|
|
39
55
|
for (var i = 0; i < items.length; i++) if (items[i].id === id) return items[i].state === "pending" && isOwn(items[i]);
|
|
@@ -116,6 +132,25 @@ function sendMutation(kind, fields) {
|
|
|
116
132
|
return true;
|
|
117
133
|
}
|
|
118
134
|
|
|
135
|
+
function sendScheduledNow(id) {
|
|
136
|
+
var target = context();
|
|
137
|
+
var queue = currentQueue();
|
|
138
|
+
var item = (queue.items || []).find(function (candidate) { return candidate.id === id; });
|
|
139
|
+
if (!target.enabled || queue.projectSlug !== target.projectSlug || !sameId(queue.sessionId, target.sessionId) || !item || item.state !== "pending" || !isOwn(item) || !item.schedule || item.schedule.ready === true) return false;
|
|
140
|
+
var requestId = rememberRequest("send-now", { itemId: id, jobId: item.schedule.jobId, jobRevision: item.schedule.revision, queueRevision: queue.revision });
|
|
141
|
+
if (!send({ type: "send_scheduled_now", projectSlug: target.projectSlug, sessionId: target.sessionId, jobId: item.schedule.jobId, revision: item.schedule.revision, queueRevision: queue.revision, requestId: requestId })) {
|
|
142
|
+
forgetRequest(requestId);
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function scheduledTimeLabel(notBefore) {
|
|
149
|
+
var date = new Date(Number(notBefore));
|
|
150
|
+
if (!isFinite(date.getTime())) return "Scheduled";
|
|
151
|
+
return "Scheduled for " + date.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" });
|
|
152
|
+
}
|
|
153
|
+
|
|
119
154
|
function applyCanonical(result) {
|
|
120
155
|
var target = context();
|
|
121
156
|
if (!result || result.projectSlug !== target.projectSlug || !sameId(result.sessionId, target.sessionId)) return false;
|
|
@@ -302,8 +337,10 @@ function renderItem(item, index) {
|
|
|
302
337
|
var isEditing = editing && editing.id === item.id && item.state === "pending" && own;
|
|
303
338
|
var attribution = own ? "You" : (item.actorName || "Collaborator");
|
|
304
339
|
var attachment = attachmentLabel(item);
|
|
340
|
+
var scheduled = item.schedule && item.schedule.jobId;
|
|
341
|
+
var futureScheduled = scheduled && item.schedule.ready !== true;
|
|
305
342
|
var text = (item.message && item.message.text || "").trim() || "Attachment-only message";
|
|
306
|
-
var html = '<div class="pending-message-chip' + (item.state === "claimed" ? ' is-claimed' : '') + '" data-pending-id="' + escapeHtml(item.id) + '">';
|
|
343
|
+
var html = '<div class="pending-message-chip' + (item.state === "claimed" ? ' is-claimed' : '') + (scheduled ? ' is-scheduled' : '') + (futureScheduled ? ' is-future' : '') + '" data-pending-id="' + escapeHtml(item.id) + '">';
|
|
307
344
|
html += '<span class="pending-message-number" aria-hidden="true">' + (index + 1) + '</span>';
|
|
308
345
|
if (own && item.state === "pending") html += '<button class="pending-message-drag" type="button" data-action="drag" aria-label="Reorder queued message ' + (index + 1) + '" title="Drag, or use arrow keys to reorder">' + '<i data-lucide="grip-vertical"></i></button>';
|
|
309
346
|
else html += '<span class="pending-message-drag is-static"><i data-lucide="' + (item.state === "claimed" ? "loader-circle" : "lock-keyhole") + '"></i></span>';
|
|
@@ -331,15 +368,18 @@ function renderItem(item, index) {
|
|
|
331
368
|
if (attachment) html += '<span>' + escapeHtml(attachment) + '</span>';
|
|
332
369
|
if (item.state === "claimed") html += '<span class="pending-message-sending">Sending</span>';
|
|
333
370
|
html += '</div>';
|
|
371
|
+
if (scheduled) html += '<div class="pending-message-schedule-meta"><i data-lucide="clock-3"></i><span>' + escapeHtml(futureScheduled ? scheduledTimeLabel(item.schedule.notBefore) : "Ready to send") + '</span></div>';
|
|
334
372
|
}
|
|
335
373
|
html += '</div><div class="pending-message-actions">';
|
|
336
374
|
if (!isEditing && own && item.state === "pending") html += '<button type="button" data-action="edit" aria-label="Edit queued message" title="Edit"><i data-lucide="pencil"></i></button>';
|
|
375
|
+
if (!isEditing && own && item.state === "pending" && futureScheduled) html += '<button type="button" data-action="send-now" aria-label="Send scheduled message now" title="Send now"><i data-lucide="send"></i></button>';
|
|
337
376
|
if (!isEditing && own) html += '<button type="button" data-action="cancel" aria-label="Cancel queued message" title="Cancel"><i data-lucide="x"></i></button>';
|
|
338
377
|
html += '</div></div>';
|
|
339
378
|
return html;
|
|
340
379
|
}
|
|
341
380
|
|
|
342
381
|
function render() {
|
|
382
|
+
syncScheduleButton();
|
|
343
383
|
if (!queueEl) queueEl = document.getElementById("pending-message-queue");
|
|
344
384
|
if (!queueEl) return;
|
|
345
385
|
var target = context();
|
|
@@ -382,6 +422,7 @@ function bindEvents() {
|
|
|
382
422
|
} else if (action === "edit") beginEdit(id);
|
|
383
423
|
else if (action === "edit-cancel") store.set({ pendingMessageQueueFocusReturn: { id: id, action: "edit" }, pendingMessageQueueEdit: null });
|
|
384
424
|
else if (action === "edit-save") saveEdit(id);
|
|
425
|
+
else if (action === "send-now") sendScheduledNow(id);
|
|
385
426
|
else if (action === "cancel") sendMutation("cancel", { id: id });
|
|
386
427
|
else if (action === "resume") sendMutation("resume", {});
|
|
387
428
|
});
|
|
@@ -437,6 +478,7 @@ export function initPendingMessageQueue() {
|
|
|
437
478
|
var contextChanged = state.connected !== previous.connected || state.currentSlug !== previous.currentSlug || state.activeSessionId !== previous.activeSessionId || state.activeSessionMode !== previous.activeSessionMode || state.dmMode !== previous.dmMode || state.mateProjectSlug !== previous.mateProjectSlug || state.myUserId !== previous.myUserId || state.isMultiUserMode !== previous.isMultiUserMode;
|
|
438
479
|
if (contextChanged) {
|
|
439
480
|
store.set({ pendingMessageQueue: emptyQueue(), pendingMessageQueueRequests: {}, pendingMessageQueueEdit: null, pendingMessageQueueDrag: null, pendingMessageQueueFocusReturn: null });
|
|
481
|
+
setScheduleBtnDisabled(false);
|
|
440
482
|
if (context().enabled) requestPendingMessages();
|
|
441
483
|
return;
|
|
442
484
|
}
|
|
@@ -1,39 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export function applyScheduledMessageQueued(message, actions) {
|
|
13
|
-
store.set({ scheduledMessageCorrelation: correlation(message) });
|
|
14
|
-
actions.add(message.text, message.resetsAt, message);
|
|
15
|
-
actions.setDisabled(true);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function applyScheduledMessageTerminal(message, actions) {
|
|
19
|
-
var current = store.get('scheduledMessageCorrelation');
|
|
20
|
-
if (current && !sameCorrelation(current, correlation(message))) return false;
|
|
21
|
-
store.set({ scheduledMessageCorrelation: null });
|
|
22
|
-
actions.remove();
|
|
23
|
-
actions.setDisabled(false);
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function applyScheduledMessageState(message, actions) {
|
|
28
|
-
actions.remove();
|
|
29
|
-
actions.setDisabled(false);
|
|
30
|
-
var pending = message && message.scheduledMessage;
|
|
31
|
-
store.set({ scheduledMessageCorrelation: correlation(pending) });
|
|
32
|
-
if (!pending && message && message.interrupted && actions.interrupted) actions.interrupted(message.interrupted);
|
|
33
|
-
if (!pending) return false;
|
|
34
|
-
actions.add(pending.text, pending.resetsAt, pending);
|
|
35
|
-
actions.setDisabled(true);
|
|
36
|
-
return true;
|
|
1
|
+
export function setScheduleBtnDisabled(disabled) {
|
|
2
|
+
var btn = document.getElementById("schedule-btn");
|
|
3
|
+
if (!btn) return;
|
|
4
|
+
btn.disabled = disabled;
|
|
5
|
+
if (disabled) {
|
|
6
|
+
btn.style.opacity = "0.3";
|
|
7
|
+
btn.style.pointerEvents = "none";
|
|
8
|
+
} else {
|
|
9
|
+
btn.style.opacity = "";
|
|
10
|
+
btn.style.pointerEvents = "";
|
|
11
|
+
}
|
|
37
12
|
}
|
|
38
13
|
|
|
39
14
|
export function scheduledMessageControl(type, correlation) {
|
|
@@ -41,6 +16,7 @@ export function scheduledMessageControl(type, correlation) {
|
|
|
41
16
|
if (correlation && correlation.jobId && correlation.revision) {
|
|
42
17
|
message.jobId = correlation.jobId;
|
|
43
18
|
message.revision = correlation.revision;
|
|
19
|
+
if (correlation.queueRevision != null) message.queueRevision = correlation.queueRevision;
|
|
44
20
|
}
|
|
45
21
|
return message;
|
|
46
22
|
}
|
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
var buildShape = require("./session-spawn-mcp-server").buildShape;
|
|
4
4
|
var z;
|
|
5
5
|
try { z = require("zod"); } catch (e) { z = null; }
|
|
6
|
+
var EVALUATION_OUTCOMES = ["succeeded", "partial", "failed", "abandoned"];
|
|
6
7
|
var evaluationSchema = z ? z.union([
|
|
7
|
-
z.
|
|
8
|
-
z.object({ outcome: z.
|
|
8
|
+
z.enum(EVALUATION_OUTCOMES),
|
|
9
|
+
z.object({ outcome: z.enum(EVALUATION_OUTCOMES), note: z.string().optional() }),
|
|
9
10
|
]) : null;
|
|
10
11
|
|
|
11
12
|
// `options.lifecycle` adds the autonomous management tools. They are omitted
|
|
@@ -75,7 +76,7 @@ function getToolDefs(handlers, options) {
|
|
|
75
76
|
recommendationRationale: { type: "string", description: "Concise Driver-authored explanation of why the recommended replacement vendor, model, and effort fit the next task." },
|
|
76
77
|
evaluation: {
|
|
77
78
|
schema: evaluationSchema,
|
|
78
|
-
description: "Optional
|
|
79
|
+
description: "Optional assessment of the Worker being replaced. Use succeeded, partial, failed, or abandoned, or an object with outcome set to one of those values and an optional note. Omit when no assessment is needed.",
|
|
79
80
|
},
|
|
80
81
|
operationId: { type: "string", description: "Optional stable id for this replacement. Reusing it within the same human turn returns the original operation instead of replacing twice." },
|
|
81
82
|
supersedeProposalId: { type: "string", description: "Exact pending replacement proposal id to supersede with this new configuration." },
|
|
@@ -86,7 +87,7 @@ function getToolDefs(handlers, options) {
|
|
|
86
87
|
name: "record_partner_evaluation",
|
|
87
88
|
description: "Record a bounded assessment of one Split Worker generation so your later model choices can use observed outcomes instead of re-reading transcripts. Records your judgement alongside the objective signals Clay already measured (turns, errors, interruption, context used). This is scoped to your own pair and forms no global model ranking.",
|
|
88
89
|
inputSchema: buildShape({
|
|
89
|
-
outcome: {
|
|
90
|
+
outcome: { enum: EVALUATION_OUTCOMES, description: 'One of "succeeded", "partial", "failed", "abandoned".' },
|
|
90
91
|
note: { type: "string", description: "Optional short reason, up to 400 characters." },
|
|
91
92
|
generation: { type: "number", description: "Optional generation number. Defaults to the current Worker." },
|
|
92
93
|
}, ["outcome"]),
|
package/package.json
CHANGED