@tritard/waterbrother 0.16.96 → 0.16.97

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.96",
3
+ "version": "0.16.97",
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
@@ -834,7 +834,7 @@ function parseTelegramStateIntent(text = "") {
834
834
  if (/\bwhat can i do here\b/.test(lower) || /\bhow do i use this room\b/.test(lower) || /\bhow do i use this chat\b/.test(lower) || /\bwhat do i do here\b/.test(lower)) {
835
835
  return { action: "room-guidance" };
836
836
  }
837
- if (/\bwho are the bots\b/.test(lower) || /\bwho are the agents\b/.test(lower) || /\bwhat bots are here\b/.test(lower) || /\bwhat agents are here\b/.test(lower)) {
837
+ if (/\bwho are the (?:bots|boys)\b/.test(lower) || /\bwho are the agents\b/.test(lower) || /\bwhat (?:bots|boys) are here\b/.test(lower) || /\bwhat agents are here\b/.test(lower)) {
838
838
  return { action: "agent-list" };
839
839
  }
840
840
  if (/\bwhich terminals are live\b/.test(lower) || /\bwhich bots are live\b/.test(lower) || /\bwho is live\b/.test(lower) || /\bwhat terminals are live\b/.test(lower)) {
@@ -54,11 +54,13 @@ function normalizeParticipant(participant = {}) {
54
54
  }
55
55
 
56
56
  function normalizeAgent(agent = {}) {
57
+ const rawLabel = String(agent.label || agent.name || "").trim();
58
+ const cleanLabel = rawLabel.replace(/\bundefined\b/ig, "").replace(/\s+/g, " ").trim();
57
59
  return {
58
60
  id: String(agent.id || "").trim(),
59
61
  ownerId: String(agent.ownerId || "").trim(),
60
62
  ownerName: String(agent.ownerName || "").trim(),
61
- label: String(agent.label || agent.name || "").trim(),
63
+ label: cleanLabel,
62
64
  surface: String(agent.surface || "").trim(),
63
65
  role: AGENT_ROLES.includes(String(agent.role || "").trim()) ? String(agent.role).trim() : "standby",
64
66
  provider: String(agent.provider || "").trim(),
@@ -71,6 +73,42 @@ function normalizeAgent(agent = {}) {
71
73
  };
72
74
  }
73
75
 
76
+ function isBrokenAgent(agent = {}) {
77
+ const ownerId = String(agent.ownerId || "").trim();
78
+ const ownerName = String(agent.ownerName || "").trim();
79
+ const label = String(agent.label || "").trim();
80
+ return !ownerId && !ownerName && !label;
81
+ }
82
+
83
+ function dedupeAgents(agents = []) {
84
+ const bySemanticKey = new Map();
85
+ for (const agent of agents) {
86
+ const normalized = normalizeAgent(agent);
87
+ if (!normalized.id || isBrokenAgent(normalized)) continue;
88
+ const semanticKey = [
89
+ String(normalized.ownerId || "").trim(),
90
+ String(normalized.ownerName || "").trim().toLowerCase(),
91
+ String(normalized.label || "").trim().toLowerCase(),
92
+ String(normalized.surface || "").trim().toLowerCase(),
93
+ String(normalized.role || "").trim().toLowerCase(),
94
+ String(normalized.provider || "").trim().toLowerCase(),
95
+ String(normalized.model || "").trim().toLowerCase(),
96
+ String(normalized.chatId || "").trim()
97
+ ].join("|");
98
+ const prior = bySemanticKey.get(semanticKey);
99
+ if (!prior) {
100
+ bySemanticKey.set(semanticKey, normalized);
101
+ continue;
102
+ }
103
+ const priorUpdated = Date.parse(String(prior.updatedAt || "").trim()) || 0;
104
+ const nextUpdated = Date.parse(String(normalized.updatedAt || "").trim()) || 0;
105
+ if (nextUpdated >= priorUpdated) {
106
+ bySemanticKey.set(semanticKey, normalized);
107
+ }
108
+ }
109
+ return [...bySemanticKey.values()];
110
+ }
111
+
74
112
  function areAgentsEquivalent(left = {}, right = {}) {
75
113
  return [
76
114
  "ownerId",
@@ -119,12 +157,8 @@ function buildProjectParticipants(project = {}) {
119
157
  }
120
158
 
121
159
  function buildProjectAgents(project = {}) {
122
- const existing = new Map(
123
- (Array.isArray(project.agents) ? project.agents : [])
124
- .map((agent) => normalizeAgent(agent))
125
- .filter((agent) => agent.id)
126
- .map((agent) => [agent.id, agent])
127
- );
160
+ const existingAgents = dedupeAgents(Array.isArray(project.agents) ? project.agents : []);
161
+ const existing = new Map(existingAgents.map((agent) => [agent.id, agent]));
128
162
  const next = [];
129
163
  const activeOperatorId = String(project.activeOperator?.id || "").trim();
130
164
  if (activeOperatorId) {
@@ -145,7 +179,7 @@ function buildProjectAgents(project = {}) {
145
179
  if (next.some((item) => item.id === agent.id)) continue;
146
180
  next.push(normalizeAgent(agent));
147
181
  }
148
- return next;
182
+ return dedupeAgents(next);
149
183
  }
150
184
 
151
185
  function memberRoleWeight(role = "") {
@@ -433,7 +467,12 @@ async function recordSharedProjectEvent(cwd, project, text, { type = "note", act
433
467
  export async function loadSharedProject(cwd) {
434
468
  try {
435
469
  const raw = await fs.readFile(sharedFilePath(cwd), "utf8");
436
- return normalizeSharedProject(JSON.parse(raw), cwd);
470
+ const parsed = JSON.parse(raw);
471
+ const normalized = normalizeSharedProject(parsed, cwd);
472
+ if (JSON.stringify(parsed) !== JSON.stringify(normalized)) {
473
+ await writeJsonAtomically(sharedFilePath(cwd), normalized);
474
+ }
475
+ return normalized;
437
476
  } catch (error) {
438
477
  if (error?.code === "ENOENT") return null;
439
478
  throw error;