@tritard/waterbrother 0.16.71 → 0.16.72

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.72",
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>",
@@ -1369,6 +1386,24 @@ class TelegramGateway {
1369
1386
  };
1370
1387
  }
1371
1388
 
1389
+ async maybeAnnounceRoomAgentEvents(message, project) {
1390
+ if (!this.isGroupChat(message) || !project?.enabled) return;
1391
+ if (String(project?.room?.chatId || "").trim() !== String(message?.chat?.id || "").trim()) return;
1392
+ const events = Array.isArray(project.recentEvents) ? project.recentEvents : [];
1393
+ const announced = new Set(Array.isArray(this.state.announcedEvents) ? this.state.announcedEvents : []);
1394
+ const pending = events
1395
+ .filter((event) => String(event?.type || "").trim() === "agent-upsert")
1396
+ .filter((event) => event?.id && !announced.has(String(event.id)))
1397
+ .slice(-3);
1398
+ if (!pending.length) return;
1399
+ for (const event of pending) {
1400
+ await this.sendMarkup(message.chat.id, buildTelegramAgentAnnouncementMarkup(event), message.message_id);
1401
+ announced.add(String(event.id));
1402
+ }
1403
+ this.state.announcedEvents = [...announced].slice(-100);
1404
+ await this.persistState();
1405
+ }
1406
+
1372
1407
  buildTrustedRoomIntroMarkup({ project, actor }) {
1373
1408
  const participants = listProjectParticipants(project);
1374
1409
  const agents = listProjectAgents(project);
@@ -2451,6 +2486,8 @@ class TelegramGateway {
2451
2486
  const peer = this.getPeerState(userId);
2452
2487
  const linkedSession = await loadSession(sessionId);
2453
2488
  const sessionCwd = linkedSession.cwd || this.cwd;
2489
+ const linkedProject = await loadSharedProject(sessionCwd).catch(() => null);
2490
+ await this.maybeAnnounceRoomAgentEvents(message, linkedProject);
2454
2491
 
2455
2492
  if (text === "/about") {
2456
2493
  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
  }