@tritard/waterbrother 0.16.63 → 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 +1 -1
- package/src/gateway-state.js +14 -1
- package/src/gateway.js +32 -4
- package/src/shared-project.js +115 -1
package/package.json
CHANGED
package/src/gateway-state.js
CHANGED
|
@@ -18,11 +18,24 @@ function gatewayBridgePath(serviceId) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function normalizeGatewayState(parsed = {}) {
|
|
21
|
+
const continuations = parsed?.continuations && typeof parsed.continuations === "object" ? parsed.continuations : {};
|
|
21
22
|
return {
|
|
22
23
|
offset: Number.isFinite(Number(parsed?.offset)) ? Math.max(0, Math.floor(Number(parsed.offset))) : 0,
|
|
23
24
|
peers: parsed?.peers && typeof parsed.peers === "object" ? parsed.peers : {},
|
|
24
25
|
pendingPairings: parsed?.pendingPairings && typeof parsed.pendingPairings === "object" ? parsed.pendingPairings : {},
|
|
25
|
-
continuations:
|
|
26
|
+
continuations: Object.fromEntries(
|
|
27
|
+
Object.entries(continuations).map(([key, item]) => [
|
|
28
|
+
key,
|
|
29
|
+
{
|
|
30
|
+
chatId: String(item?.chatId || "").trim(),
|
|
31
|
+
userId: String(item?.userId || "").trim(),
|
|
32
|
+
lastPrompt: String(item?.lastPrompt || "").trim(),
|
|
33
|
+
kind: String(item?.kind || "follow-up").trim() || "follow-up",
|
|
34
|
+
source: String(item?.source || "assistant-question").trim() || "assistant-question",
|
|
35
|
+
expiresAt: String(item?.expiresAt || "").trim()
|
|
36
|
+
}
|
|
37
|
+
])
|
|
38
|
+
)
|
|
26
39
|
};
|
|
27
40
|
}
|
|
28
41
|
|
package/src/gateway.js
CHANGED
|
@@ -754,6 +754,16 @@ function classifyTelegramGroupIntent(text = "") {
|
|
|
754
754
|
return { kind: "chat", reason: "default non-execution" };
|
|
755
755
|
}
|
|
756
756
|
|
|
757
|
+
function buildContinuationPrompt(text = "", continuation = null) {
|
|
758
|
+
const body = String(text || "").trim();
|
|
759
|
+
if (!continuation?.lastPrompt) return body;
|
|
760
|
+
return [
|
|
761
|
+
`Continue the active Telegram follow-up.`,
|
|
762
|
+
`Your last question/request to this user was: ${continuation.lastPrompt}`,
|
|
763
|
+
`User reply: ${body}`
|
|
764
|
+
].join("\n");
|
|
765
|
+
}
|
|
766
|
+
|
|
757
767
|
function suggestTaskText(text = "") {
|
|
758
768
|
return String(text || "")
|
|
759
769
|
.replace(/^@[\w_]+\s*/g, "")
|
|
@@ -991,6 +1001,15 @@ class TelegramGateway {
|
|
|
991
1001
|
return Number.isFinite(expiresAtMs) && expiresAtMs > Date.now();
|
|
992
1002
|
}
|
|
993
1003
|
|
|
1004
|
+
getPendingContinuation(message) {
|
|
1005
|
+
const key = continuationKey(message?.chat?.id, message?.from?.id);
|
|
1006
|
+
const item = this.state?.continuations?.[key];
|
|
1007
|
+
if (!item) return null;
|
|
1008
|
+
const expiresAtMs = Date.parse(String(item.expiresAt || ""));
|
|
1009
|
+
if (!Number.isFinite(expiresAtMs) || expiresAtMs <= Date.now()) return null;
|
|
1010
|
+
return item;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
994
1013
|
clearContinuation(message) {
|
|
995
1014
|
const key = continuationKey(message?.chat?.id, message?.from?.id);
|
|
996
1015
|
if (this.state?.continuations?.[key]) {
|
|
@@ -1010,6 +1029,8 @@ class TelegramGateway {
|
|
|
1010
1029
|
chatId: String(message?.chat?.id || "").trim(),
|
|
1011
1030
|
userId: String(message?.from?.id || "").trim(),
|
|
1012
1031
|
lastPrompt: body.slice(0, 400),
|
|
1032
|
+
kind: "follow-up",
|
|
1033
|
+
source: "assistant-question",
|
|
1013
1034
|
expiresAt: new Date(Date.now() + TELEGRAM_CONTINUATION_TTL_MS).toISOString()
|
|
1014
1035
|
};
|
|
1015
1036
|
await this.persistState();
|
|
@@ -1102,7 +1123,8 @@ class TelegramGateway {
|
|
|
1102
1123
|
project.activeOperator?.id
|
|
1103
1124
|
? `active operator: <code>${escapeTelegramHtml(project.activeOperator.name || project.activeOperator.id)}</code>`
|
|
1104
1125
|
: "active operator: <code>none</code>",
|
|
1105
|
-
"
|
|
1126
|
+
"As observer, you can ask questions, discuss plans, and review work here.",
|
|
1127
|
+
"An owner can promote you to editor or owner conversationally.",
|
|
1106
1128
|
"Examples: <code>what project is this chat bound to?</code>, <code>who is in the room?</code>, <code>what can I do here?</code>"
|
|
1107
1129
|
].join("\n");
|
|
1108
1130
|
}
|
|
@@ -1985,9 +2007,10 @@ class TelegramGateway {
|
|
|
1985
2007
|
if (trustedOnboarding) {
|
|
1986
2008
|
await this.sendMarkup(message.chat.id, this.buildTrustedRoomIntroMarkup(trustedOnboarding), message.message_id);
|
|
1987
2009
|
}
|
|
1988
|
-
this.clearContinuation(message);
|
|
1989
2010
|
|
|
1990
2011
|
const text = this.normalizeTelegramCommandText(String(message.text || "").trim());
|
|
2012
|
+
const continuation = this.getPendingContinuation(message);
|
|
2013
|
+
this.clearContinuation(message);
|
|
1991
2014
|
const userId = String(message.from.id);
|
|
1992
2015
|
|
|
1993
2016
|
if (text === "/help" || text === "/start") {
|
|
@@ -2706,9 +2729,14 @@ Ask them to run <code>/whoami</code> and then <code>/accept-invite ${escapeTeleg
|
|
|
2706
2729
|
}
|
|
2707
2730
|
|
|
2708
2731
|
const isExplicitRun = text.startsWith("/run ");
|
|
2709
|
-
const
|
|
2732
|
+
const rawPromptText = this.stripBotMention(isExplicitRun ? text.replace("/run", "").trim() : text);
|
|
2733
|
+
const promptText = continuation ? buildContinuationPrompt(rawPromptText, continuation) : rawPromptText;
|
|
2710
2734
|
const groupIntent = this.isGroupChat(message)
|
|
2711
|
-
? (isExplicitRun
|
|
2735
|
+
? (isExplicitRun
|
|
2736
|
+
? { kind: "execution", reason: "explicit /run" }
|
|
2737
|
+
: continuation
|
|
2738
|
+
? { kind: "execution", reason: "follow-up continuation" }
|
|
2739
|
+
: classifyTelegramGroupIntent(rawPromptText))
|
|
2712
2740
|
: { kind: "execution", reason: "private chat" };
|
|
2713
2741
|
const shouldExecutePrompt = !this.isGroupChat(message) || groupIntent.kind === "execution";
|
|
2714
2742
|
const sharedBinding = await this.bindSharedRoomForMessage(message, sessionId);
|
package/src/shared-project.js
CHANGED
|
@@ -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:
|
|
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
|