@tritard/waterbrother 0.16.141 → 0.16.143
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/package.json +1 -1
- package/src/discord.js +339 -3
package/package.json
CHANGED
package/src/discord.js
CHANGED
|
@@ -5,6 +5,8 @@ import { buildSelfAwarenessManifest, formatAboutWaterbrother, formatSelfState }
|
|
|
5
5
|
import { createSession, listSessions, loadSession, saveSession } from "./session-store.js";
|
|
6
6
|
import { loadGatewayBridge, loadGatewayState, saveGatewayBridge, saveGatewayState } from "./gateway-state.js";
|
|
7
7
|
import {
|
|
8
|
+
addSharedRoomNote,
|
|
9
|
+
addVerificationResult,
|
|
8
10
|
acceptSharedInvite,
|
|
9
11
|
approveSharedInvite,
|
|
10
12
|
assignSharedTask,
|
|
@@ -153,6 +155,63 @@ function buildReply(message, botUserId) {
|
|
|
153
155
|
return null;
|
|
154
156
|
}
|
|
155
157
|
|
|
158
|
+
function normalizeDiscordIntentText(text = "") {
|
|
159
|
+
return String(text || "")
|
|
160
|
+
.replace(/[‘’]/g, "'")
|
|
161
|
+
.replace(/[“”]/g, '"')
|
|
162
|
+
.replace(/\s+/g, " ")
|
|
163
|
+
.trim();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function parseDiscordNaturalRoomIntent(text = "") {
|
|
167
|
+
const value = normalizeDiscordIntentText(text);
|
|
168
|
+
const lower = value.toLowerCase();
|
|
169
|
+
if (!value || lower.startsWith("/")) return null;
|
|
170
|
+
|
|
171
|
+
if (/\bwho(?:'s| is)? in (?:the )?room\b/.test(lower) || /\bwho(?:'s| is)? in (?:the )?project\b/.test(lower) || /\bwho are the members\b/.test(lower) || /\bshow members\b/.test(lower)) {
|
|
172
|
+
return { type: "command", command: "/members" };
|
|
173
|
+
}
|
|
174
|
+
if (/\bwhich terminals are live\b/.test(lower) || /\bwhat terminals are live\b/.test(lower) || /\bwhich bots are live\b/.test(lower) || /\bwho is live\b/.test(lower)) {
|
|
175
|
+
return { type: "command", command: "/terminals" };
|
|
176
|
+
}
|
|
177
|
+
if (/\bwho(?:'s| is)? the verifier\b/.test(lower) || /\bwhat is the verifier\b/.test(lower) || /\bwho should verify this\b/.test(lower)) {
|
|
178
|
+
return { type: "command", command: "/verifier" };
|
|
179
|
+
}
|
|
180
|
+
if (/\bwho(?:'s| is)? the reviewer\b/.test(lower) || /\bwhat is the reviewer\b/.test(lower)) {
|
|
181
|
+
return { type: "command", command: "/reviewer" };
|
|
182
|
+
}
|
|
183
|
+
if (/\bwho(?:'s| is)? the executor\b/.test(lower) || /\bwhat is the executor\b/.test(lower) || /\bwho should take this\b/.test(lower) || /\bwho should handle this\b/.test(lower)) {
|
|
184
|
+
return { type: "command", command: "/executor" };
|
|
185
|
+
}
|
|
186
|
+
if (/\bcompare (?:executor|reviewer)\b/.test(lower) || /\bcompare reviewer and executor\b/.test(lower) || /\bcompare executor and reviewer\b/.test(lower) || /\bdo the reviewer and executor agree\b/.test(lower)) {
|
|
187
|
+
return { type: "compare-executor-reviewer" };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const roleMatch = lower.match(/\b(executor|reviewer|verifier|standby)\b/);
|
|
191
|
+
if (roleMatch) {
|
|
192
|
+
const role = String(roleMatch[1] || "").trim().toLowerCase();
|
|
193
|
+
const patterns = [
|
|
194
|
+
/^(?:have|make|set)\s+(.+?)['’]s\s+(?:bot|terminal)\s+(?:be|as|to)?\s*(?:the\s+)?(executor|reviewer|verifier|standby)\s*$/i,
|
|
195
|
+
/^(?:have|make|set)\s+(.+?)\s+(?:bot|terminal)\s+(?:be|as|to)?\s*(?:the\s+)?(executor|reviewer|verifier|standby)\s*$/i,
|
|
196
|
+
/^(.+?)['’]s\s+(?:bot|terminal)\s+should\s+(?:be\s+)?(?:the\s+)?(executor|reviewer|verifier|standby)\s*$/i,
|
|
197
|
+
/^(.+?)\s+(?:bot|terminal)\s+should\s+(?:be\s+)?(?:the\s+)?(executor|reviewer|verifier|standby)\s*$/i,
|
|
198
|
+
/^(?:have|make|set)\s+(.+?)\s+(?:be|as|to)?\s*(?:the\s+)?(executor|reviewer|verifier|standby)\s*$/i,
|
|
199
|
+
/^(.+?)\s+should\s+(?:be\s+)?(?:the\s+)?(executor|reviewer|verifier|standby)\s*$/i
|
|
200
|
+
];
|
|
201
|
+
for (const pattern of patterns) {
|
|
202
|
+
const match = value.match(pattern);
|
|
203
|
+
if (!match) continue;
|
|
204
|
+
const target = String(match[1] || "").trim();
|
|
205
|
+
const nextRole = String(match[2] || role).trim().toLowerCase();
|
|
206
|
+
if (target && nextRole) {
|
|
207
|
+
return { type: "command", command: `/assign-role ${target} ${nextRole}` };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
156
215
|
function buildDiscordHelp() {
|
|
157
216
|
return [
|
|
158
217
|
"Waterbrother Discord control",
|
|
@@ -170,6 +229,14 @@ function buildDiscordHelp() {
|
|
|
170
229
|
"/executor show the selected executor",
|
|
171
230
|
"/reviewer show the assigned reviewer",
|
|
172
231
|
"/verifier show the assigned verifier",
|
|
232
|
+
"/verify run verification through the assigned verifier or live terminal",
|
|
233
|
+
"/verification show the latest verification result",
|
|
234
|
+
"/rerun-verification rerun verification",
|
|
235
|
+
"/override-verification override the current blocking verification",
|
|
236
|
+
"/review-status show blocking review and latest reviewer outcome",
|
|
237
|
+
"/accept-reviewer-concerns clear blocking review by accepting the reviewer concerns",
|
|
238
|
+
"/override-reviewer override the current blocking reviewer outcome",
|
|
239
|
+
"/switch-executor-to-reviewer make the reviewer the executor",
|
|
173
240
|
"/assign-role <terminal|this terminal> <executor|reviewer|verifier|standby> assign a room role",
|
|
174
241
|
"/members list shared room members",
|
|
175
242
|
"/tasks list shared room tasks",
|
|
@@ -399,6 +466,107 @@ function formatDiscordTaskHistory(result = {}) {
|
|
|
399
466
|
return lines.join("\n");
|
|
400
467
|
}
|
|
401
468
|
|
|
469
|
+
function getLatestBlockingReviewPolicy(project = {}) {
|
|
470
|
+
const events = Array.isArray(project?.recentEvents) ? [...project.recentEvents] : [];
|
|
471
|
+
const ordered = events
|
|
472
|
+
.filter((event) => event?.createdAt)
|
|
473
|
+
.sort((a, b) => String(b.createdAt || "").localeCompare(String(a.createdAt || "")));
|
|
474
|
+
const resolvedAgents = new Set();
|
|
475
|
+
for (const event of ordered) {
|
|
476
|
+
const type = String(event?.type || "").trim();
|
|
477
|
+
const meta = event?.meta && typeof event.meta === "object" ? event.meta : {};
|
|
478
|
+
const agentId = String(meta.agentId || "").trim();
|
|
479
|
+
if (!agentId) continue;
|
|
480
|
+
if (type === "review-policy-cleared" || type === "review-policy-override") {
|
|
481
|
+
resolvedAgents.add(agentId);
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (type === "review-policy" && String(meta.policy || "").trim() === "blocking" && !resolvedAgents.has(agentId)) {
|
|
485
|
+
return event;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function getLatestReviewResult(project = {}) {
|
|
492
|
+
const events = Array.isArray(project?.recentEvents) ? [...project.recentEvents] : [];
|
|
493
|
+
const ordered = events
|
|
494
|
+
.filter((event) => event?.createdAt)
|
|
495
|
+
.sort((a, b) => String(b.createdAt || "").localeCompare(String(a.createdAt || "")));
|
|
496
|
+
return ordered.find((event) => String(event?.type || "").trim() === "review-result") || null;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function getBlockingVerificationResult(project = {}) {
|
|
500
|
+
if (String(project?.verificationMode || "manual").trim() !== "blocking") {
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
const latestResult = getLatestVerificationResult(project);
|
|
504
|
+
const latestId = String(latestResult?.id || "").trim();
|
|
505
|
+
const latestOutcome = String(latestResult?.outcome || "").trim().toLowerCase();
|
|
506
|
+
if (!latestId || !latestOutcome || latestOutcome === "passed") return null;
|
|
507
|
+
const events = Array.isArray(project?.recentEvents) ? [...project.recentEvents] : [];
|
|
508
|
+
const ordered = events
|
|
509
|
+
.filter((event) => event?.createdAt)
|
|
510
|
+
.sort((a, b) => String(b.createdAt || "").localeCompare(String(a.createdAt || "")));
|
|
511
|
+
for (const event of ordered) {
|
|
512
|
+
const type = String(event?.type || "").trim();
|
|
513
|
+
const meta = event?.meta && typeof event.meta === "object" ? event.meta : {};
|
|
514
|
+
if (String(meta.verificationResultId || "").trim() !== latestId) continue;
|
|
515
|
+
if (type === "verification-override") return null;
|
|
516
|
+
if (type === "verification-result") return latestResult;
|
|
517
|
+
}
|
|
518
|
+
return latestResult;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function formatDiscordVerificationResult(result = {}, verifier = null) {
|
|
522
|
+
const lines = [
|
|
523
|
+
"Verification result",
|
|
524
|
+
`outcome: ${String(result.outcome || "failed")}`
|
|
525
|
+
];
|
|
526
|
+
if (verifier) lines.splice(1, 0, `verifier: ${getAgentOwnerDisplay(verifier)}`);
|
|
527
|
+
if (result.failedCommand) lines.push(`failed step: ${String(result.failedCommand || "")}`);
|
|
528
|
+
if (result.isolation) lines.push(`environment: ${String(result.isolation || "")}`);
|
|
529
|
+
if (Array.isArray(result.commands) && result.commands.length) {
|
|
530
|
+
lines.push("commands:");
|
|
531
|
+
for (const command of result.commands) lines.push(`- ${command}`);
|
|
532
|
+
}
|
|
533
|
+
if (result.summary) lines.push("", "Summary", String(result.summary || ""));
|
|
534
|
+
if (Array.isArray(result.logs) && result.logs.length) {
|
|
535
|
+
lines.push("", "Top errors", ...result.logs.slice(0, 6).map((line) => `- ${line}`));
|
|
536
|
+
}
|
|
537
|
+
if (String(result.outcome || "").trim() !== "passed") {
|
|
538
|
+
lines.push("", "Next actions", "- /rerun-verification", "- /override-verification");
|
|
539
|
+
}
|
|
540
|
+
return lines.join("\n");
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function formatDiscordReviewStatus(project = {}) {
|
|
544
|
+
const blockingReview = getLatestBlockingReviewPolicy(project);
|
|
545
|
+
const latestReview = getLatestReviewResult(project);
|
|
546
|
+
const reviewMeta = latestReview?.meta && typeof latestReview.meta === "object" ? latestReview.meta : {};
|
|
547
|
+
return [
|
|
548
|
+
"Reviewer status",
|
|
549
|
+
`blocking review: ${blockingReview ? "yes" : "no"}`,
|
|
550
|
+
`latest reviewer outcome: ${String(reviewMeta.outcome || "").trim() || "none"}`,
|
|
551
|
+
reviewMeta.agentId ? `reviewer agent: ${reviewMeta.agentId}` : ""
|
|
552
|
+
].filter(Boolean).join("\n");
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
async function formatDiscordExecutorReviewerCompare(project = {}, session = null) {
|
|
556
|
+
const executor = chooseAgentByRole(project, "executor");
|
|
557
|
+
const reviewer = chooseAgentByRole(project, "reviewer");
|
|
558
|
+
const liveHosts = await getLiveBridgeHosts({ cwd: session?.cwd || "" });
|
|
559
|
+
const executorLive = executor ? liveHosts.some((host) => String(host?.ownerId || "") === String(executor?.ownerId || "") || String(host?.sessionId || "") === String(executor?.sessionId || "")) : false;
|
|
560
|
+
const reviewerLive = reviewer ? liveHosts.some((host) => String(host?.ownerId || "") === String(reviewer?.ownerId || "") || String(host?.sessionId || "") === String(reviewer?.sessionId || "")) : false;
|
|
561
|
+
return [
|
|
562
|
+
formatDiscordAgentStatus("Executor", executor, { live: executorLive }),
|
|
563
|
+
"",
|
|
564
|
+
formatDiscordAgentStatus("Reviewer", reviewer, { live: reviewerLive }),
|
|
565
|
+
"",
|
|
566
|
+
formatDiscordReviewStatus(project)
|
|
567
|
+
].join("\n");
|
|
568
|
+
}
|
|
569
|
+
|
|
402
570
|
function parseDiscordInviteCommand(message = {}, text = "") {
|
|
403
571
|
const value = String(text || "").trim();
|
|
404
572
|
const match = value.match(/^\/?(?:room\s+)?invite\s+(.+)$/i);
|
|
@@ -988,6 +1156,10 @@ async function handleDiscordControlCommand(runtime, state, message, rawText) {
|
|
|
988
1156
|
const value = String(rawText || "").replace(/\s+/g, " ").trim();
|
|
989
1157
|
const normalized = value.toLowerCase();
|
|
990
1158
|
if (!normalized) return null;
|
|
1159
|
+
const naturalIntent = parseDiscordNaturalRoomIntent(value);
|
|
1160
|
+
if (naturalIntent?.type === "command" && naturalIntent.command) {
|
|
1161
|
+
return handleDiscordControlCommand(runtime, state, message, naturalIntent.command);
|
|
1162
|
+
}
|
|
991
1163
|
const roomAlias = normalized.startsWith("/room ") ? value.slice("/room ".length).trim() : (normalized.startsWith("room ") ? value.slice("room ".length).trim() : "");
|
|
992
1164
|
const normalizedRoomAlias = roomAlias.toLowerCase();
|
|
993
1165
|
if (normalized === "/help" || normalized === "help") {
|
|
@@ -1049,7 +1221,169 @@ async function handleDiscordControlCommand(runtime, state, message, rawText) {
|
|
|
1049
1221
|
const agent = chooseAgentByRole(project, "verifier");
|
|
1050
1222
|
const liveHosts = await getLiveBridgeHosts({ cwd: session?.cwd || "" });
|
|
1051
1223
|
const live = agent ? liveHosts.some((host) => String(host?.ownerId || "") === String(agent?.ownerId || "") || String(host?.sessionId || "") === String(agent?.sessionId || "")) : false;
|
|
1052
|
-
|
|
1224
|
+
const latestVerification = getLatestVerificationResult(project);
|
|
1225
|
+
const blockingVerification = getBlockingVerificationResult(project);
|
|
1226
|
+
return [
|
|
1227
|
+
formatDiscordAgentStatus("Verifier", agent, { live }),
|
|
1228
|
+
`verification mode: ${String(project.verificationMode || "manual")}`,
|
|
1229
|
+
`blocking verification: ${blockingVerification ? "yes" : "no"}`,
|
|
1230
|
+
`latest verification outcome: ${String(latestVerification?.outcome || "").trim() || "none"}`
|
|
1231
|
+
].filter(Boolean).join("\n");
|
|
1232
|
+
}
|
|
1233
|
+
if (normalized === "/verification" || normalized === "verification" || normalizedRoomAlias === "verification") {
|
|
1234
|
+
const sessionId = await ensurePeerSession(runtime, state, message);
|
|
1235
|
+
const { project } = await bindSharedRoomForMessage(message, sessionId);
|
|
1236
|
+
if (!project?.enabled) return "This project is not shared.";
|
|
1237
|
+
const verifier = chooseAgentByRole(project, "verifier");
|
|
1238
|
+
const result = getLatestVerificationResult(project);
|
|
1239
|
+
if (!result) return "Verification result\nNo verification result is on record yet.";
|
|
1240
|
+
return formatDiscordVerificationResult(result, verifier);
|
|
1241
|
+
}
|
|
1242
|
+
if (normalized === "/verify" || normalized === "verify" || normalized === "/run-verification" || normalizedRoomAlias === "verify" || normalizedRoomAlias === "run-verification") {
|
|
1243
|
+
const sessionId = await ensurePeerSession(runtime, state, message);
|
|
1244
|
+
const { project, session } = await bindSharedRoomForMessage(message, sessionId);
|
|
1245
|
+
if (!project?.enabled) return "This project is not shared.";
|
|
1246
|
+
const actor = describeDiscordUser(message);
|
|
1247
|
+
const verifier = chooseAgentByRole(project, "verifier");
|
|
1248
|
+
const liveHosts = await getLiveBridgeHosts({ cwd: session?.cwd || "" });
|
|
1249
|
+
const verifierHost = verifier ? liveHosts.find((host) => String(host?.ownerId || "") === String(verifier?.ownerId || "") || String(host?.sessionId || "") === String(verifier?.sessionId || "")) : null;
|
|
1250
|
+
const targetHost = verifierHost || await getLiveBridgeHost({ cwd: session?.cwd || "" });
|
|
1251
|
+
if (!targetHost) {
|
|
1252
|
+
return "No live Waterbrother TUI is connected. Start Waterbrother in the terminal first, then retry.";
|
|
1253
|
+
}
|
|
1254
|
+
const bridged = await runPromptViaBridge(runtime, message, "run verification", {
|
|
1255
|
+
sessionId,
|
|
1256
|
+
requestKind: "verification",
|
|
1257
|
+
targetHost
|
|
1258
|
+
});
|
|
1259
|
+
const routedResult = bridged?.meta?.verificationResult && typeof bridged.meta.verificationResult === "object"
|
|
1260
|
+
? bridged.meta.verificationResult
|
|
1261
|
+
: null;
|
|
1262
|
+
if (routedResult) {
|
|
1263
|
+
const nextProject = await addVerificationResult(session.cwd || process.cwd(), routedResult, {
|
|
1264
|
+
actorId: actor.userId,
|
|
1265
|
+
actorName: actor.displayName
|
|
1266
|
+
});
|
|
1267
|
+
const latest = getLatestVerificationResult(nextProject) || routedResult;
|
|
1268
|
+
return formatDiscordVerificationResult(latest, verifier);
|
|
1269
|
+
}
|
|
1270
|
+
if (bridged?.error) return bridged.error;
|
|
1271
|
+
return String(bridged?.content || "").trim() || "Verification result\n(no content)";
|
|
1272
|
+
}
|
|
1273
|
+
if (normalized === "/rerun-verification" || normalized === "rerun verification" || normalizedRoomAlias === "rerun verification") {
|
|
1274
|
+
return handleDiscordControlCommand(runtime, state, message, "/verify");
|
|
1275
|
+
}
|
|
1276
|
+
if (normalized === "/override-verification" || normalized === "override verification" || normalizedRoomAlias === "override verification") {
|
|
1277
|
+
const sessionId = await ensurePeerSession(runtime, state, message);
|
|
1278
|
+
const { project, session } = await bindSharedRoomForMessage(message, sessionId);
|
|
1279
|
+
if (!project?.enabled) return "This project is not shared.";
|
|
1280
|
+
const blockingVerification = getBlockingVerificationResult(project);
|
|
1281
|
+
if (!blockingVerification) return "Verification override\nNo blocking verification is active.";
|
|
1282
|
+
const actor = describeDiscordUser(message);
|
|
1283
|
+
await addSharedRoomNote(session.cwd || process.cwd(), "Blocking verification overridden for now", {
|
|
1284
|
+
actorId: actor.userId,
|
|
1285
|
+
actorName: actor.displayName,
|
|
1286
|
+
type: "verification-override",
|
|
1287
|
+
meta: {
|
|
1288
|
+
verificationResultId: String(blockingVerification.id || "").trim(),
|
|
1289
|
+
outcome: String(blockingVerification.outcome || "").trim()
|
|
1290
|
+
}
|
|
1291
|
+
});
|
|
1292
|
+
return [
|
|
1293
|
+
"Verification overridden",
|
|
1294
|
+
`latest outcome: ${String(blockingVerification.outcome || "failed")}`,
|
|
1295
|
+
"Execution can proceed for now."
|
|
1296
|
+
].join("\n");
|
|
1297
|
+
}
|
|
1298
|
+
if (normalized === "/review-status" || normalized === "review status" || normalizedRoomAlias === "review-status" || normalizedRoomAlias === "review status") {
|
|
1299
|
+
const sessionId = await ensurePeerSession(runtime, state, message);
|
|
1300
|
+
const { project } = await bindSharedRoomForMessage(message, sessionId);
|
|
1301
|
+
if (!project?.enabled) return "This project is not shared.";
|
|
1302
|
+
return formatDiscordReviewStatus(project);
|
|
1303
|
+
}
|
|
1304
|
+
if (naturalIntent?.type === "compare-executor-reviewer") {
|
|
1305
|
+
const sessionId = await ensurePeerSession(runtime, state, message);
|
|
1306
|
+
const { project, session } = await bindSharedRoomForMessage(message, sessionId);
|
|
1307
|
+
if (!project?.enabled) return "This project is not shared.";
|
|
1308
|
+
return formatDiscordExecutorReviewerCompare(project, session);
|
|
1309
|
+
}
|
|
1310
|
+
if (normalized === "/accept-reviewer-concerns" || normalized === "accept reviewer concerns" || normalizedRoomAlias === "accept reviewer concerns") {
|
|
1311
|
+
const sessionId = await ensurePeerSession(runtime, state, message);
|
|
1312
|
+
const { project, session } = await bindSharedRoomForMessage(message, sessionId);
|
|
1313
|
+
if (!project?.enabled) return "This project is not shared.";
|
|
1314
|
+
const blockingReview = getLatestBlockingReviewPolicy(project);
|
|
1315
|
+
if (!blockingReview) return "Reviewer concerns\nNo blocking review is active.";
|
|
1316
|
+
const result = getLatestReviewResult(project);
|
|
1317
|
+
if (!result) return "Reviewer concerns\nNo reviewer result is on record yet.";
|
|
1318
|
+
const meta = result.meta && typeof result.meta === "object" ? result.meta : {};
|
|
1319
|
+
const blockingMeta = blockingReview.meta && typeof blockingReview.meta === "object" ? blockingReview.meta : {};
|
|
1320
|
+
if (String(meta.agentId || "").trim() !== String(blockingMeta.agentId || "").trim()) {
|
|
1321
|
+
return "Reviewer concerns\nThe latest reviewer result does not match the current blocking reviewer.";
|
|
1322
|
+
}
|
|
1323
|
+
if (String(meta.outcome || "").trim() !== "concerns") {
|
|
1324
|
+
return "Reviewer concerns\nThe latest reviewer result is not concerns.";
|
|
1325
|
+
}
|
|
1326
|
+
const actor = describeDiscordUser(message);
|
|
1327
|
+
await addSharedRoomNote(session.cwd || process.cwd(), "Reviewer concerns accepted; revise before continuing", {
|
|
1328
|
+
actorId: actor.userId,
|
|
1329
|
+
actorName: actor.displayName,
|
|
1330
|
+
type: "review-concerns-accepted",
|
|
1331
|
+
meta: { agentId: String(meta.agentId || "").trim() }
|
|
1332
|
+
});
|
|
1333
|
+
await addSharedRoomNote(session.cwd || process.cwd(), "Blocking review cleared after reviewer concerns were accepted", {
|
|
1334
|
+
actorId: actor.userId,
|
|
1335
|
+
actorName: actor.displayName,
|
|
1336
|
+
type: "review-policy-cleared",
|
|
1337
|
+
meta: { agentId: String(meta.agentId || "").trim() }
|
|
1338
|
+
});
|
|
1339
|
+
return "Reviewer concerns accepted\nBlocking review has been cleared so the team can revise and continue.";
|
|
1340
|
+
}
|
|
1341
|
+
if (normalized === "/override-reviewer" || normalized === "override reviewer" || normalizedRoomAlias === "override reviewer") {
|
|
1342
|
+
const sessionId = await ensurePeerSession(runtime, state, message);
|
|
1343
|
+
const { project, session } = await bindSharedRoomForMessage(message, sessionId);
|
|
1344
|
+
if (!project?.enabled) return "This project is not shared.";
|
|
1345
|
+
const blockingReview = getLatestBlockingReviewPolicy(project);
|
|
1346
|
+
if (!blockingReview) return "Reviewer override\nNo blocking review is active.";
|
|
1347
|
+
const result = getLatestReviewResult(project);
|
|
1348
|
+
const meta = result?.meta && typeof result.meta === "object" ? result.meta : {};
|
|
1349
|
+
const blockingMeta = blockingReview.meta && typeof blockingReview.meta === "object" ? blockingReview.meta : {};
|
|
1350
|
+
if (String(meta.agentId || "").trim() !== String(blockingMeta.agentId || "").trim()) {
|
|
1351
|
+
return "Reviewer override\nThe latest reviewer result does not match the current blocking reviewer.";
|
|
1352
|
+
}
|
|
1353
|
+
const actor = describeDiscordUser(message);
|
|
1354
|
+
await addSharedRoomNote(session.cwd || process.cwd(), "Reviewer outcome overridden by the room", {
|
|
1355
|
+
actorId: actor.userId,
|
|
1356
|
+
actorName: actor.displayName,
|
|
1357
|
+
type: "review-policy-override",
|
|
1358
|
+
meta: { agentId: String(meta.agentId || "").trim() }
|
|
1359
|
+
});
|
|
1360
|
+
return "Reviewer overridden\nExecution may proceed.";
|
|
1361
|
+
}
|
|
1362
|
+
if (normalized === "/switch-executor-to-reviewer" || normalized === "switch executor to reviewer" || normalizedRoomAlias === "switch executor to reviewer") {
|
|
1363
|
+
const sessionId = await ensurePeerSession(runtime, state, message);
|
|
1364
|
+
const { project, session } = await bindSharedRoomForMessage(message, sessionId);
|
|
1365
|
+
if (!project?.enabled) return "This project is not shared.";
|
|
1366
|
+
const reviewer = chooseAgentByRole(project, "reviewer");
|
|
1367
|
+
if (!reviewer) return "Executor switch\nNo reviewer is assigned.";
|
|
1368
|
+
const currentExecutor = chooseAgentByRole(project, "executor");
|
|
1369
|
+
const actor = describeDiscordUser(message);
|
|
1370
|
+
if (currentExecutor?.id && currentExecutor.id !== reviewer.id) {
|
|
1371
|
+
await upsertSharedAgent(session.cwd || process.cwd(), { ...currentExecutor, role: "standby" }, {
|
|
1372
|
+
actorId: actor.userId,
|
|
1373
|
+
actorName: actor.displayName
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
await upsertSharedAgent(session.cwd || process.cwd(), { ...reviewer, role: "executor" }, {
|
|
1377
|
+
actorId: actor.userId,
|
|
1378
|
+
actorName: actor.displayName
|
|
1379
|
+
});
|
|
1380
|
+
await addSharedRoomNote(session.cwd || process.cwd(), `${reviewer.ownerName || reviewer.label || reviewer.ownerId || "Reviewer"} is now the executor`, {
|
|
1381
|
+
actorId: actor.userId,
|
|
1382
|
+
actorName: actor.displayName,
|
|
1383
|
+
type: "executor-handoff-reassigned",
|
|
1384
|
+
meta: { currentAgentId: String(reviewer.id || "").trim() }
|
|
1385
|
+
});
|
|
1386
|
+
return `Executor switched\nexecutor: ${reviewer.ownerName || reviewer.label || reviewer.ownerId || reviewer.id || "reviewer"}`;
|
|
1053
1387
|
}
|
|
1054
1388
|
if (normalized.startsWith("/assign-role ") || normalizedRoomAlias.startsWith("assign-role ")) {
|
|
1055
1389
|
const body = normalized.startsWith("/assign-role ") ? value.slice("/assign-role ".length).trim() : roomAlias.slice("assign-role ".length).trim();
|
|
@@ -1395,7 +1729,9 @@ async function getLiveBridgeHosts({ cwd = "" } = {}) {
|
|
|
1395
1729
|
}
|
|
1396
1730
|
|
|
1397
1731
|
async function runPromptViaBridge(runtime, message, promptText, options = {}) {
|
|
1398
|
-
const host =
|
|
1732
|
+
const host = options.targetHost && typeof options.targetHost === "object"
|
|
1733
|
+
? options.targetHost
|
|
1734
|
+
: await getLiveBridgeHost();
|
|
1399
1735
|
if (!host) {
|
|
1400
1736
|
return { error: "No live Waterbrother TUI is connected. Start Waterbrother in the terminal first, then retry." };
|
|
1401
1737
|
}
|
|
@@ -1413,7 +1749,7 @@ async function runPromptViaBridge(runtime, message, promptText, options = {}) {
|
|
|
1413
1749
|
displayName: actor.displayName,
|
|
1414
1750
|
sessionId: String(options.sessionId || "").trim(),
|
|
1415
1751
|
text: String(promptText || "").trim(),
|
|
1416
|
-
requestKind: "prompt",
|
|
1752
|
+
requestKind: String(options.requestKind || "prompt").trim() || "prompt",
|
|
1417
1753
|
explicitExecution: true,
|
|
1418
1754
|
targetPid: Number(host?.pid || 0),
|
|
1419
1755
|
targetSessionId: String(host?.sessionId || "").trim(),
|