clay-server 2.26.0-beta.1 → 2.26.0-beta.10
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 +206 -88
- 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 +263 -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 +313 -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,6 +156,7 @@ function attachDebate(ctx) {
|
|
|
156
156
|
setupStartedAt: d.setupStartedAt || null,
|
|
157
157
|
round: d.round || 1,
|
|
158
158
|
awaitingConcludeConfirm: !!d.awaitingConcludeConfirm,
|
|
159
|
+
awaitingUserFloor: !!d.awaitingUserFloor,
|
|
159
160
|
};
|
|
160
161
|
ctx.sm.saveSessionFile(session);
|
|
161
162
|
}
|
|
@@ -186,6 +187,7 @@ function attachDebate(ctx) {
|
|
|
186
187
|
setupStartedAt: ds.setupStartedAt || null,
|
|
187
188
|
briefPath: ds.briefPath || null,
|
|
188
189
|
awaitingConcludeConfirm: !!ds.awaitingConcludeConfirm,
|
|
190
|
+
awaitingUserFloor: !!ds.awaitingUserFloor,
|
|
189
191
|
};
|
|
190
192
|
|
|
191
193
|
// Fallback: if awaitingConcludeConfirm was not persisted, detect from history
|
|
@@ -255,17 +257,11 @@ function attachDebate(ctx) {
|
|
|
255
257
|
debate.context = brief.context || "";
|
|
256
258
|
debate.specialRequests = brief.specialRequests || null;
|
|
257
259
|
|
|
258
|
-
//
|
|
260
|
+
// Replace panelists with those selected in the brief
|
|
259
261
|
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
|
-
}
|
|
262
|
+
debate.panelists = brief.panelists.map(function (bp) {
|
|
263
|
+
return { mateId: bp.mateId, role: bp.role || "", brief: bp.brief || "" };
|
|
264
|
+
});
|
|
269
265
|
}
|
|
270
266
|
|
|
271
267
|
// Rebuild name map with updated roles
|
|
@@ -358,20 +354,17 @@ function attachDebate(ctx) {
|
|
|
358
354
|
console.log("[debate] Restoring debate (preparing). topic:", debate.topic, "briefPath:", briefPath);
|
|
359
355
|
startDebateBriefWatcher(session, debate, briefPath);
|
|
360
356
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}),
|
|
373
|
-
});
|
|
374
|
-
}
|
|
357
|
+
ctx.sendTo(ws, {
|
|
358
|
+
type: "debate_preparing",
|
|
359
|
+
topic: debate.topic,
|
|
360
|
+
moderatorId: debate.moderatorId,
|
|
361
|
+
moderatorName: moderatorProfile.name,
|
|
362
|
+
setupSessionId: debate.setupSessionId,
|
|
363
|
+
panelists: debate.panelists.map(function (p) {
|
|
364
|
+
var prof = ctx.getMateProfile(mateCtx, p.mateId);
|
|
365
|
+
return { mateId: p.mateId, name: prof.name };
|
|
366
|
+
}),
|
|
367
|
+
});
|
|
375
368
|
} else if (phase === "reviewing") {
|
|
376
369
|
console.log("[debate] Restoring debate (reviewing). topic:", debate.topic);
|
|
377
370
|
ctx.sendTo(ws, {
|
|
@@ -500,8 +493,27 @@ function attachDebate(ctx) {
|
|
|
500
493
|
var session = ctx.getSessionForWs(ws);
|
|
501
494
|
if (!session) return;
|
|
502
495
|
|
|
503
|
-
if (!msg.moderatorId || !msg.topic
|
|
504
|
-
ctx.sendTo(ws, { type: "debate_error", error: "Missing required fields: moderatorId, topic
|
|
496
|
+
if (!msg.moderatorId || !msg.topic) {
|
|
497
|
+
ctx.sendTo(ws, { type: "debate_error", error: "Missing required fields: moderatorId, topic." });
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// delegatePanelists: moderator picks panelists, populate all available mates
|
|
502
|
+
if (msg.delegatePanelists) {
|
|
503
|
+
var userId = ws._clayUser ? ws._clayUser.id : null;
|
|
504
|
+
var tmpCtx = matesModule.buildMateCtx(userId);
|
|
505
|
+
var matesData = matesModule.loadMates(tmpCtx);
|
|
506
|
+
var allMates = matesData.mates || [];
|
|
507
|
+
msg.panelists = [];
|
|
508
|
+
for (var mi = 0; mi < allMates.length; mi++) {
|
|
509
|
+
if (allMates[mi].id !== msg.moderatorId && allMates[mi].status !== "interviewing") {
|
|
510
|
+
msg.panelists.push({ mateId: allMates[mi].id, role: "", brief: "" });
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
if (!msg.panelists || !msg.panelists.length) {
|
|
516
|
+
ctx.sendTo(ws, { type: "debate_error", error: "No panelists available." });
|
|
505
517
|
return;
|
|
506
518
|
}
|
|
507
519
|
|
|
@@ -597,7 +609,9 @@ function attachDebate(ctx) {
|
|
|
597
609
|
"",
|
|
598
610
|
"## Your Task",
|
|
599
611
|
"Based on the conversation context, create a debate brief. You know the topic well because you were just discussing it.",
|
|
600
|
-
|
|
612
|
+
msg.delegatePanelists
|
|
613
|
+
? "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."
|
|
614
|
+
: "The user already selected these panelists. Assign each one a role and perspective that will create the most productive debate.",
|
|
601
615
|
"",
|
|
602
616
|
"Output ONLY a valid JSON object (no markdown fences, no extra text):",
|
|
603
617
|
"{",
|
|
@@ -723,13 +737,24 @@ function attachDebate(ctx) {
|
|
|
723
737
|
// Watch for brief.json in the debate-specific directory
|
|
724
738
|
startDebateBriefWatcher(session, debate, briefPath);
|
|
725
739
|
|
|
726
|
-
//
|
|
727
|
-
|
|
740
|
+
// Notify clients that setup is in progress
|
|
741
|
+
var preparingMsg = {
|
|
742
|
+
type: "debate_preparing",
|
|
743
|
+
topic: debate.topic || "(Setting up...)",
|
|
744
|
+
moderatorId: debate.moderatorId,
|
|
745
|
+
moderatorName: moderatorProfile.name,
|
|
746
|
+
setupSessionId: setupSession.localId,
|
|
747
|
+
panelists: debate.panelists.map(function (p) {
|
|
748
|
+
var prof = ctx.getMateProfile(mateCtx, p.mateId);
|
|
749
|
+
return { mateId: p.mateId, name: prof.name };
|
|
750
|
+
}),
|
|
751
|
+
};
|
|
752
|
+
ctx.sendTo(ws, preparingMsg);
|
|
753
|
+
ctx.sendToSession(session.localId, preparingMsg);
|
|
728
754
|
|
|
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 });
|
|
755
|
+
// Start the setup skill session (don't send user_message to client — it's an internal prompt)
|
|
756
|
+
setupSession.history.push({ type: "user_message", text: craftingPrompt, _internal: true });
|
|
757
|
+
ctx.sm.appendToSessionFile(setupSession, { type: "user_message", text: craftingPrompt, _internal: true });
|
|
733
758
|
setupSession.isProcessing = true;
|
|
734
759
|
ctx.onProcessingChanged();
|
|
735
760
|
setupSession.sentToolResults = {};
|
|
@@ -754,10 +779,6 @@ function attachDebate(ctx) {
|
|
|
754
779
|
var debateSession = ctx.sm.createSession();
|
|
755
780
|
debateSession.title = debate.topic.slice(0, 50);
|
|
756
781
|
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
782
|
ctx.sm.saveSessionFile(debateSession);
|
|
762
783
|
ctx.sm.switchSession(debateSession.localId, null, ctx.hydrateImageRefs);
|
|
763
784
|
debate.liveSessionId = debateSession.localId;
|
|
@@ -886,6 +907,12 @@ function attachDebate(ctx) {
|
|
|
886
907
|
return;
|
|
887
908
|
}
|
|
888
909
|
|
|
910
|
+
// Check if user raised hand
|
|
911
|
+
if (debate.handRaised) {
|
|
912
|
+
yieldFloorToUser(session);
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
|
|
889
916
|
// Trigger the first mentioned panelist
|
|
890
917
|
triggerPanelist(session, mentionedIds[0], fullText);
|
|
891
918
|
}
|
|
@@ -1041,12 +1068,18 @@ function attachDebate(ctx) {
|
|
|
1041
1068
|
return;
|
|
1042
1069
|
}
|
|
1043
1070
|
|
|
1044
|
-
// Check for pending user comment
|
|
1071
|
+
// Check for pending user comment (legacy)
|
|
1045
1072
|
if (debate.pendingComment) {
|
|
1046
1073
|
injectUserComment(session);
|
|
1047
1074
|
return;
|
|
1048
1075
|
}
|
|
1049
1076
|
|
|
1077
|
+
// Check if user raised hand (no comment, just wants the floor)
|
|
1078
|
+
if (debate.handRaised) {
|
|
1079
|
+
yieldFloorToUser(session);
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1050
1083
|
// Feed panelist response back to moderator
|
|
1051
1084
|
feedBackToModerator(session, mateId, fullText);
|
|
1052
1085
|
}
|
|
@@ -1115,73 +1148,75 @@ function attachDebate(ctx) {
|
|
|
1115
1148
|
|
|
1116
1149
|
// --- User interaction during debate ---
|
|
1117
1150
|
|
|
1118
|
-
function
|
|
1151
|
+
function handleDebateHandRaise(ws) {
|
|
1119
1152
|
var session = ctx.getSessionForWs(ws);
|
|
1120
1153
|
if (!session) return;
|
|
1121
1154
|
|
|
1122
1155
|
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
|
|
1156
|
+
if (!debate || debate.phase !== "live") return;
|
|
1157
|
+
if (debate.awaitingUserFloor || debate.handRaised) return;
|
|
1129
1158
|
if (debate.awaitingConcludeConfirm) {
|
|
1130
1159
|
ctx.sendTo(ws, { type: "debate_conclude_confirm", topic: debate.topic, round: debate.round });
|
|
1131
1160
|
return;
|
|
1132
1161
|
}
|
|
1133
1162
|
|
|
1163
|
+
debate.handRaised = true;
|
|
1164
|
+
ctx.sendToSession(session.localId, { type: "debate_hand_raised" });
|
|
1165
|
+
|
|
1166
|
+
// If no one is speaking, yield floor immediately
|
|
1167
|
+
if (!debate.turnInProgress) {
|
|
1168
|
+
yieldFloorToUser(session);
|
|
1169
|
+
}
|
|
1170
|
+
// Otherwise: current speaker finishes -> handRaised detected -> yieldFloorToUser
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
function handleDebateComment(ws, msg) {
|
|
1174
|
+
// Legacy: kept for compatibility but now hand raise is separate
|
|
1175
|
+
var session = ctx.getSessionForWs(ws);
|
|
1176
|
+
if (!session) return;
|
|
1177
|
+
|
|
1178
|
+
var debate = session._debate;
|
|
1179
|
+
if (!debate || debate.phase !== "live") {
|
|
1180
|
+
ctx.sendTo(ws, { type: "debate_error", error: "No active debate." });
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
if (debate.awaitingUserFloor) return;
|
|
1134
1184
|
if (!msg.text) return;
|
|
1135
1185
|
|
|
1136
1186
|
debate.pendingComment = { text: msg.text };
|
|
1187
|
+
debate.handRaised = true;
|
|
1137
1188
|
ctx.sendToSession(session.localId, { type: "debate_comment_queued", text: msg.text });
|
|
1138
1189
|
|
|
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
|
-
}
|
|
1190
|
+
if (!debate.turnInProgress) {
|
|
1191
|
+
injectUserComment(session);
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1149
1194
|
|
|
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
|
-
}
|
|
1195
|
+
function yieldFloorToUser(session) {
|
|
1196
|
+
var debate = session._debate;
|
|
1197
|
+
if (!debate || !debate.moderatorSession || debate.phase === "ended") return;
|
|
1157
1198
|
|
|
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
|
-
});
|
|
1199
|
+
debate.handRaised = false;
|
|
1200
|
+
debate.turnInProgress = true;
|
|
1201
|
+
var moderatorProfile = ctx.getMateProfile(debate.mateCtx, debate.moderatorId);
|
|
1169
1202
|
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1203
|
+
ctx.sendToSession(session.localId, {
|
|
1204
|
+
type: "debate_turn",
|
|
1205
|
+
mateId: debate.moderatorId,
|
|
1206
|
+
mateName: moderatorProfile.name,
|
|
1207
|
+
role: "moderator",
|
|
1208
|
+
round: debate.round,
|
|
1209
|
+
avatarColor: moderatorProfile.avatarColor,
|
|
1210
|
+
avatarStyle: moderatorProfile.avatarStyle,
|
|
1211
|
+
avatarSeed: moderatorProfile.avatarSeed,
|
|
1212
|
+
});
|
|
1174
1213
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1214
|
+
var feedText = "[The user raised their hand to speak.]\n" +
|
|
1215
|
+
"[Acknowledge this briefly and yield the floor to the user. Say something like " +
|
|
1216
|
+
"\"Go ahead\" or \"The floor is yours\". Do NOT call on any panelist (no @mentions). " +
|
|
1217
|
+
"The debate will pause for the user to speak.]";
|
|
1179
1218
|
|
|
1180
|
-
|
|
1181
|
-
if (!debate.turnInProgress) {
|
|
1182
|
-
injectUserComment(session);
|
|
1183
|
-
}
|
|
1184
|
-
// If moderator is currently speaking, pendingComment will be picked up after moderator's onDone
|
|
1219
|
+
debate.moderatorSession.pushMessage(feedText, buildModeratorYieldCallbacks(session));
|
|
1185
1220
|
}
|
|
1186
1221
|
|
|
1187
1222
|
function injectUserComment(session) {
|
|
@@ -1190,6 +1225,7 @@ function attachDebate(ctx) {
|
|
|
1190
1225
|
|
|
1191
1226
|
var comment = debate.pendingComment;
|
|
1192
1227
|
debate.pendingComment = null;
|
|
1228
|
+
debate.handRaised = false;
|
|
1193
1229
|
|
|
1194
1230
|
// Record in debate history
|
|
1195
1231
|
debate.history.push({ speaker: "user", mateId: null, mateName: "User", text: comment.text });
|
|
@@ -1199,7 +1235,7 @@ function attachDebate(ctx) {
|
|
|
1199
1235
|
ctx.sm.appendToSessionFile(session, commentEntry);
|
|
1200
1236
|
ctx.sendToSession(session.localId, commentEntry);
|
|
1201
1237
|
|
|
1202
|
-
// Feed to moderator
|
|
1238
|
+
// Feed to moderator: yield the floor to the user
|
|
1203
1239
|
debate.turnInProgress = true;
|
|
1204
1240
|
var moderatorProfile = ctx.getMateProfile(debate.mateCtx, debate.moderatorId);
|
|
1205
1241
|
|
|
@@ -1216,9 +1252,89 @@ function attachDebate(ctx) {
|
|
|
1216
1252
|
|
|
1217
1253
|
var feedText = "[The user raised their hand and said:]\n" +
|
|
1218
1254
|
comment.text + "\n" +
|
|
1219
|
-
"[
|
|
1255
|
+
"[Acknowledge the user's input. Briefly respond, then YIELD THE FLOOR to the user by saying something like " +
|
|
1256
|
+
"\"The floor is yours\" or \"Go ahead\". Do NOT call on any panelist (no @mentions). " +
|
|
1257
|
+
"The debate will pause for the user to speak.]";
|
|
1258
|
+
|
|
1259
|
+
debate.moderatorSession.pushMessage(feedText, buildModeratorYieldCallbacks(session));
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
function buildModeratorYieldCallbacks(session) {
|
|
1263
|
+
var debate = session._debate;
|
|
1264
|
+
var moderatorProfile = ctx.getMateProfile(debate.mateCtx, debate.moderatorId);
|
|
1265
|
+
return {
|
|
1266
|
+
onActivity: function (activity) {
|
|
1267
|
+
if (session._debate && session._debate.phase !== "ended") {
|
|
1268
|
+
ctx.sendToSession(session.localId, { type: "debate_activity", mateId: debate.moderatorId, activity: activity });
|
|
1269
|
+
}
|
|
1270
|
+
},
|
|
1271
|
+
onDelta: function (delta) {
|
|
1272
|
+
if (session._debate && session._debate.phase !== "ended") {
|
|
1273
|
+
ctx.sendToSession(session.localId, { type: "debate_stream", mateId: debate.moderatorId, mateName: moderatorProfile.name, delta: delta });
|
|
1274
|
+
}
|
|
1275
|
+
},
|
|
1276
|
+
onDone: function (fullText) {
|
|
1277
|
+
if (!debate || debate.phase === "ended") return;
|
|
1278
|
+
debate.turnInProgress = false;
|
|
1279
|
+
|
|
1280
|
+
// Record moderator yield turn
|
|
1281
|
+
debate.history.push({ speaker: "moderator", mateId: debate.moderatorId, mateName: moderatorProfile.name, text: fullText });
|
|
1282
|
+
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 };
|
|
1283
|
+
session.history.push(turnEntry);
|
|
1284
|
+
ctx.sm.appendToSessionFile(session, turnEntry);
|
|
1285
|
+
ctx.sendToSession(session.localId, turnEntry);
|
|
1286
|
+
|
|
1287
|
+
// Enter user floor mode: pause debate and show input
|
|
1288
|
+
debate.awaitingUserFloor = true;
|
|
1289
|
+
persistDebateState(session);
|
|
1290
|
+
ctx.sendToSession(session.localId, { type: "debate_user_floor", topic: debate.topic, round: debate.round });
|
|
1291
|
+
},
|
|
1292
|
+
onError: function (errMsg) {
|
|
1293
|
+
console.error("[debate] Moderator yield error:", errMsg);
|
|
1294
|
+
endDebate(session, "error");
|
|
1295
|
+
},
|
|
1296
|
+
};
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function handleDebateUserFloorResponse(ws, msg) {
|
|
1300
|
+
var session = ctx.getSessionForWs(ws);
|
|
1301
|
+
if (!session) return;
|
|
1302
|
+
|
|
1303
|
+
var debate = session._debate;
|
|
1304
|
+
if (!debate || !debate.awaitingUserFloor || debate.phase !== "live") return;
|
|
1305
|
+
|
|
1306
|
+
debate.awaitingUserFloor = false;
|
|
1307
|
+
var userText = (msg && msg.text) ? msg.text.trim() : "";
|
|
1308
|
+
if (!userText) return;
|
|
1309
|
+
|
|
1310
|
+
// Record user's floor contribution
|
|
1311
|
+
debate.history.push({ speaker: "user", mateId: null, mateName: "User", text: userText });
|
|
1312
|
+
var floorEntry = { type: "debate_user_floor_done", text: userText };
|
|
1313
|
+
session.history.push(floorEntry);
|
|
1314
|
+
ctx.sm.appendToSessionFile(session, floorEntry);
|
|
1315
|
+
ctx.sendToSession(session.localId, floorEntry);
|
|
1316
|
+
|
|
1317
|
+
// Feed to moderator to resume debate
|
|
1318
|
+
debate.turnInProgress = true;
|
|
1319
|
+
var moderatorProfile = ctx.getMateProfile(debate.mateCtx, debate.moderatorId);
|
|
1320
|
+
|
|
1321
|
+
ctx.sendToSession(session.localId, {
|
|
1322
|
+
type: "debate_turn",
|
|
1323
|
+
mateId: debate.moderatorId,
|
|
1324
|
+
mateName: moderatorProfile.name,
|
|
1325
|
+
role: "moderator",
|
|
1326
|
+
round: debate.round,
|
|
1327
|
+
avatarColor: moderatorProfile.avatarColor,
|
|
1328
|
+
avatarStyle: moderatorProfile.avatarStyle,
|
|
1329
|
+
avatarSeed: moderatorProfile.avatarSeed,
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
var feedText = "[The user took the floor and said:]\n" +
|
|
1333
|
+
userText + "\n" +
|
|
1334
|
+
"[Acknowledge the user's contribution and resume the debate. Call on the next panelist with @TheirName.]";
|
|
1220
1335
|
|
|
1221
1336
|
debate.moderatorSession.pushMessage(feedText, buildModeratorCallbacks(session));
|
|
1337
|
+
persistDebateState(session);
|
|
1222
1338
|
}
|
|
1223
1339
|
|
|
1224
1340
|
function handleDebateConfirmBrief(ws) {
|
|
@@ -1592,10 +1708,12 @@ function attachDebate(ctx) {
|
|
|
1592
1708
|
|
|
1593
1709
|
return {
|
|
1594
1710
|
handleDebateStart: handleDebateStart,
|
|
1711
|
+
handleDebateHandRaise: handleDebateHandRaise,
|
|
1595
1712
|
handleDebateComment: handleDebateComment,
|
|
1596
1713
|
handleDebateStop: handleDebateStop,
|
|
1597
1714
|
handleDebateConcludeResponse: handleDebateConcludeResponse,
|
|
1598
1715
|
handleDebateConfirmBrief: handleDebateConfirmBrief,
|
|
1716
|
+
handleDebateUserFloorResponse: handleDebateUserFloorResponse,
|
|
1599
1717
|
restoreDebateState: restoreDebateState,
|
|
1600
1718
|
checkForDmDebateBrief: checkForDmDebateBrief,
|
|
1601
1719
|
};
|