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.
@@ -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
- import { store } from './store.js';
2
-
3
- function correlation(message) {
4
- if (!message || !message.jobId || !message.revision) return null;
5
- return { jobId: message.jobId, revision: message.revision };
6
- }
7
-
8
- function sameCorrelation(left, right) {
9
- return !!left && !!right && left.jobId === right.jobId && left.revision === right.revision;
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.string(),
8
- z.object({ outcome: z.string(), note: z.string().optional() }),
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 bounded assessment of the Worker being replaced, recorded against that exact generation.",
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: { type: "string", description: 'One of "succeeded", "partial", "failed", "abandoned".' },
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "4.1.0-beta.7",
3
+ "version": "4.1.0-beta.9",
4
4
  "description": "Self-hosted team workspace for Claude Code and Codex. Multi-user, browser-based, with persistent AI mates.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js"