clay-server 2.23.0 → 2.23.1-beta.1
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-debate.js +1616 -0
- package/lib/project.js +38 -1567
- package/lib/public/app.js +2 -1
- package/lib/public/css/debate.css +48 -0
- package/lib/public/modules/debate.js +41 -0
- package/lib/sdk-bridge.js +43 -9
- package/lib/sdk-worker.js +16 -10
- package/package.json +1 -1
package/lib/public/app.js
CHANGED
|
@@ -31,7 +31,7 @@ import { initMateWizard, openMateWizard, closeMateWizard, handleMateCreated } fr
|
|
|
31
31
|
import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } from './modules/command-palette.js';
|
|
32
32
|
import { initLongPress } from './modules/longpress.js';
|
|
33
33
|
import { initMention, handleMentionStart, handleMentionStream, handleMentionDone, handleMentionError, handleMentionActivity, renderMentionUser, renderMentionResponse } from './modules/mention.js';
|
|
34
|
-
import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn, handleDebateActivity, handleDebateStream, handleDebateTurnDone, handleDebateCommentQueued, handleDebateCommentInjected, handleDebateEnded, handleDebateError, renderDebateStarted, renderDebateTurnDone, renderDebateEnded, renderDebateCommentInjected, renderDebateUserResume, openDebateModal, closeDebateModal, openQuickDebateModal, handleDebateBriefReady, renderDebateBriefReady, isDebateActive, resetDebateState } from './modules/debate.js';
|
|
34
|
+
import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateResumed, handleDebateTurn, handleDebateActivity, handleDebateStream, handleDebateTurnDone, handleDebateCommentQueued, handleDebateCommentInjected, handleDebateEnded, handleDebateError, renderDebateStarted, renderDebateTurnDone, renderDebateEnded, renderDebateCommentInjected, renderDebateUserResume, openDebateModal, closeDebateModal, openQuickDebateModal, handleDebateBriefReady, renderDebateBriefReady, isDebateActive, resetDebateState } from './modules/debate.js';
|
|
35
35
|
|
|
36
36
|
// --- Base path for multi-project routing ---
|
|
37
37
|
var slugMatch = location.pathname.match(/^\/p\/([a-z0-9_-]+)/);
|
|
@@ -4979,6 +4979,7 @@ import { initDebate, handleDebateStarted, handleDebateResumed, handleDebateTurn,
|
|
|
4979
4979
|
// --- Debate ---
|
|
4980
4980
|
case "debate_preparing":
|
|
4981
4981
|
showDebateSticky("preparing", msg);
|
|
4982
|
+
handleDebatePreparing(msg);
|
|
4982
4983
|
break;
|
|
4983
4984
|
|
|
4984
4985
|
case "debate_brief_ready":
|
|
@@ -331,6 +331,54 @@
|
|
|
331
331
|
color: var(--text);
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
/* --- Preparing indicator (shown while brief is being generated) --- */
|
|
335
|
+
.debate-preparing-indicator {
|
|
336
|
+
max-width: var(--content-width);
|
|
337
|
+
margin: 32px auto;
|
|
338
|
+
padding: 0 20px;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.debate-preparing-inner {
|
|
342
|
+
display: flex;
|
|
343
|
+
align-items: center;
|
|
344
|
+
gap: 12px;
|
|
345
|
+
padding: 16px 20px;
|
|
346
|
+
border-radius: 12px;
|
|
347
|
+
background: color-mix(in srgb, var(--accent2) 8%, transparent);
|
|
348
|
+
border: 1px solid color-mix(in srgb, var(--accent2) 15%, transparent);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.debate-preparing-spinner {
|
|
352
|
+
display: inline-flex;
|
|
353
|
+
flex-shrink: 0;
|
|
354
|
+
color: var(--accent2);
|
|
355
|
+
animation: spin 1.2s linear infinite;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.debate-preparing-spinner .lucide {
|
|
359
|
+
width: 20px;
|
|
360
|
+
height: 20px;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.debate-preparing-text {
|
|
364
|
+
font-size: 14px;
|
|
365
|
+
color: var(--text-secondary);
|
|
366
|
+
line-height: 1.5;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.debate-preparing-dots {
|
|
370
|
+
animation: blink-dots 1.4s steps(4, end) infinite;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
@keyframes spin {
|
|
374
|
+
to { transform: rotate(360deg); }
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
@keyframes blink-dots {
|
|
378
|
+
0%, 20% { opacity: 0; }
|
|
379
|
+
40% { opacity: 1; }
|
|
380
|
+
}
|
|
381
|
+
|
|
334
382
|
/* --- Activity bar inside turns --- */
|
|
335
383
|
.debate-activity-bar {
|
|
336
384
|
margin: 4px 0;
|
|
@@ -31,6 +31,11 @@ export function resetDebateState() {
|
|
|
31
31
|
flushTurnStream();
|
|
32
32
|
currentTurnEl = null;
|
|
33
33
|
currentTurnMateId = null;
|
|
34
|
+
// Remove preparing indicator if present
|
|
35
|
+
if (ctx && ctx.messagesEl) {
|
|
36
|
+
var prep = ctx.messagesEl.querySelector(".debate-preparing-indicator");
|
|
37
|
+
if (prep) prep.remove();
|
|
38
|
+
}
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
function buildAvatarUrl(meta) {
|
|
@@ -85,7 +90,43 @@ export function handleDebateResumed(msg) {
|
|
|
85
90
|
showDebateInfoFloat(msg);
|
|
86
91
|
}
|
|
87
92
|
|
|
93
|
+
export function handleDebatePreparing(msg) {
|
|
94
|
+
debatePhase = "preparing";
|
|
95
|
+
|
|
96
|
+
if (!ctx.messagesEl) return;
|
|
97
|
+
|
|
98
|
+
// Remove any existing preparing indicator
|
|
99
|
+
var existing = ctx.messagesEl.querySelector(".debate-preparing-indicator");
|
|
100
|
+
if (existing) existing.remove();
|
|
101
|
+
|
|
102
|
+
var el = document.createElement("div");
|
|
103
|
+
el.className = "debate-preparing-indicator";
|
|
104
|
+
|
|
105
|
+
var moderatorName = msg.moderatorName || "Moderator";
|
|
106
|
+
var panelistNames = (msg.panelists || []).map(function (p) { return p.name; }).filter(Boolean).join(", ");
|
|
107
|
+
|
|
108
|
+
el.innerHTML =
|
|
109
|
+
'<div class="debate-preparing-inner">' +
|
|
110
|
+
'<div class="debate-preparing-spinner">' + iconHtml("loader") + '</div>' +
|
|
111
|
+
'<div class="debate-preparing-text">' +
|
|
112
|
+
'<strong>' + escapeHtml(moderatorName) + '</strong> is setting up the debate' +
|
|
113
|
+
(panelistNames ? ' with <strong>' + escapeHtml(panelistNames) + '</strong>' : '') +
|
|
114
|
+
'<span class="debate-preparing-dots">...</span>' +
|
|
115
|
+
'</div>' +
|
|
116
|
+
'</div>';
|
|
117
|
+
|
|
118
|
+
ctx.messagesEl.appendChild(el);
|
|
119
|
+
refreshIcons();
|
|
120
|
+
if (ctx.scrollToBottom) ctx.scrollToBottom();
|
|
121
|
+
}
|
|
122
|
+
|
|
88
123
|
export function handleDebateStarted(msg) {
|
|
124
|
+
// Remove preparing indicator when debate goes live
|
|
125
|
+
if (ctx.messagesEl) {
|
|
126
|
+
var prep = ctx.messagesEl.querySelector(".debate-preparing-indicator");
|
|
127
|
+
if (prep) prep.remove();
|
|
128
|
+
}
|
|
129
|
+
|
|
89
130
|
debateActive = true;
|
|
90
131
|
debateTopic = msg.topic || "";
|
|
91
132
|
debateRound = 1;
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -821,9 +821,14 @@ function createSDKBridge(opts) {
|
|
|
821
821
|
worker._readyResolve = null;
|
|
822
822
|
// Let the readyPromise hang; the query_error handler will clean up
|
|
823
823
|
}
|
|
824
|
-
// Notify message handlers about unexpected exit so sessions don't hang
|
|
824
|
+
// Notify message handlers about unexpected exit so sessions don't hang.
|
|
825
|
+
// Always dispatch a fallback query_error. The handler is idempotent:
|
|
826
|
+
// it checks isProcessing before taking action, and cleanupSessionWorker
|
|
827
|
+
// guards against stale workers. This covers all exit cases including
|
|
828
|
+
// signal kills (code=null) and normal exits where the IPC query_error
|
|
829
|
+
// was lost due to connection timing.
|
|
825
830
|
if (code === 0 && !worker.ready) {
|
|
826
|
-
// Worker exited cleanly before sending "ready"
|
|
831
|
+
// Worker exited cleanly before sending "ready"
|
|
827
832
|
for (var h = 0; h < worker.messageHandlers.length; h++) {
|
|
828
833
|
worker.messageHandlers[h]({
|
|
829
834
|
type: "query_error",
|
|
@@ -832,17 +837,35 @@ function createSDKBridge(opts) {
|
|
|
832
837
|
stderr: worker._stderrBuf || null,
|
|
833
838
|
});
|
|
834
839
|
}
|
|
835
|
-
}
|
|
836
|
-
|
|
840
|
+
} else if (code !== 0 || code === null || signal) {
|
|
841
|
+
// Worker crashed, was killed by signal, or exited abnormally
|
|
837
842
|
var stderrText = worker._stderrBuf || "";
|
|
843
|
+
var exitReason = signal
|
|
844
|
+
? "Worker killed by " + signal
|
|
845
|
+
: (stderrText || "Worker exited with code " + code);
|
|
838
846
|
for (var h = 0; h < worker.messageHandlers.length; h++) {
|
|
839
847
|
worker.messageHandlers[h]({
|
|
840
848
|
type: "query_error",
|
|
841
|
-
error:
|
|
849
|
+
error: exitReason,
|
|
842
850
|
exitCode: code,
|
|
843
851
|
stderr: stderrText || null,
|
|
844
852
|
});
|
|
845
853
|
}
|
|
854
|
+
} else if (worker.messageHandlers.length > 0) {
|
|
855
|
+
// Normal exit (code=0, ready=true). Dispatch fallback in case the
|
|
856
|
+
// IPC query_done/query_error was lost (e.g. connection closed early).
|
|
857
|
+
var fallbackMsg = worker._abortSent
|
|
858
|
+
? "Worker aborted"
|
|
859
|
+
: "Worker exited before query completed";
|
|
860
|
+
for (var h = 0; h < worker.messageHandlers.length; h++) {
|
|
861
|
+
worker.messageHandlers[h]({
|
|
862
|
+
type: "query_error",
|
|
863
|
+
error: fallbackMsg,
|
|
864
|
+
exitCode: 0,
|
|
865
|
+
stderr: worker._stderrBuf || null,
|
|
866
|
+
_fallback: true,
|
|
867
|
+
});
|
|
868
|
+
}
|
|
846
869
|
}
|
|
847
870
|
cleanupWorker(worker);
|
|
848
871
|
});
|
|
@@ -863,13 +886,17 @@ function createSDKBridge(opts) {
|
|
|
863
886
|
|
|
864
887
|
worker.kill = function() {
|
|
865
888
|
worker.send({ type: "shutdown" });
|
|
866
|
-
// Force kill after
|
|
889
|
+
// Force kill after 5 seconds if still alive (gives SDK time to save session)
|
|
867
890
|
setTimeout(function() {
|
|
868
891
|
if (worker.process && !worker.process.killed) {
|
|
869
892
|
try { worker.process.kill("SIGKILL"); } catch (e) {}
|
|
870
893
|
}
|
|
871
|
-
},
|
|
872
|
-
cleanupWorker
|
|
894
|
+
}, 5000);
|
|
895
|
+
// Don't call cleanupWorker here. Let the exit handler do it after
|
|
896
|
+
// the worker has had time to save SDK session state to disk.
|
|
897
|
+
// Closing the connection prematurely causes the worker to exit
|
|
898
|
+
// before the SDK can flush its session file, leading to "no
|
|
899
|
+
// conversation found" errors on resume (OS multi-user mode).
|
|
873
900
|
};
|
|
874
901
|
|
|
875
902
|
return worker;
|
|
@@ -942,7 +969,7 @@ function createSDKBridge(opts) {
|
|
|
942
969
|
session.pendingElicitations = {};
|
|
943
970
|
session.streamedText = false;
|
|
944
971
|
session.responsePreview = "";
|
|
945
|
-
session.abortController = { abort: function() { worker.send({ type: "abort" }); } };
|
|
972
|
+
session.abortController = { abort: function() { worker._abortSent = true; worker.send({ type: "abort" }); } };
|
|
946
973
|
|
|
947
974
|
// Build initial user message content
|
|
948
975
|
var content = [];
|
|
@@ -1049,6 +1076,9 @@ function createSDKBridge(opts) {
|
|
|
1049
1076
|
break;
|
|
1050
1077
|
|
|
1051
1078
|
case "query_done":
|
|
1079
|
+
// Mark that we received a proper IPC completion, so the exit
|
|
1080
|
+
// handler fallback knows not to double-process.
|
|
1081
|
+
worker._queryEnded = true;
|
|
1052
1082
|
// Stream ended normally
|
|
1053
1083
|
if (session.isProcessing && session.taskStopRequested) {
|
|
1054
1084
|
session.isProcessing = false;
|
|
@@ -1066,6 +1096,10 @@ function createSDKBridge(opts) {
|
|
|
1066
1096
|
break;
|
|
1067
1097
|
|
|
1068
1098
|
case "query_error": {
|
|
1099
|
+
// Skip fallback errors from exit handler if we already handled the real one
|
|
1100
|
+
if (msg._fallback && worker._queryEnded) break;
|
|
1101
|
+
// Mark that we received a proper IPC completion
|
|
1102
|
+
worker._queryEnded = true;
|
|
1069
1103
|
// Check session-not-found before isProcessing gate (it can arrive after processing is cleared)
|
|
1070
1104
|
var qerrLower = (msg.error || "").toLowerCase();
|
|
1071
1105
|
// Only match the exact SDK error, not generic worker stderr
|
package/lib/sdk-worker.js
CHANGED
|
@@ -124,8 +124,7 @@ function handleMessage(msg) {
|
|
|
124
124
|
handleWarmup(msg);
|
|
125
125
|
break;
|
|
126
126
|
case "shutdown":
|
|
127
|
-
|
|
128
|
-
process.exit(0);
|
|
127
|
+
gracefulExit(0);
|
|
129
128
|
break;
|
|
130
129
|
default:
|
|
131
130
|
console.error("[sdk-worker] Unknown message type:", msg.type);
|
|
@@ -393,6 +392,7 @@ async function handleWarmup(msg) {
|
|
|
393
392
|
}
|
|
394
393
|
|
|
395
394
|
// --- Cleanup ---
|
|
395
|
+
var _exitScheduled = false;
|
|
396
396
|
function cleanup() {
|
|
397
397
|
if (_keepAlive) {
|
|
398
398
|
try { clearInterval(_keepAlive); } catch (e) {}
|
|
@@ -408,6 +408,16 @@ function cleanup() {
|
|
|
408
408
|
}
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
+
// Exit with a grace period so the SDK can flush session state to disk.
|
|
412
|
+
// Without this, process.exit(0) kills pending async writes and the
|
|
413
|
+
// session file may be incomplete, causing "no conversation found" on resume.
|
|
414
|
+
function gracefulExit(code) {
|
|
415
|
+
if (_exitScheduled) return;
|
|
416
|
+
_exitScheduled = true;
|
|
417
|
+
cleanup();
|
|
418
|
+
setTimeout(function() { process.exit(code); }, 800);
|
|
419
|
+
}
|
|
420
|
+
|
|
411
421
|
// Keep event loop alive — without this, Node may exit if the socket handle
|
|
412
422
|
// gets unreferenced (observed on Linux with uid/gid spawn)
|
|
413
423
|
var _keepAlive = setInterval(function() {}, 30000);
|
|
@@ -436,25 +446,21 @@ conn.on("data", function(chunk) {
|
|
|
436
446
|
|
|
437
447
|
conn.on("error", function(err) {
|
|
438
448
|
console.error("[sdk-worker] Socket error:", err.message);
|
|
439
|
-
|
|
440
|
-
process.exit(1);
|
|
449
|
+
gracefulExit(1);
|
|
441
450
|
});
|
|
442
451
|
|
|
443
452
|
conn.on("close", function() {
|
|
444
453
|
try { require("fs").writeSync(2, "[sdk-worker] EXIT REASON: socket closed\n"); } catch (e) {}
|
|
445
|
-
|
|
446
|
-
process.exit(0);
|
|
454
|
+
gracefulExit(0);
|
|
447
455
|
});
|
|
448
456
|
|
|
449
457
|
// Handle process signals
|
|
450
458
|
process.on("SIGTERM", function() {
|
|
451
459
|
try { require("fs").writeSync(2, "[sdk-worker] EXIT REASON: SIGTERM\n"); } catch (e) {}
|
|
452
|
-
|
|
453
|
-
process.exit(0);
|
|
460
|
+
gracefulExit(0);
|
|
454
461
|
});
|
|
455
462
|
|
|
456
463
|
process.on("SIGINT", function() {
|
|
457
464
|
try { require("fs").writeSync(2, "[sdk-worker] EXIT REASON: SIGINT\n"); } catch (e) {}
|
|
458
|
-
|
|
459
|
-
process.exit(0);
|
|
465
|
+
gracefulExit(0);
|
|
460
466
|
});
|