@tritard/waterbrother 0.16.71 → 0.16.73

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tritard/waterbrother",
3
- "version": "0.16.71",
3
+ "version": "0.16.73",
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": {
@@ -23,6 +23,9 @@ function normalizeGatewayState(parsed = {}) {
23
23
  offset: Number.isFinite(Number(parsed?.offset)) ? Math.max(0, Math.floor(Number(parsed.offset))) : 0,
24
24
  peers: parsed?.peers && typeof parsed.peers === "object" ? parsed.peers : {},
25
25
  pendingPairings: parsed?.pendingPairings && typeof parsed.pendingPairings === "object" ? parsed.pendingPairings : {},
26
+ announcedEvents: Array.isArray(parsed?.announcedEvents)
27
+ ? parsed.announcedEvents.map((value) => String(value || "").trim()).filter(Boolean).slice(-100)
28
+ : [],
26
29
  continuations: Object.fromEntries(
27
30
  Object.entries(continuations).map(([key, item]) => [
28
31
  key,
@@ -135,7 +138,7 @@ export async function loadGatewayState(serviceId) {
135
138
  return normalizeGatewayState(JSON.parse(raw));
136
139
  } catch (error) {
137
140
  if (error?.code === "ENOENT") {
138
- return { offset: 0, peers: {}, pendingPairings: {}, continuations: {} };
141
+ return { offset: 0, peers: {}, pendingPairings: {}, announcedEvents: [], continuations: {} };
139
142
  }
140
143
  throw error;
141
144
  }
package/src/gateway.js CHANGED
@@ -334,6 +334,23 @@ function buildTelegramRoomGuidanceMarkup({ project = null, member = null, execut
334
334
  ].filter(Boolean).join("\n");
335
335
  }
336
336
 
337
+ function buildTelegramAgentAnnouncementMarkup(event = {}) {
338
+ const meta = event?.meta && typeof event.meta === "object" ? event.meta : {};
339
+ const ownerName = String(meta.ownerName || "").trim() || String(meta.ownerId || "").trim() || "unknown";
340
+ const label = String(meta.label || meta.agentId || "").trim() || "terminal";
341
+ const runtime = meta.provider && meta.model ? `${meta.provider}/${meta.model}` : "";
342
+ const title = meta.isNew ? "Roundtable terminal joined" : "Roundtable terminal updated";
343
+ return [
344
+ `<b>${escapeTelegramHtml(title)}</b>`,
345
+ `owner: <code>${escapeTelegramHtml(ownerName)}</code>`,
346
+ `terminal: <code>${escapeTelegramHtml(label)}</code>`,
347
+ `role: <code>${escapeTelegramHtml(String(meta.role || "standby"))}</code>`,
348
+ `surface: <code>${escapeTelegramHtml(String(meta.surface || "unknown"))}</code>`,
349
+ runtime ? `runtime: <code>${escapeTelegramHtml(runtime)}</code>` : "",
350
+ meta.runtimeProfile ? `runtime profile: <code>${escapeTelegramHtml(String(meta.runtimeProfile || ""))}</code>` : ""
351
+ ].filter(Boolean).join("\n");
352
+ }
353
+
337
354
  function formatGatewaySessionStatus({ sessionId, userId, username, cwd, runtimeProfile, provider, model }) {
338
355
  const bits = [
339
356
  "<b>Telegram remote session</b>",
@@ -565,7 +582,9 @@ function parseTelegramAgentIntent(text = "") {
565
582
  if (/^(how|what|why|when|where|who)\b/.test(lowered)) return null;
566
583
  const reviewPatterns = [
567
584
  /^(?:have|ask|use)\s+(.+?)['’]s\s+(?:bot|terminal)\s+(?:to\s+)?review(?:\s+this)?\s*$/i,
568
- /^(?:have|ask|use)\s+(.+?)\s+(?:bot|terminal)\s+(?:to\s+)?review(?:\s+this)?\s*$/i
585
+ /^(?:have|ask|use)\s+(.+?)\s+(?:bot|terminal)\s+(?:to\s+)?review(?:\s+this)?\s*$/i,
586
+ /^(.+?),\s*review(?:\s+this)?\s+with\s+(?:your|ur)\s+(?:bot|terminal)\s*$/i,
587
+ /^(.+?)\s+should\s+review(?:\s+this)?\s+with\s+(?:their|his|her)\s+(?:bot|terminal)\s*$/i
569
588
  ];
570
589
  for (const pattern of reviewPatterns) {
571
590
  const match = value.match(pattern);
@@ -580,7 +599,9 @@ function parseTelegramAgentIntent(text = "") {
580
599
  /^(?:have|ask|use)\s+(.+?)['’]s\s+(?:bot|terminal)\s+(?:to\s+)?(?:take execution|execute|handle execution|take this)\s*$/i,
581
600
  /^(?:have|ask|use)\s+(.+?)\s+(?:bot|terminal)\s+(?:to\s+)?(?:take execution|execute|handle execution|take this)\s*$/i,
582
601
  /^(.+?)['’]s\s+(?:bot|terminal)\s+should\s+(?:take execution|execute|handle execution)\s*$/i,
583
- /^(.+?)\s+(?:bot|terminal)\s+should\s+(?:take execution|execute|handle execution)\s*$/i
602
+ /^(.+?)\s+(?:bot|terminal)\s+should\s+(?:take execution|execute|handle execution)\s*$/i,
603
+ /^(.+?),\s*(?:take execution|execute|handle execution|take this)\s+with\s+(?:your|ur)\s+(?:bot|terminal)\s*$/i,
604
+ /^(.+?)\s+should\s+(?:take execution|execute|handle execution)\s+with\s+(?:their|his|her)\s+(?:bot|terminal)\s*$/i
584
605
  ];
585
606
  for (const pattern of executePatterns) {
586
607
  const match = value.match(pattern);
@@ -1369,6 +1390,24 @@ class TelegramGateway {
1369
1390
  };
1370
1391
  }
1371
1392
 
1393
+ async maybeAnnounceRoomAgentEvents(message, project) {
1394
+ if (!this.isGroupChat(message) || !project?.enabled) return;
1395
+ if (String(project?.room?.chatId || "").trim() !== String(message?.chat?.id || "").trim()) return;
1396
+ const events = Array.isArray(project.recentEvents) ? project.recentEvents : [];
1397
+ const announced = new Set(Array.isArray(this.state.announcedEvents) ? this.state.announcedEvents : []);
1398
+ const pending = events
1399
+ .filter((event) => String(event?.type || "").trim() === "agent-upsert")
1400
+ .filter((event) => event?.id && !announced.has(String(event.id)))
1401
+ .slice(-3);
1402
+ if (!pending.length) return;
1403
+ for (const event of pending) {
1404
+ await this.sendMarkup(message.chat.id, buildTelegramAgentAnnouncementMarkup(event), message.message_id);
1405
+ announced.add(String(event.id));
1406
+ }
1407
+ this.state.announcedEvents = [...announced].slice(-100);
1408
+ await this.persistState();
1409
+ }
1410
+
1372
1411
  buildTrustedRoomIntroMarkup({ project, actor }) {
1373
1412
  const participants = listProjectParticipants(project);
1374
1413
  const agents = listProjectAgents(project);
@@ -2451,6 +2490,8 @@ class TelegramGateway {
2451
2490
  const peer = this.getPeerState(userId);
2452
2491
  const linkedSession = await loadSession(sessionId);
2453
2492
  const sessionCwd = linkedSession.cwd || this.cwd;
2493
+ const linkedProject = await loadSharedProject(sessionCwd).catch(() => null);
2494
+ await this.maybeAnnounceRoomAgentEvents(message, linkedProject);
2454
2495
 
2455
2496
  if (text === "/about") {
2456
2497
  await this.sendMessage(
@@ -71,6 +71,22 @@ function normalizeAgent(agent = {}) {
71
71
  };
72
72
  }
73
73
 
74
+ function areAgentsEquivalent(left = {}, right = {}) {
75
+ return [
76
+ "ownerId",
77
+ "ownerName",
78
+ "label",
79
+ "surface",
80
+ "role",
81
+ "provider",
82
+ "model",
83
+ "runtimeProfile",
84
+ "sessionId",
85
+ "cwd",
86
+ "chatId"
87
+ ].every((key) => String(left?.[key] || "").trim() === String(right?.[key] || "").trim());
88
+ }
89
+
74
90
  function buildProjectParticipants(project = {}) {
75
91
  const members = Array.isArray(project.members) ? project.members.map(normalizeMember).filter((item) => item.id) : [];
76
92
  const memberIds = new Set(members.map((member) => member.id));
@@ -615,6 +631,10 @@ export async function upsertSharedAgent(cwd, agent = {}, options = {}) {
615
631
  }
616
632
  const agents = Array.isArray(existing.agents) ? [...existing.agents] : [];
617
633
  const index = agents.findIndex((item) => item.id === nextAgent.id);
634
+ const previousAgent = index >= 0 ? normalizeAgent(agents[index]) : null;
635
+ if (previousAgent && areAgentsEquivalent(previousAgent, nextAgent)) {
636
+ return existing;
637
+ }
618
638
  if (index >= 0) {
619
639
  agents[index] = normalizeAgent({ ...agents[index], ...nextAgent });
620
640
  } else {
@@ -631,8 +651,14 @@ export async function upsertSharedAgent(cwd, agent = {}, options = {}) {
631
651
  meta: {
632
652
  agentId: nextAgent.id,
633
653
  ownerId: nextAgent.ownerId,
654
+ ownerName: nextAgent.ownerName,
655
+ label: nextAgent.label,
634
656
  role: nextAgent.role,
635
- surface: nextAgent.surface
657
+ surface: nextAgent.surface,
658
+ provider: nextAgent.provider,
659
+ model: nextAgent.model,
660
+ runtimeProfile: nextAgent.runtimeProfile,
661
+ isNew: !previousAgent
636
662
  }
637
663
  })).project;
638
664
  }