@tritard/waterbrother 0.16.64 → 0.16.65

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.64",
3
+ "version": "0.16.65",
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": {
@@ -7,6 +7,8 @@ const SHARED_FILE = path.join(".waterbrother", "shared.json");
7
7
  const ROUNDTABLE_FILE = "ROUNDTABLE.md";
8
8
  const TASK_STATES = ["open", "active", "blocked", "done"];
9
9
  const RECENT_EVENT_LIMIT = 24;
10
+ const PARTICIPANT_ROLES = ["owner", "editor", "observer"];
11
+ const AGENT_ROLES = ["executor", "reviewer", "standby", "coordinator"];
10
12
 
11
13
  function makeId(prefix = "id") {
12
14
  return `${prefix}_${crypto.randomBytes(3).toString("hex")}`;
@@ -16,11 +18,117 @@ function normalizeMember(member = {}) {
16
18
  return {
17
19
  id: String(member.id || "").trim(),
18
20
  name: String(member.name || "").trim(),
19
- role: ["owner", "editor", "observer"].includes(String(member.role || "").trim()) ? String(member.role).trim() : "editor",
21
+ role: PARTICIPANT_ROLES.includes(String(member.role || "").trim()) ? String(member.role).trim() : "editor",
20
22
  paired: member.paired !== false
21
23
  };
22
24
  }
23
25
 
26
+ function inferParticipantChannels(memberId = "", participant = {}) {
27
+ const channels = participant?.channels && typeof participant.channels === "object" ? { ...participant.channels } : {};
28
+ const normalizedId = String(memberId || "").trim();
29
+ if (!normalizedId) return channels;
30
+ if (/^\d+$/.test(normalizedId)) {
31
+ channels.telegram = channels.telegram && typeof channels.telegram === "object"
32
+ ? { ...channels.telegram, userId: normalizedId }
33
+ : { userId: normalizedId };
34
+ } else if (normalizedId.startsWith("local:")) {
35
+ const localName = normalizedId.slice("local:".length).trim();
36
+ channels.local = channels.local && typeof channels.local === "object"
37
+ ? { ...channels.local, userId: normalizedId, username: channels.local.username || localName }
38
+ : { userId: normalizedId, username: localName };
39
+ }
40
+ return channels;
41
+ }
42
+
43
+ function normalizeParticipant(participant = {}) {
44
+ return {
45
+ id: String(participant.id || participant.memberId || "").trim(),
46
+ memberId: String(participant.memberId || participant.id || "").trim(),
47
+ kind: "human",
48
+ displayName: String(participant.displayName || participant.name || "").trim(),
49
+ role: PARTICIPANT_ROLES.includes(String(participant.role || "").trim()) ? String(participant.role).trim() : "editor",
50
+ trusted: participant.trusted !== false,
51
+ channels: participant.channels && typeof participant.channels === "object" ? { ...participant.channels } : {},
52
+ joinedAt: String(participant.joinedAt || new Date().toISOString()).trim()
53
+ };
54
+ }
55
+
56
+ function normalizeAgent(agent = {}) {
57
+ return {
58
+ id: String(agent.id || "").trim(),
59
+ ownerId: String(agent.ownerId || "").trim(),
60
+ label: String(agent.label || agent.name || "").trim(),
61
+ surface: String(agent.surface || "").trim(),
62
+ role: AGENT_ROLES.includes(String(agent.role || "").trim()) ? String(agent.role).trim() : "standby",
63
+ provider: String(agent.provider || "").trim(),
64
+ model: String(agent.model || "").trim(),
65
+ runtimeProfile: String(agent.runtimeProfile || "").trim(),
66
+ sessionId: String(agent.sessionId || "").trim(),
67
+ updatedAt: String(agent.updatedAt || new Date().toISOString()).trim()
68
+ };
69
+ }
70
+
71
+ function buildProjectParticipants(project = {}) {
72
+ const members = Array.isArray(project.members) ? project.members.map(normalizeMember).filter((item) => item.id) : [];
73
+ const memberIds = new Set(members.map((member) => member.id));
74
+ const existing = new Map(
75
+ (Array.isArray(project.participants) ? project.participants : [])
76
+ .map((participant) => normalizeParticipant(participant))
77
+ .filter((participant) => participant.id)
78
+ .map((participant) => [participant.id, participant])
79
+ );
80
+ const next = members.map((member) => {
81
+ const prior = existing.get(member.id) || {};
82
+ return normalizeParticipant({
83
+ ...prior,
84
+ id: member.id,
85
+ memberId: member.id,
86
+ displayName: prior.displayName || member.name || member.id,
87
+ role: member.role,
88
+ trusted: prior.trusted !== false && member.paired !== false,
89
+ channels: inferParticipantChannels(member.id, prior),
90
+ joinedAt: prior.joinedAt || project.createdAt || new Date().toISOString()
91
+ });
92
+ });
93
+ for (const participant of existing.values()) {
94
+ if (!participant.id) continue;
95
+ if (participant.memberId && !memberIds.has(participant.memberId)) continue;
96
+ if (next.some((item) => item.id === participant.id)) continue;
97
+ next.push(normalizeParticipant(participant));
98
+ }
99
+ return next;
100
+ }
101
+
102
+ function buildProjectAgents(project = {}) {
103
+ const existing = new Map(
104
+ (Array.isArray(project.agents) ? project.agents : [])
105
+ .map((agent) => normalizeAgent(agent))
106
+ .filter((agent) => agent.id)
107
+ .map((agent) => [agent.id, agent])
108
+ );
109
+ const next = [];
110
+ const activeOperatorId = String(project.activeOperator?.id || "").trim();
111
+ if (activeOperatorId) {
112
+ const inferredId = `agent:operator:${activeOperatorId}`;
113
+ const prior = existing.get(inferredId) || {};
114
+ next.push(normalizeAgent({
115
+ ...prior,
116
+ id: inferredId,
117
+ ownerId: activeOperatorId,
118
+ label: prior.label || `${project.activeOperator?.name || activeOperatorId} terminal`,
119
+ surface: prior.surface || (activeOperatorId.startsWith("local:") ? "local-tui" : String(project.room?.provider || "remote").trim() || "remote"),
120
+ role: "executor",
121
+ updatedAt: new Date().toISOString()
122
+ }));
123
+ }
124
+ for (const agent of existing.values()) {
125
+ if (!agent.id) continue;
126
+ if (next.some((item) => item.id === agent.id)) continue;
127
+ next.push(normalizeAgent(agent));
128
+ }
129
+ return next;
130
+ }
131
+
24
132
  function memberRoleWeight(role = "") {
25
133
  const normalized = String(role || "").trim().toLowerCase();
26
134
  if (normalized === "owner") return 3;
@@ -54,6 +162,8 @@ function normalizeSharedProject(project = {}, cwd = process.cwd()) {
54
162
  claimedAt: String(project.activeOperator.claimedAt || "").trim()
55
163
  }
56
164
  : null;
165
+ const participants = buildProjectParticipants({ ...project, members });
166
+ const agents = buildProjectAgents({ ...project, members, activeOperator });
57
167
 
58
168
  return {
59
169
  version: 1,
@@ -72,6 +182,8 @@ function normalizeSharedProject(project = {}, cwd = process.cwd()) {
72
182
  : "chat",
73
183
  runtimeProfile: String(project.runtimeProfile || "").trim(),
74
184
  members,
185
+ participants,
186
+ agents,
75
187
  tasks,
76
188
  pendingInvites,
77
189
  recentEvents,
@@ -899,6 +1011,8 @@ export function formatSharedProjectStatus(project) {
899
1011
  approvalPolicy: project.approvalPolicy,
900
1012
  activeOperator: project.activeOperator,
901
1013
  members: project.members,
1014
+ participants: project.participants,
1015
+ agents: project.agents,
902
1016
  pendingInvites: project.pendingInvites,
903
1017
  tasks: project.tasks,
904
1018
  recentEvents: project.recentEvents