@tritard/waterbrother 0.16.94 → 0.16.96

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 +71 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tritard/waterbrother",
3
- "version": "0.16.94",
3
+ "version": "0.16.96",
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
@@ -342,8 +342,10 @@ function buildTelegramRoomGuidanceMarkup({ project = null, member = null, execut
342
342
 
343
343
  function buildTelegramAgentAnnouncementMarkup(event = {}) {
344
344
  const meta = event?.meta && typeof event.meta === "object" ? event.meta : {};
345
- const ownerName = String(meta.ownerName || "").trim() || String(meta.ownerId || "").trim() || "unknown";
346
- const label = String(meta.label || meta.agentId || "").trim() || "terminal";
345
+ const rawOwnerName = String(meta.ownerName || "").trim() || String(meta.ownerId || "").trim();
346
+ const rawLabel = String(meta.label || meta.agentId || "").trim();
347
+ const ownerName = rawOwnerName && !/^unknown$/i.test(rawOwnerName) ? rawOwnerName : "this room";
348
+ const label = rawLabel && !/\bundefined\b/i.test(rawLabel) ? rawLabel : (ownerName !== "this room" ? `${ownerName} terminal` : "live terminal");
347
349
  const runtime = meta.provider && meta.model ? `${meta.provider}/${meta.model}` : "";
348
350
  const title = meta.isNew ? "Roundtable terminal joined" : "Roundtable terminal updated";
349
351
  const role = String(meta.role || "standby").trim() || "standby";
@@ -361,7 +363,7 @@ function buildTelegramAgentAnnouncementMarkup(event = {}) {
361
363
  runtime ? `runtime: <code>${escapeTelegramHtml(runtime)}</code>` : "",
362
364
  meta.runtimeProfile ? `runtime profile: <code>${escapeTelegramHtml(String(meta.runtimeProfile || ""))}</code>` : "",
363
365
  roleGuidance,
364
- "Examples: <code>which terminals are live?</code>, <code>have Austin's bot be reviewer</code>, <code>have Phillip's bot take execution</code>"
366
+ "Examples: <code>which terminals are live?</code>, <code>make this terminal the reviewer</code>, <code>make this terminal the executor</code>"
365
367
  ].filter(Boolean).join("\n");
366
368
  }
367
369
 
@@ -383,7 +385,27 @@ function listProjectParticipants(project) {
383
385
  }
384
386
 
385
387
  function listProjectAgents(project) {
386
- return Array.isArray(project?.agents) ? project.agents : [];
388
+ const agents = Array.isArray(project?.agents) ? project.agents : [];
389
+ const seen = new Set();
390
+ return agents.filter((agent) => {
391
+ const ownerId = String(agent?.ownerId || "").trim();
392
+ const ownerName = String(agent?.ownerName || "").trim();
393
+ const label = String(agent?.label || "").trim();
394
+ const runtime = agent?.provider && agent?.model ? `${agent.provider}/${agent.model}` : "";
395
+ if (!String(agent?.id || "").trim()) return false;
396
+ if (!ownerId && !ownerName && (!label || /\bundefined\b/i.test(label))) return false;
397
+ const key = [
398
+ ownerId,
399
+ ownerName.toLowerCase(),
400
+ label.replace(/\bundefined\b/ig, "").trim().toLowerCase(),
401
+ String(agent?.role || "").trim().toLowerCase(),
402
+ String(agent?.surface || "").trim().toLowerCase(),
403
+ runtime.toLowerCase()
404
+ ].join("|");
405
+ if (seen.has(key)) return false;
406
+ seen.add(key);
407
+ return true;
408
+ });
387
409
  }
388
410
 
389
411
  function findProjectParticipant(project, memberId = "") {
@@ -484,7 +506,9 @@ function formatBridgeHostLabel(host = {}) {
484
506
  const owner = String(host?.ownerName || host?.ownerId || "").trim();
485
507
  const label = String(host?.label || "").trim();
486
508
  const runtime = host?.provider && host?.model ? `${host.provider}/${host.model}` : "";
487
- return [owner || label, label && label !== owner ? `(${label})` : "", runtime ? `[${runtime}]` : ""].filter(Boolean).join(" ").trim();
509
+ const safeLabel = label && !/\bundefined\b/i.test(label) ? label : "";
510
+ const primary = owner || safeLabel || "live terminal";
511
+ return [primary, safeLabel && safeLabel !== owner ? `(${safeLabel})` : "", runtime ? `[${runtime}]` : ""].filter(Boolean).join(" ").trim();
488
512
  }
489
513
 
490
514
  function findLiveHostForAgent(hosts = [], agent = {}) {
@@ -786,6 +810,9 @@ function parseTelegramStateIntent(text = "") {
786
810
  const lower = value.toLowerCase();
787
811
  if (!value) return null;
788
812
 
813
+ if (/^(?:hi|hello|hey|yo|sup|suh|wazzup|whazzup|what'?s up|whats up|how are you)\b.*$/i.test(value)) {
814
+ return { action: "bot-greeting" };
815
+ }
789
816
  if (/^(?:are you|you(?:'re| are)?|wb|waterbrother)\s+(?:online|running|alive|there)\??$/.test(lower)
790
817
  || /^(?:you online|you running|you there)\??$/.test(lower)
791
818
  || /^(?:are you online|are you running|are you there)\??$/.test(lower)) {
@@ -795,7 +822,7 @@ function parseTelegramStateIntent(text = "") {
795
822
  if (/\bwhat project\b/.test(lower) || /\bwhich project\b/.test(lower) || /\bproject is this chat\b/.test(lower) || /\bchat bound to\b/.test(lower)) {
796
823
  return { action: "project-status" };
797
824
  }
798
- if (/\bwho 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)) {
825
+ 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)) {
799
826
  return { action: "room-members" };
800
827
  }
801
828
  if (/\bwhat mode\b/.test(lower) || /\broom mode\b/.test(lower) || /\bmode are we in\b/.test(lower)) {
@@ -1609,13 +1636,38 @@ class TelegramGateway {
1609
1636
  if (String(project?.room?.chatId || "").trim() !== String(message?.chat?.id || "").trim()) return;
1610
1637
  const events = Array.isArray(project.recentEvents) ? project.recentEvents : [];
1611
1638
  const announced = new Set(Array.isArray(this.state.announcedEvents) ? this.state.announcedEvents : []);
1639
+ const agents = listProjectAgents(project);
1640
+ const freshThresholdMs = 2 * 60 * 1000;
1641
+ const now = Date.now();
1612
1642
  const pending = events
1613
1643
  .filter((event) => String(event?.type || "").trim() === "agent-upsert")
1614
1644
  .filter((event) => event?.id && !announced.has(String(event.id)))
1645
+ .filter((event) => {
1646
+ const createdAtMs = Date.parse(String(event?.createdAt || "").trim());
1647
+ return Number.isFinite(createdAtMs) ? now - createdAtMs <= freshThresholdMs : true;
1648
+ })
1615
1649
  .slice(-3);
1616
1650
  if (!pending.length) return;
1617
1651
  for (const event of pending) {
1618
- await this.sendMarkup(message.chat.id, buildTelegramAgentAnnouncementMarkup(event), message.message_id);
1652
+ const meta = event?.meta && typeof event.meta === "object" ? { ...event.meta } : {};
1653
+ const agent = agents.find((item) => String(item?.id || "").trim() === String(meta.agentId || "").trim()) || null;
1654
+ const enrichedEvent = agent
1655
+ ? {
1656
+ ...event,
1657
+ meta: {
1658
+ ...meta,
1659
+ ownerId: String(meta.ownerId || agent.ownerId || "").trim(),
1660
+ ownerName: String(meta.ownerName || agent.ownerName || "").trim(),
1661
+ label: String(meta.label || agent.label || "").trim(),
1662
+ role: String(meta.role || agent.role || "").trim(),
1663
+ surface: String(meta.surface || agent.surface || "").trim(),
1664
+ provider: String(meta.provider || agent.provider || "").trim(),
1665
+ model: String(meta.model || agent.model || "").trim(),
1666
+ runtimeProfile: String(meta.runtimeProfile || agent.runtimeProfile || "").trim()
1667
+ }
1668
+ }
1669
+ : event;
1670
+ await this.sendMarkup(message.chat.id, buildTelegramAgentAnnouncementMarkup(enrichedEvent), message.message_id);
1619
1671
  announced.add(String(event.id));
1620
1672
  }
1621
1673
  this.state.announcedEvents = [...announced].slice(-100);
@@ -2208,6 +2260,18 @@ class TelegramGateway {
2208
2260
  return lines.join("\n");
2209
2261
  }
2210
2262
 
2263
+ if (intent.action === "bot-greeting") {
2264
+ return [
2265
+ "<b>Waterbrother</b>",
2266
+ "I’m here.",
2267
+ `room mode: <code>${escapeTelegramHtml(project?.enabled ? (project.roomMode || "chat") : "off")}</code>`,
2268
+ project?.activeOperator?.id
2269
+ ? `active operator: <code>${escapeTelegramHtml(project.activeOperator.name || project.activeOperator.id)}</code>`
2270
+ : "active operator: <code>none</code>",
2271
+ "Ask a question, discuss a plan, or give me a task when you want me to act."
2272
+ ].join("\n");
2273
+ }
2274
+
2211
2275
  if (intent.action === "accept-reviewer-concerns") {
2212
2276
  if (!project?.enabled) {
2213
2277
  return "This project is not shared.";