@tritard/waterbrother 0.16.83 → 0.16.85

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/gateway.js +151 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tritard/waterbrother",
3
- "version": "0.16.83",
3
+ "version": "0.16.85",
4
4
  "description": "Waterbrother: bring-your-own-model coding CLI with local tools, sessions, operator modes, and approval controls",
5
5
  "type": "module",
6
6
  "bin": {
package/src/gateway.js CHANGED
@@ -488,6 +488,21 @@ function chooseReviewerAgent(project) {
488
488
  return agents.find((agent) => String(agent?.role || "").trim() === "reviewer") || null;
489
489
  }
490
490
 
491
+ function summarizeExecutorReviewerArbitration(project, fallbackExecutor = {}) {
492
+ const executorAgent = chooseExecutorAgent(project, fallbackExecutor);
493
+ const reviewerAgent = chooseReviewerAgent(project);
494
+ const executorRuntime = getAgentRuntimeKey(executorAgent || fallbackExecutor);
495
+ const reviewerRuntime = getAgentRuntimeKey(reviewerAgent);
496
+ return {
497
+ executorAgent,
498
+ reviewerAgent,
499
+ executorRuntime,
500
+ reviewerRuntime,
501
+ aligned: Boolean(executorRuntime && reviewerRuntime && executorRuntime === reviewerRuntime),
502
+ split: Boolean(executorRuntime && reviewerRuntime && executorRuntime !== reviewerRuntime)
503
+ };
504
+ }
505
+
491
506
  function parseReviewerOutcome(text = "") {
492
507
  const value = String(text || "").trim();
493
508
  const lower = value.toLowerCase();
@@ -525,6 +540,14 @@ function getLatestBlockingReviewPolicy(project) {
525
540
  return null;
526
541
  }
527
542
 
543
+ function getLatestReviewResult(project) {
544
+ const events = Array.isArray(project?.recentEvents) ? [...project.recentEvents] : [];
545
+ const ordered = events
546
+ .filter((event) => event?.createdAt)
547
+ .sort((a, b) => String(b.createdAt || "").localeCompare(String(a.createdAt || "")));
548
+ return ordered.find((event) => String(event?.type || "").trim() === "review-result") || null;
549
+ }
550
+
528
551
  function memberRoleWeight(role = "") {
529
552
  const normalized = String(role || "").trim().toLowerCase();
530
553
  if (normalized === "owner") return 3;
@@ -773,6 +796,9 @@ function parseTelegramStateIntent(text = "") {
773
796
  if (/\bmodel conflict\b/.test(lower) || /\bruntime conflict\b/.test(lower) || /\bmodel split\b/.test(lower) || /\bruntime split\b/.test(lower) || /\bcompare models\b/.test(lower) || /\bcompare bots\b/.test(lower)) {
774
797
  return { action: "model-conflict" };
775
798
  }
799
+ 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)) {
800
+ return { action: "executor-reviewer-compare" };
801
+ }
776
802
  if (/\bwhich (?:bot|agent|terminal) should take this\b/.test(lower) || /\bwho should take this\b/.test(lower) || /\bwho should handle this\b/.test(lower)) {
777
803
  return { action: "executor-recommendation" };
778
804
  }
@@ -799,6 +825,16 @@ function parseTelegramStateIntent(text = "") {
799
825
  return { action: "member-status", target: memberMatch[1].trim() };
800
826
  }
801
827
 
828
+ if (/\baccept reviewer concerns\b/.test(lower) || /\baccept the reviewer'?s concerns\b/.test(lower)) {
829
+ return { action: "accept-reviewer-concerns" };
830
+ }
831
+ if (/\boverride reviewer\b/.test(lower) || /\boverride the reviewer\b/.test(lower)) {
832
+ return { action: "override-reviewer" };
833
+ }
834
+ if (/\bswitch executor to reviewer\b/.test(lower) || /\bmake reviewer the executor\b/.test(lower) || /\buse reviewer as executor\b/.test(lower)) {
835
+ return { action: "switch-executor-to-reviewer" };
836
+ }
837
+
802
838
  return null;
803
839
  }
804
840
 
@@ -2120,6 +2156,8 @@ class TelegramGateway {
2120
2156
  const { session, project } = await this.bindSharedRoomForMessage(message, sessionId);
2121
2157
  const cwd = session.cwd || this.cwd;
2122
2158
  const known = this.listKnownChatPeople(message);
2159
+ const actorId = String(message?.from?.id || "").trim();
2160
+ const actorName = this.describeTelegramUser(message?.from || {}).displayName || actorId;
2123
2161
  const host = await this.getLiveBridgeHost();
2124
2162
  const executor = {
2125
2163
  surface: host?.surface || (host ? "live-tui" : "telegram-fallback"),
@@ -2129,6 +2167,90 @@ class TelegramGateway {
2129
2167
  hostSessionId: host?.sessionId || ""
2130
2168
  };
2131
2169
 
2170
+ if (intent.action === "accept-reviewer-concerns") {
2171
+ if (!project?.enabled) {
2172
+ return "This project is not shared.";
2173
+ }
2174
+ const result = getLatestReviewResult(project);
2175
+ if (!result) {
2176
+ return "<b>Reviewer concerns</b>\nNo reviewer result is on record yet.";
2177
+ }
2178
+ const meta = result.meta && typeof result.meta === "object" ? result.meta : {};
2179
+ if (String(meta.outcome || "").trim() !== "concerns") {
2180
+ return "<b>Reviewer concerns</b>\nThe latest reviewer result is not concerns.";
2181
+ }
2182
+ await addSharedRoomNote(cwd, "Reviewer concerns accepted; revise before continuing", {
2183
+ actorId,
2184
+ actorName,
2185
+ type: "review-concerns-accepted",
2186
+ meta: {
2187
+ agentId: String(meta.agentId || "").trim()
2188
+ }
2189
+ });
2190
+ await addSharedRoomNote(cwd, "Blocking review cleared after reviewer concerns were accepted", {
2191
+ actorId,
2192
+ actorName,
2193
+ type: "review-policy-cleared",
2194
+ meta: {
2195
+ agentId: String(meta.agentId || "").trim()
2196
+ }
2197
+ });
2198
+ return [
2199
+ "<b>Reviewer concerns accepted</b>",
2200
+ "The room accepted the reviewer concerns.",
2201
+ "Blocking review has been cleared so the team can revise and continue."
2202
+ ].join("\n");
2203
+ }
2204
+
2205
+ if (intent.action === "override-reviewer") {
2206
+ if (!project?.enabled) {
2207
+ return "This project is not shared.";
2208
+ }
2209
+ const result = getLatestReviewResult(project);
2210
+ const meta = result?.meta && typeof result.meta === "object" ? result.meta : {};
2211
+ await addSharedRoomNote(cwd, "Reviewer outcome overridden by the room", {
2212
+ actorId,
2213
+ actorName,
2214
+ type: "review-policy-override",
2215
+ meta: {
2216
+ agentId: String(meta.agentId || "").trim()
2217
+ }
2218
+ });
2219
+ return [
2220
+ "<b>Reviewer overridden</b>",
2221
+ "The room overrode the reviewer outcome.",
2222
+ "Execution may proceed."
2223
+ ].join("\n");
2224
+ }
2225
+
2226
+ if (intent.action === "switch-executor-to-reviewer") {
2227
+ if (!project?.enabled) {
2228
+ return "This project is not shared.";
2229
+ }
2230
+ const reviewer = chooseReviewerAgent(project);
2231
+ if (!reviewer) {
2232
+ return "<b>Executor switch</b>\nNo reviewer is assigned.";
2233
+ }
2234
+ const currentExecutor = chooseExecutorAgent(project, executor);
2235
+ if (currentExecutor?.id && currentExecutor.id !== reviewer.id) {
2236
+ await upsertSharedAgent(cwd, { ...currentExecutor, role: "standby" }, { actorId, actorName });
2237
+ }
2238
+ await upsertSharedAgent(cwd, { ...reviewer, role: "executor" }, { actorId, actorName });
2239
+ await addSharedRoomNote(cwd, `${reviewer.ownerName || reviewer.label || reviewer.ownerId || "Reviewer"} is now the executor`, {
2240
+ actorId,
2241
+ actorName,
2242
+ type: "executor-handoff-reassigned",
2243
+ meta: {
2244
+ currentAgentId: String(reviewer.id || "").trim()
2245
+ }
2246
+ });
2247
+ return [
2248
+ "<b>Executor switched</b>",
2249
+ `executor: <code>${escapeTelegramHtml(reviewer.ownerName || reviewer.label || reviewer.ownerId || reviewer.id || "reviewer")}</code>`,
2250
+ "The reviewer is now the selected executor."
2251
+ ].join("\n");
2252
+ }
2253
+
2132
2254
  if (intent.action === "project-status") {
2133
2255
  return formatTelegramSummaryMarkup({
2134
2256
  cwd,
@@ -2233,6 +2355,35 @@ class TelegramGateway {
2233
2355
  return lines.join("\n");
2234
2356
  }
2235
2357
 
2358
+ if (intent.action === "executor-reviewer-compare") {
2359
+ if (!project?.enabled) {
2360
+ return "This project is not shared.";
2361
+ }
2362
+ const comparison = summarizeExecutorReviewerArbitration(project, executor);
2363
+ if (!comparison.executorAgent && !comparison.reviewerAgent) {
2364
+ return "<b>Executor vs reviewer</b>\nNo executor or reviewer is assigned yet.";
2365
+ }
2366
+ if (!comparison.executorAgent) {
2367
+ return "<b>Executor vs reviewer</b>\nNo executor is assigned yet.";
2368
+ }
2369
+ if (!comparison.reviewerAgent) {
2370
+ return "<b>Executor vs reviewer</b>\nNo reviewer is assigned yet.";
2371
+ }
2372
+ return [
2373
+ "<b>Executor vs reviewer</b>",
2374
+ `executor: <code>${escapeTelegramHtml(comparison.executorAgent.ownerName || comparison.executorAgent.label || comparison.executorAgent.ownerId || comparison.executorAgent.id || "unknown")}</code>`,
2375
+ `executor runtime: <code>${escapeTelegramHtml(comparison.executorRuntime || "unknown")}</code>`,
2376
+ `reviewer: <code>${escapeTelegramHtml(comparison.reviewerAgent.ownerName || comparison.reviewerAgent.label || comparison.reviewerAgent.ownerId || comparison.reviewerAgent.id || "unknown")}</code>`,
2377
+ `reviewer runtime: <code>${escapeTelegramHtml(comparison.reviewerRuntime || "unknown")}</code>`,
2378
+ `alignment: <code>${escapeTelegramHtml(comparison.aligned ? "aligned" : comparison.split ? "split" : "unknown")}</code>`,
2379
+ comparison.split
2380
+ ? "These terminals are on different runtimes. Expect different opinions and use the reviewer outcome to arbitrate."
2381
+ : comparison.aligned
2382
+ ? "These terminals are on the same runtime. Disagreement is still possible, but it is less likely to come from model differences alone."
2383
+ : "One side is missing runtime identity, so Waterbrother cannot compare them yet."
2384
+ ].join("\n");
2385
+ }
2386
+
2236
2387
  if (intent.action === "agent-perspective") {
2237
2388
  if (!project?.enabled) {
2238
2389
  return "This project is not shared.";