clay-server 2.26.0-beta.1 → 2.26.0-beta.11
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 +5 -9
- package/lib/browser-mcp-server.js +496 -0
- package/lib/daemon.js +1 -1
- package/lib/os-users.js +23 -0
- package/lib/project-debate.js +243 -95
- package/lib/project-mate-interaction.js +766 -0
- package/lib/project-memory.js +677 -0
- package/lib/project.js +546 -1361
- package/lib/public/app.js +817 -175
- package/lib/public/css/debate.css +224 -2
- package/lib/public/css/icon-strip.css +10 -10
- package/lib/public/css/input.css +296 -83
- package/lib/public/css/mates.css +56 -57
- package/lib/public/css/mention.css +7 -4
- package/lib/public/css/menus.css +7 -0
- package/lib/public/css/messages.css +17 -0
- package/lib/public/css/mobile-nav.css +3 -1
- package/lib/public/css/overlays.css +181 -0
- package/lib/public/css/rewind.css +79 -0
- package/lib/public/css/server-settings.css +1 -0
- package/lib/public/css/sidebar.css +10 -0
- package/lib/public/css/title-bar.css +189 -3
- package/lib/public/index.html +53 -16
- package/lib/public/modules/context-sources.js +328 -0
- package/lib/public/modules/debate.js +184 -97
- package/lib/public/modules/input.js +18 -1
- package/lib/public/modules/mate-knowledge.js +11 -11
- package/lib/public/modules/mate-memory.js +5 -5
- package/lib/public/modules/mate-sidebar.js +13 -9
- package/lib/public/modules/mention.js +40 -2
- package/lib/public/modules/notifications.js +109 -1
- package/lib/public/modules/rewind.js +36 -0
- package/lib/public/modules/sidebar.js +107 -28
- package/lib/public/modules/terminal.js +8 -0
- package/lib/public/modules/theme.js +2 -1
- package/lib/public/modules/tools.js +69 -24
- package/lib/sdk-bridge.js +81 -7
- package/lib/sdk-worker.js +13 -1
- package/lib/server.js +42 -0
- package/lib/sessions.js +39 -7
- package/lib/terminal-manager.js +36 -6
- package/package.json +2 -2
package/lib/project-debate.js
CHANGED
|
@@ -156,14 +156,16 @@ function attachDebate(ctx) {
|
|
|
156
156
|
setupStartedAt: d.setupStartedAt || null,
|
|
157
157
|
round: d.round || 1,
|
|
158
158
|
awaitingConcludeConfirm: !!d.awaitingConcludeConfirm,
|
|
159
|
+
awaitingUserFloor: !!d.awaitingUserFloor,
|
|
160
|
+
ownerId: d.ownerId || null,
|
|
159
161
|
};
|
|
160
162
|
ctx.sm.saveSessionFile(session);
|
|
161
163
|
}
|
|
162
164
|
|
|
163
|
-
function restoreDebateFromState(session) {
|
|
165
|
+
function restoreDebateFromState(session, restoreUserId) {
|
|
164
166
|
var ds = session.debateState;
|
|
165
167
|
if (!ds) return null;
|
|
166
|
-
var userId =
|
|
168
|
+
var userId = restoreUserId || ds.ownerId || null;
|
|
167
169
|
var mateCtx = matesModule.buildMateCtx(userId);
|
|
168
170
|
var debate = {
|
|
169
171
|
phase: ds.phase,
|
|
@@ -186,6 +188,8 @@ function attachDebate(ctx) {
|
|
|
186
188
|
setupStartedAt: ds.setupStartedAt || null,
|
|
187
189
|
briefPath: ds.briefPath || null,
|
|
188
190
|
awaitingConcludeConfirm: !!ds.awaitingConcludeConfirm,
|
|
191
|
+
awaitingUserFloor: !!ds.awaitingUserFloor,
|
|
192
|
+
ownerId: ds.ownerId || userId,
|
|
189
193
|
};
|
|
190
194
|
|
|
191
195
|
// Fallback: if awaitingConcludeConfirm was not persisted, detect from history
|
|
@@ -255,17 +259,11 @@ function attachDebate(ctx) {
|
|
|
255
259
|
debate.context = brief.context || "";
|
|
256
260
|
debate.specialRequests = brief.specialRequests || null;
|
|
257
261
|
|
|
258
|
-
//
|
|
262
|
+
// Replace panelists with those selected in the brief
|
|
259
263
|
if (brief.panelists && brief.panelists.length) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
if (debate.panelists[j].mateId === bp.mateId) {
|
|
264
|
-
debate.panelists[j].role = bp.role || "";
|
|
265
|
-
debate.panelists[j].brief = bp.brief || "";
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
264
|
+
debate.panelists = brief.panelists.map(function (bp) {
|
|
265
|
+
return { mateId: bp.mateId, role: bp.role || "", brief: bp.brief || "" };
|
|
266
|
+
});
|
|
269
267
|
}
|
|
270
268
|
|
|
271
269
|
// Rebuild name map with updated roles
|
|
@@ -338,8 +336,8 @@ function attachDebate(ctx) {
|
|
|
338
336
|
var phase = session.debateState.phase;
|
|
339
337
|
if (phase !== "preparing" && phase !== "reviewing" && phase !== "live") return;
|
|
340
338
|
|
|
341
|
-
// Restore _debate from persisted state
|
|
342
|
-
var debate = restoreDebateFromState(session);
|
|
339
|
+
// Restore _debate from persisted state (pass userId for correct mateCtx)
|
|
340
|
+
var debate = restoreDebateFromState(session, userId);
|
|
343
341
|
if (!debate) return;
|
|
344
342
|
|
|
345
343
|
// Update mateCtx with the connected user's context
|
|
@@ -358,20 +356,17 @@ function attachDebate(ctx) {
|
|
|
358
356
|
console.log("[debate] Restoring debate (preparing). topic:", debate.topic, "briefPath:", briefPath);
|
|
359
357
|
startDebateBriefWatcher(session, debate, briefPath);
|
|
360
358
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}),
|
|
373
|
-
});
|
|
374
|
-
}
|
|
359
|
+
ctx.sendTo(ws, {
|
|
360
|
+
type: "debate_preparing",
|
|
361
|
+
topic: debate.topic,
|
|
362
|
+
moderatorId: debate.moderatorId,
|
|
363
|
+
moderatorName: moderatorProfile.name,
|
|
364
|
+
setupSessionId: debate.setupSessionId,
|
|
365
|
+
panelists: debate.panelists.map(function (p) {
|
|
366
|
+
var prof = ctx.getMateProfile(mateCtx, p.mateId);
|
|
367
|
+
return { mateId: p.mateId, name: prof.name };
|
|
368
|
+
}),
|
|
369
|
+
});
|
|
375
370
|
} else if (phase === "reviewing") {
|
|
376
371
|
console.log("[debate] Restoring debate (reviewing). topic:", debate.topic);
|
|
377
372
|
ctx.sendTo(ws, {
|
|
@@ -470,6 +465,7 @@ function attachDebate(ctx) {
|
|
|
470
465
|
setupSessionId: null,
|
|
471
466
|
debateId: debateId,
|
|
472
467
|
briefPath: briefPath,
|
|
468
|
+
ownerId: mateCtx.userId || null,
|
|
473
469
|
};
|
|
474
470
|
debate.nameMap = buildDebateNameMap(debate.panelists, mateCtx);
|
|
475
471
|
session._debate = debate;
|
|
@@ -500,8 +496,27 @@ function attachDebate(ctx) {
|
|
|
500
496
|
var session = ctx.getSessionForWs(ws);
|
|
501
497
|
if (!session) return;
|
|
502
498
|
|
|
503
|
-
if (!msg.moderatorId || !msg.topic
|
|
504
|
-
ctx.sendTo(ws, { type: "debate_error", error: "Missing required fields: moderatorId, topic
|
|
499
|
+
if (!msg.moderatorId || !msg.topic) {
|
|
500
|
+
ctx.sendTo(ws, { type: "debate_error", error: "Missing required fields: moderatorId, topic." });
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// delegatePanelists: moderator picks panelists, populate all available mates
|
|
505
|
+
if (msg.delegatePanelists) {
|
|
506
|
+
var userId = ws._clayUser ? ws._clayUser.id : null;
|
|
507
|
+
var tmpCtx = matesModule.buildMateCtx(userId);
|
|
508
|
+
var matesData = matesModule.loadMates(tmpCtx);
|
|
509
|
+
var allMates = matesData.mates || [];
|
|
510
|
+
msg.panelists = [];
|
|
511
|
+
for (var mi = 0; mi < allMates.length; mi++) {
|
|
512
|
+
if (allMates[mi].id !== msg.moderatorId && allMates[mi].status !== "interviewing") {
|
|
513
|
+
msg.panelists.push({ mateId: allMates[mi].id, role: "", brief: "" });
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (!msg.panelists || !msg.panelists.length) {
|
|
519
|
+
ctx.sendTo(ws, { type: "debate_error", error: "No panelists available." });
|
|
505
520
|
return;
|
|
506
521
|
}
|
|
507
522
|
|
|
@@ -538,6 +553,7 @@ function attachDebate(ctx) {
|
|
|
538
553
|
round: 1,
|
|
539
554
|
history: [],
|
|
540
555
|
setupSessionId: null,
|
|
556
|
+
ownerId: userId,
|
|
541
557
|
};
|
|
542
558
|
session._debate = debate;
|
|
543
559
|
|
|
@@ -565,7 +581,8 @@ function attachDebate(ctx) {
|
|
|
565
581
|
var debateId = debate.debateId;
|
|
566
582
|
|
|
567
583
|
// Create setup session (still needed for session grouping)
|
|
568
|
-
var
|
|
584
|
+
var setupOpts = debate.ownerId ? { ownerId: debate.ownerId } : null;
|
|
585
|
+
var setupSession = ctx.sm.createSession(setupOpts);
|
|
569
586
|
setupSession.title = "Debate Setup: " + (msg.topic || "Quick").slice(0, 40);
|
|
570
587
|
setupSession.debateSetupMode = true;
|
|
571
588
|
setupSession.loop = { active: true, iteration: 0, role: "crafting", loopId: debateId, name: (msg.topic || "Quick").slice(0, 40), source: "debate", startedAt: Date.now() };
|
|
@@ -597,7 +614,9 @@ function attachDebate(ctx) {
|
|
|
597
614
|
"",
|
|
598
615
|
"## Your Task",
|
|
599
616
|
"Based on the conversation context, create a debate brief. You know the topic well because you were just discussing it.",
|
|
600
|
-
|
|
617
|
+
msg.delegatePanelists
|
|
618
|
+
? "IMPORTANT: Select only 2-4 panelists who are most relevant to this specific topic. Do NOT include all of them. Be selective. Only pick mates whose expertise or personality directly contributes to this debate."
|
|
619
|
+
: "The user already selected these panelists. Assign each one a role and perspective that will create the most productive debate.",
|
|
601
620
|
"",
|
|
602
621
|
"Output ONLY a valid JSON object (no markdown fences, no extra text):",
|
|
603
622
|
"{",
|
|
@@ -688,7 +707,8 @@ function attachDebate(ctx) {
|
|
|
688
707
|
var debateId = debate.debateId;
|
|
689
708
|
|
|
690
709
|
// Create a new session for the setup skill (like Ralph crafting)
|
|
691
|
-
var
|
|
710
|
+
var skillSetupOpts = debate.ownerId ? { ownerId: debate.ownerId } : null;
|
|
711
|
+
var setupSession = ctx.sm.createSession(skillSetupOpts);
|
|
692
712
|
setupSession.title = "Debate Setup: " + msg.topic.slice(0, 40);
|
|
693
713
|
setupSession.debateSetupMode = true;
|
|
694
714
|
setupSession.loop = { active: true, iteration: 0, role: "crafting", loopId: debateId, name: msg.topic.slice(0, 40), source: "debate", startedAt: Date.now() };
|
|
@@ -723,13 +743,24 @@ function attachDebate(ctx) {
|
|
|
723
743
|
// Watch for brief.json in the debate-specific directory
|
|
724
744
|
startDebateBriefWatcher(session, debate, briefPath);
|
|
725
745
|
|
|
726
|
-
//
|
|
727
|
-
|
|
746
|
+
// Notify clients that setup is in progress
|
|
747
|
+
var preparingMsg = {
|
|
748
|
+
type: "debate_preparing",
|
|
749
|
+
topic: debate.topic || "(Setting up...)",
|
|
750
|
+
moderatorId: debate.moderatorId,
|
|
751
|
+
moderatorName: moderatorProfile.name,
|
|
752
|
+
setupSessionId: setupSession.localId,
|
|
753
|
+
panelists: debate.panelists.map(function (p) {
|
|
754
|
+
var prof = ctx.getMateProfile(mateCtx, p.mateId);
|
|
755
|
+
return { mateId: p.mateId, name: prof.name };
|
|
756
|
+
}),
|
|
757
|
+
};
|
|
758
|
+
ctx.sendTo(ws, preparingMsg);
|
|
759
|
+
ctx.sendToSession(session.localId, preparingMsg);
|
|
728
760
|
|
|
729
|
-
// Start the setup skill session
|
|
730
|
-
setupSession.history.push({ type: "user_message", text: craftingPrompt });
|
|
731
|
-
ctx.sm.appendToSessionFile(setupSession, { type: "user_message", text: craftingPrompt });
|
|
732
|
-
ctx.sendToSession(setupSession.localId, { type: "user_message", text: craftingPrompt });
|
|
761
|
+
// Start the setup skill session (don't send user_message to client — it's an internal prompt)
|
|
762
|
+
setupSession.history.push({ type: "user_message", text: craftingPrompt, _internal: true });
|
|
763
|
+
ctx.sm.appendToSessionFile(setupSession, { type: "user_message", text: craftingPrompt, _internal: true });
|
|
733
764
|
setupSession.isProcessing = true;
|
|
734
765
|
ctx.onProcessingChanged();
|
|
735
766
|
setupSession.sentToolResults = {};
|
|
@@ -737,6 +768,13 @@ function attachDebate(ctx) {
|
|
|
737
768
|
ctx.sdk.startQuery(setupSession, craftingPrompt, undefined, ctx.getLinuxUserForSession(setupSession));
|
|
738
769
|
}
|
|
739
770
|
|
|
771
|
+
// --- Mate strip processing indicator ---
|
|
772
|
+
// Broadcast mention_processing so the correct mate's active dot lights up
|
|
773
|
+
// on the mate strip during debate turns (instead of always the moderator's).
|
|
774
|
+
function debateMateProcessing(mateId, active) {
|
|
775
|
+
ctx.send({ type: "mention_processing", mateId: mateId, active: active });
|
|
776
|
+
}
|
|
777
|
+
|
|
740
778
|
// --- Live debate ---
|
|
741
779
|
|
|
742
780
|
function startDebateLive(session) {
|
|
@@ -751,13 +789,10 @@ function attachDebate(ctx) {
|
|
|
751
789
|
var moderatorProfile = ctx.getMateProfile(mateCtx, debate.moderatorId);
|
|
752
790
|
|
|
753
791
|
// Create a dedicated debate session, grouped with the setup session
|
|
754
|
-
var
|
|
792
|
+
var liveOpts = debate.ownerId ? { ownerId: debate.ownerId } : null;
|
|
793
|
+
var debateSession = ctx.sm.createSession(liveOpts);
|
|
755
794
|
debateSession.title = debate.topic.slice(0, 50);
|
|
756
795
|
debateSession.loop = { active: true, iteration: 1, role: "debate", loopId: debate.debateId, name: debate.topic.slice(0, 40), source: "debate", startedAt: debate.setupStartedAt || Date.now() };
|
|
757
|
-
// Assign cliSessionId manually so saveSessionFile works (no SDK query for debate sessions)
|
|
758
|
-
if (!debateSession.cliSessionId) {
|
|
759
|
-
debateSession.cliSessionId = crypto.randomUUID();
|
|
760
|
-
}
|
|
761
796
|
ctx.sm.saveSessionFile(debateSession);
|
|
762
797
|
ctx.sm.switchSession(debateSession.localId, null, ctx.hydrateImageRefs);
|
|
763
798
|
debate.liveSessionId = debateSession.localId;
|
|
@@ -789,6 +824,7 @@ function attachDebate(ctx) {
|
|
|
789
824
|
ctx.sendToSession(debateSession.localId, debateStartEntry);
|
|
790
825
|
|
|
791
826
|
// Signal moderator's first turn
|
|
827
|
+
debateMateProcessing(debate.moderatorId, true);
|
|
792
828
|
ctx.sendToSession(debateSession.localId, {
|
|
793
829
|
type: "debate_turn",
|
|
794
830
|
mateId: debate.moderatorId,
|
|
@@ -843,6 +879,7 @@ function attachDebate(ctx) {
|
|
|
843
879
|
var debate = session._debate;
|
|
844
880
|
if (!debate || debate.phase === "ended") return;
|
|
845
881
|
|
|
882
|
+
debateMateProcessing(debate.moderatorId, false);
|
|
846
883
|
debate.turnInProgress = false;
|
|
847
884
|
|
|
848
885
|
// Record in debate history
|
|
@@ -886,6 +923,12 @@ function attachDebate(ctx) {
|
|
|
886
923
|
return;
|
|
887
924
|
}
|
|
888
925
|
|
|
926
|
+
// Check if user raised hand
|
|
927
|
+
if (debate.handRaised) {
|
|
928
|
+
yieldFloorToUser(session);
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
|
|
889
932
|
// Trigger the first mentioned panelist
|
|
890
933
|
triggerPanelist(session, mentionedIds[0], fullText);
|
|
891
934
|
}
|
|
@@ -908,6 +951,7 @@ function attachDebate(ctx) {
|
|
|
908
951
|
}
|
|
909
952
|
if (!panelistInfo) {
|
|
910
953
|
console.error("[debate] Panelist not found:", mateId);
|
|
954
|
+
debateMateProcessing(mateId, false);
|
|
911
955
|
debate._currentTurnMateId = null;
|
|
912
956
|
// Feed error back to moderator
|
|
913
957
|
feedBackToModerator(session, mateId, "[This panelist is not part of the debate panel.]");
|
|
@@ -915,6 +959,7 @@ function attachDebate(ctx) {
|
|
|
915
959
|
}
|
|
916
960
|
|
|
917
961
|
// Notify clients of new turn
|
|
962
|
+
debateMateProcessing(mateId, true);
|
|
918
963
|
ctx.sendToSession(session.localId, {
|
|
919
964
|
type: "debate_turn",
|
|
920
965
|
mateId: mateId,
|
|
@@ -943,6 +988,7 @@ function attachDebate(ctx) {
|
|
|
943
988
|
},
|
|
944
989
|
onError: function (errMsg) {
|
|
945
990
|
console.error("[debate] Panelist error for " + mateId + ":", errMsg);
|
|
991
|
+
debateMateProcessing(mateId, false);
|
|
946
992
|
debate.turnInProgress = false;
|
|
947
993
|
// Feed error back to moderator so the debate can continue
|
|
948
994
|
feedBackToModerator(session, mateId, "[" + profile.name + " encountered an error and could not respond. Please continue with other panelists or wrap up.]");
|
|
@@ -1003,6 +1049,7 @@ function attachDebate(ctx) {
|
|
|
1003
1049
|
}
|
|
1004
1050
|
}).catch(function (err) {
|
|
1005
1051
|
console.error("[debate] Failed to create panelist session for " + mateId + ":", err.message || err);
|
|
1052
|
+
debateMateProcessing(mateId, false);
|
|
1006
1053
|
debate.turnInProgress = false;
|
|
1007
1054
|
feedBackToModerator(session, mateId, "[" + profile.name + " is unavailable. Please continue with other panelists or wrap up.]");
|
|
1008
1055
|
});
|
|
@@ -1013,6 +1060,7 @@ function attachDebate(ctx) {
|
|
|
1013
1060
|
var debate = session._debate;
|
|
1014
1061
|
if (!debate || debate.phase === "ended") return;
|
|
1015
1062
|
|
|
1063
|
+
debateMateProcessing(mateId, false);
|
|
1016
1064
|
debate.turnInProgress = false;
|
|
1017
1065
|
debate._currentTurnMateId = null;
|
|
1018
1066
|
debate._currentTurnText = "";
|
|
@@ -1041,12 +1089,18 @@ function attachDebate(ctx) {
|
|
|
1041
1089
|
return;
|
|
1042
1090
|
}
|
|
1043
1091
|
|
|
1044
|
-
// Check for pending user comment
|
|
1092
|
+
// Check for pending user comment (legacy)
|
|
1045
1093
|
if (debate.pendingComment) {
|
|
1046
1094
|
injectUserComment(session);
|
|
1047
1095
|
return;
|
|
1048
1096
|
}
|
|
1049
1097
|
|
|
1098
|
+
// Check if user raised hand (no comment, just wants the floor)
|
|
1099
|
+
if (debate.handRaised) {
|
|
1100
|
+
yieldFloorToUser(session);
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1050
1104
|
// Feed panelist response back to moderator
|
|
1051
1105
|
feedBackToModerator(session, mateId, fullText);
|
|
1052
1106
|
}
|
|
@@ -1070,6 +1124,7 @@ function attachDebate(ctx) {
|
|
|
1070
1124
|
var moderatorProfile = ctx.getMateProfile(debate.mateCtx, debate.moderatorId);
|
|
1071
1125
|
|
|
1072
1126
|
// Notify clients of moderator turn
|
|
1127
|
+
debateMateProcessing(debate.moderatorId, true);
|
|
1073
1128
|
ctx.sendToSession(session.localId, {
|
|
1074
1129
|
type: "debate_turn",
|
|
1075
1130
|
mateId: debate.moderatorId,
|
|
@@ -1115,73 +1170,75 @@ function attachDebate(ctx) {
|
|
|
1115
1170
|
|
|
1116
1171
|
// --- User interaction during debate ---
|
|
1117
1172
|
|
|
1118
|
-
function
|
|
1173
|
+
function handleDebateHandRaise(ws) {
|
|
1119
1174
|
var session = ctx.getSessionForWs(ws);
|
|
1120
1175
|
if (!session) return;
|
|
1121
1176
|
|
|
1122
1177
|
var debate = session._debate;
|
|
1123
|
-
if (!debate || debate.phase !== "live")
|
|
1124
|
-
|
|
1125
|
-
return;
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
// If awaiting conclude confirmation, re-send the confirm prompt instead
|
|
1178
|
+
if (!debate || debate.phase !== "live") return;
|
|
1179
|
+
if (debate.awaitingUserFloor || debate.handRaised) return;
|
|
1129
1180
|
if (debate.awaitingConcludeConfirm) {
|
|
1130
1181
|
ctx.sendTo(ws, { type: "debate_conclude_confirm", topic: debate.topic, round: debate.round });
|
|
1131
1182
|
return;
|
|
1132
1183
|
}
|
|
1133
1184
|
|
|
1185
|
+
debate.handRaised = true;
|
|
1186
|
+
ctx.sendToSession(session.localId, { type: "debate_hand_raised" });
|
|
1187
|
+
|
|
1188
|
+
// If no one is speaking, yield floor immediately
|
|
1189
|
+
if (!debate.turnInProgress) {
|
|
1190
|
+
yieldFloorToUser(session);
|
|
1191
|
+
}
|
|
1192
|
+
// Otherwise: current speaker finishes -> handRaised detected -> yieldFloorToUser
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
function handleDebateComment(ws, msg) {
|
|
1196
|
+
// Legacy: kept for compatibility but now hand raise is separate
|
|
1197
|
+
var session = ctx.getSessionForWs(ws);
|
|
1198
|
+
if (!session) return;
|
|
1199
|
+
|
|
1200
|
+
var debate = session._debate;
|
|
1201
|
+
if (!debate || debate.phase !== "live") {
|
|
1202
|
+
ctx.sendTo(ws, { type: "debate_error", error: "No active debate." });
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
if (debate.awaitingUserFloor) return;
|
|
1134
1206
|
if (!msg.text) return;
|
|
1135
1207
|
|
|
1136
1208
|
debate.pendingComment = { text: msg.text };
|
|
1209
|
+
debate.handRaised = true;
|
|
1137
1210
|
ctx.sendToSession(session.localId, { type: "debate_comment_queued", text: msg.text });
|
|
1138
1211
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
// Close the panelist's mention session to stop generation
|
|
1145
|
-
if (debate.panelistSessions[abortMateId]) {
|
|
1146
|
-
try { debate.panelistSessions[abortMateId].close(); } catch (e) {}
|
|
1147
|
-
delete debate.panelistSessions[abortMateId];
|
|
1148
|
-
}
|
|
1212
|
+
if (!debate.turnInProgress) {
|
|
1213
|
+
injectUserComment(session);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1149
1216
|
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
var panelistInfo = null;
|
|
1154
|
-
for (var pi = 0; pi < debate.panelists.length; pi++) {
|
|
1155
|
-
if (debate.panelists[pi].mateId === abortMateId) { panelistInfo = debate.panelists[pi]; break; }
|
|
1156
|
-
}
|
|
1217
|
+
function yieldFloorToUser(session) {
|
|
1218
|
+
var debate = session._debate;
|
|
1219
|
+
if (!debate || !debate.moderatorSession || debate.phase === "ended") return;
|
|
1157
1220
|
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
mateName: profile.name,
|
|
1162
|
-
role: panelistInfo ? panelistInfo.role : "",
|
|
1163
|
-
text: partialText,
|
|
1164
|
-
interrupted: true,
|
|
1165
|
-
avatarStyle: profile.avatarStyle,
|
|
1166
|
-
avatarSeed: profile.avatarSeed,
|
|
1167
|
-
avatarColor: profile.avatarColor,
|
|
1168
|
-
});
|
|
1221
|
+
debate.handRaised = false;
|
|
1222
|
+
debate.turnInProgress = true;
|
|
1223
|
+
var moderatorProfile = ctx.getMateProfile(debate.mateCtx, debate.moderatorId);
|
|
1169
1224
|
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1225
|
+
ctx.sendToSession(session.localId, {
|
|
1226
|
+
type: "debate_turn",
|
|
1227
|
+
mateId: debate.moderatorId,
|
|
1228
|
+
mateName: moderatorProfile.name,
|
|
1229
|
+
role: "moderator",
|
|
1230
|
+
round: debate.round,
|
|
1231
|
+
avatarColor: moderatorProfile.avatarColor,
|
|
1232
|
+
avatarStyle: moderatorProfile.avatarStyle,
|
|
1233
|
+
avatarSeed: moderatorProfile.avatarSeed,
|
|
1234
|
+
});
|
|
1174
1235
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1236
|
+
var feedText = "[The user raised their hand to speak.]\n" +
|
|
1237
|
+
"[Acknowledge this briefly and yield the floor to the user. Say something like " +
|
|
1238
|
+
"\"Go ahead\" or \"The floor is yours\". Do NOT call on any panelist (no @mentions). " +
|
|
1239
|
+
"The debate will pause for the user to speak.]";
|
|
1179
1240
|
|
|
1180
|
-
|
|
1181
|
-
if (!debate.turnInProgress) {
|
|
1182
|
-
injectUserComment(session);
|
|
1183
|
-
}
|
|
1184
|
-
// If moderator is currently speaking, pendingComment will be picked up after moderator's onDone
|
|
1241
|
+
debate.moderatorSession.pushMessage(feedText, buildModeratorYieldCallbacks(session));
|
|
1185
1242
|
}
|
|
1186
1243
|
|
|
1187
1244
|
function injectUserComment(session) {
|
|
@@ -1190,6 +1247,7 @@ function attachDebate(ctx) {
|
|
|
1190
1247
|
|
|
1191
1248
|
var comment = debate.pendingComment;
|
|
1192
1249
|
debate.pendingComment = null;
|
|
1250
|
+
debate.handRaised = false;
|
|
1193
1251
|
|
|
1194
1252
|
// Record in debate history
|
|
1195
1253
|
debate.history.push({ speaker: "user", mateId: null, mateName: "User", text: comment.text });
|
|
@@ -1199,7 +1257,7 @@ function attachDebate(ctx) {
|
|
|
1199
1257
|
ctx.sm.appendToSessionFile(session, commentEntry);
|
|
1200
1258
|
ctx.sendToSession(session.localId, commentEntry);
|
|
1201
1259
|
|
|
1202
|
-
// Feed to moderator
|
|
1260
|
+
// Feed to moderator: yield the floor to the user
|
|
1203
1261
|
debate.turnInProgress = true;
|
|
1204
1262
|
var moderatorProfile = ctx.getMateProfile(debate.mateCtx, debate.moderatorId);
|
|
1205
1263
|
|
|
@@ -1216,9 +1274,90 @@ function attachDebate(ctx) {
|
|
|
1216
1274
|
|
|
1217
1275
|
var feedText = "[The user raised their hand and said:]\n" +
|
|
1218
1276
|
comment.text + "\n" +
|
|
1219
|
-
"[
|
|
1277
|
+
"[Acknowledge the user's input. Briefly respond, then YIELD THE FLOOR to the user by saying something like " +
|
|
1278
|
+
"\"The floor is yours\" or \"Go ahead\". Do NOT call on any panelist (no @mentions). " +
|
|
1279
|
+
"The debate will pause for the user to speak.]";
|
|
1280
|
+
|
|
1281
|
+
debate.moderatorSession.pushMessage(feedText, buildModeratorYieldCallbacks(session));
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
function buildModeratorYieldCallbacks(session) {
|
|
1285
|
+
var debate = session._debate;
|
|
1286
|
+
var moderatorProfile = ctx.getMateProfile(debate.mateCtx, debate.moderatorId);
|
|
1287
|
+
return {
|
|
1288
|
+
onActivity: function (activity) {
|
|
1289
|
+
if (session._debate && session._debate.phase !== "ended") {
|
|
1290
|
+
ctx.sendToSession(session.localId, { type: "debate_activity", mateId: debate.moderatorId, activity: activity });
|
|
1291
|
+
}
|
|
1292
|
+
},
|
|
1293
|
+
onDelta: function (delta) {
|
|
1294
|
+
if (session._debate && session._debate.phase !== "ended") {
|
|
1295
|
+
ctx.sendToSession(session.localId, { type: "debate_stream", mateId: debate.moderatorId, mateName: moderatorProfile.name, delta: delta });
|
|
1296
|
+
}
|
|
1297
|
+
},
|
|
1298
|
+
onDone: function (fullText) {
|
|
1299
|
+
if (!debate || debate.phase === "ended") return;
|
|
1300
|
+
debate.turnInProgress = false;
|
|
1301
|
+
|
|
1302
|
+
// Record moderator yield turn
|
|
1303
|
+
debate.history.push({ speaker: "moderator", mateId: debate.moderatorId, mateName: moderatorProfile.name, text: fullText });
|
|
1304
|
+
var turnEntry = { type: "debate_turn_done", mateId: debate.moderatorId, mateName: moderatorProfile.name, role: "moderator", round: debate.round, text: fullText, avatarStyle: moderatorProfile.avatarStyle, avatarSeed: moderatorProfile.avatarSeed, avatarColor: moderatorProfile.avatarColor };
|
|
1305
|
+
session.history.push(turnEntry);
|
|
1306
|
+
ctx.sm.appendToSessionFile(session, turnEntry);
|
|
1307
|
+
ctx.sendToSession(session.localId, turnEntry);
|
|
1308
|
+
|
|
1309
|
+
// Enter user floor mode: pause debate and show input
|
|
1310
|
+
debate.awaitingUserFloor = true;
|
|
1311
|
+
persistDebateState(session);
|
|
1312
|
+
ctx.sendToSession(session.localId, { type: "debate_user_floor", topic: debate.topic, round: debate.round });
|
|
1313
|
+
},
|
|
1314
|
+
onError: function (errMsg) {
|
|
1315
|
+
console.error("[debate] Moderator yield error:", errMsg);
|
|
1316
|
+
endDebate(session, "error");
|
|
1317
|
+
},
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
function handleDebateUserFloorResponse(ws, msg) {
|
|
1322
|
+
var session = ctx.getSessionForWs(ws);
|
|
1323
|
+
if (!session) return;
|
|
1324
|
+
|
|
1325
|
+
var debate = session._debate;
|
|
1326
|
+
if (!debate || !debate.awaitingUserFloor || debate.phase !== "live") return;
|
|
1327
|
+
|
|
1328
|
+
debate.awaitingUserFloor = false;
|
|
1329
|
+
var userText = (msg && msg.text) ? msg.text.trim() : "";
|
|
1330
|
+
if (!userText) return;
|
|
1331
|
+
|
|
1332
|
+
// Record user's floor contribution
|
|
1333
|
+
debate.history.push({ speaker: "user", mateId: null, mateName: "User", text: userText });
|
|
1334
|
+
var floorEntry = { type: "debate_user_floor_done", text: userText };
|
|
1335
|
+
session.history.push(floorEntry);
|
|
1336
|
+
ctx.sm.appendToSessionFile(session, floorEntry);
|
|
1337
|
+
ctx.sendToSession(session.localId, floorEntry);
|
|
1338
|
+
|
|
1339
|
+
// Feed to moderator to resume debate
|
|
1340
|
+
debate.turnInProgress = true;
|
|
1341
|
+
var moderatorProfile = ctx.getMateProfile(debate.mateCtx, debate.moderatorId);
|
|
1342
|
+
|
|
1343
|
+
debateMateProcessing(debate.moderatorId, true);
|
|
1344
|
+
ctx.sendToSession(session.localId, {
|
|
1345
|
+
type: "debate_turn",
|
|
1346
|
+
mateId: debate.moderatorId,
|
|
1347
|
+
mateName: moderatorProfile.name,
|
|
1348
|
+
role: "moderator",
|
|
1349
|
+
round: debate.round,
|
|
1350
|
+
avatarColor: moderatorProfile.avatarColor,
|
|
1351
|
+
avatarStyle: moderatorProfile.avatarStyle,
|
|
1352
|
+
avatarSeed: moderatorProfile.avatarSeed,
|
|
1353
|
+
});
|
|
1354
|
+
|
|
1355
|
+
var feedText = "[The user took the floor and said:]\n" +
|
|
1356
|
+
userText + "\n" +
|
|
1357
|
+
"[Acknowledge the user's contribution and resume the debate. Call on the next panelist with @TheirName.]";
|
|
1220
1358
|
|
|
1221
1359
|
debate.moderatorSession.pushMessage(feedText, buildModeratorCallbacks(session));
|
|
1360
|
+
persistDebateState(session);
|
|
1222
1361
|
}
|
|
1223
1362
|
|
|
1224
1363
|
function handleDebateConfirmBrief(ws) {
|
|
@@ -1391,6 +1530,7 @@ function attachDebate(ctx) {
|
|
|
1391
1530
|
ctx.sendToSession(session.localId, resumedMsg);
|
|
1392
1531
|
|
|
1393
1532
|
debate.turnInProgress = true;
|
|
1533
|
+
debateMateProcessing(debate.moderatorId, true);
|
|
1394
1534
|
ctx.sendToSession(session.localId, {
|
|
1395
1535
|
type: "debate_turn",
|
|
1396
1536
|
mateId: debate.moderatorId,
|
|
@@ -1464,6 +1604,12 @@ function attachDebate(ctx) {
|
|
|
1464
1604
|
var debate = session._debate;
|
|
1465
1605
|
if (!debate || debate.phase === "ended") return;
|
|
1466
1606
|
|
|
1607
|
+
// Clear all mate strip processing dots
|
|
1608
|
+
debateMateProcessing(debate.moderatorId, false);
|
|
1609
|
+
for (var ei = 0; ei < debate.panelists.length; ei++) {
|
|
1610
|
+
debateMateProcessing(debate.panelists[ei].mateId, false);
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1467
1613
|
debate.phase = "ended";
|
|
1468
1614
|
debate.turnInProgress = false;
|
|
1469
1615
|
persistDebateState(session);
|
|
@@ -1592,10 +1738,12 @@ function attachDebate(ctx) {
|
|
|
1592
1738
|
|
|
1593
1739
|
return {
|
|
1594
1740
|
handleDebateStart: handleDebateStart,
|
|
1741
|
+
handleDebateHandRaise: handleDebateHandRaise,
|
|
1595
1742
|
handleDebateComment: handleDebateComment,
|
|
1596
1743
|
handleDebateStop: handleDebateStop,
|
|
1597
1744
|
handleDebateConcludeResponse: handleDebateConcludeResponse,
|
|
1598
1745
|
handleDebateConfirmBrief: handleDebateConfirmBrief,
|
|
1746
|
+
handleDebateUserFloorResponse: handleDebateUserFloorResponse,
|
|
1599
1747
|
restoreDebateState: restoreDebateState,
|
|
1600
1748
|
checkForDmDebateBrief: checkForDmDebateBrief,
|
|
1601
1749
|
};
|