@tritard/waterbrother 0.16.49 → 0.16.51
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/README.md +2 -1
- package/package.json +1 -1
- package/src/gateway.js +122 -7
package/README.md
CHANGED
|
@@ -297,7 +297,8 @@ Current Telegram behavior:
|
|
|
297
297
|
- pending pairings are explicit and expire automatically after 12 hours unless approved
|
|
298
298
|
- paired Telegram users drive the same live session and permissions as the terminal operator when the TUI bridge is attached
|
|
299
299
|
- Telegram now supports remote workspace control with `/project`, `/project use <path|name>`, `/project new <name>`, `/project share`, plus the lower-level `/cwd`, `/use <path>`, `/desktop`, and `/new-project <name>` commands
|
|
300
|
-
- Telegram owners can pair visible chat participants conversationally or with `/pair <user-id|@username>`,
|
|
300
|
+
- Telegram owners can pair visible chat participants conversationally or with `/pair <user-id|@username>`, and owner requests like “add Austin as editor” now pair the person if needed and add them to the shared project directly
|
|
301
|
+
- Telegram also answers live state questions from room state now, including prompts like “is Austin paired?”, “is Austin in the project?”, “who is in the room?”, and “what project is this chat bound to?”
|
|
301
302
|
- Telegram owners can also use conversational project and member requests like “share this project in this chat”, “switch to TelegramTest”, or “add Austin as editor”, and Waterbrother routes those through the same project and shared-room actions
|
|
302
303
|
- shared projects now support `/room`, `/events`, `/members`, `/invites`, `/whoami`, `/people`, `/tasks`, `/mode`, `/claim`, `/release`, `/invite`, `/accept-invite`, `/approve-invite`, `/reject-invite`, `/remove-member`, `/room-runtime`, and `/task ...` from Telegram
|
|
303
304
|
- Telegram now supports `/whoami` for Telegram-id and shared-room membership visibility, plus `/events` for recent shared-room activity
|
package/package.json
CHANGED
package/src/gateway.js
CHANGED
|
@@ -379,6 +379,37 @@ function parseTelegramPairingIntent(text = "") {
|
|
|
379
379
|
return null;
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
+
function parseTelegramStateIntent(text = "") {
|
|
383
|
+
const value = normalizeTelegramProjectIntentText(text);
|
|
384
|
+
const lower = value.toLowerCase();
|
|
385
|
+
if (!value) return null;
|
|
386
|
+
|
|
387
|
+
if (/\bwhat project\b/.test(lower) || /\bwhich project\b/.test(lower) || /\bproject is this chat\b/.test(lower) || /\bchat bound to\b/.test(lower)) {
|
|
388
|
+
return { action: "project-status" };
|
|
389
|
+
}
|
|
390
|
+
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)) {
|
|
391
|
+
return { action: "room-members" };
|
|
392
|
+
}
|
|
393
|
+
if (/\bwhat mode\b/.test(lower) || /\broom mode\b/.test(lower) || /\bmode are we in\b/.test(lower)) {
|
|
394
|
+
return { action: "room-mode" };
|
|
395
|
+
}
|
|
396
|
+
if (/\bwho can act\b/.test(lower) || /\bwho is the active operator\b/.test(lower) || /\bwho has the floor\b/.test(lower) || /\bwho claimed\b/.test(lower)) {
|
|
397
|
+
return { action: "active-operator" };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const pairedMatch = value.match(/^(?:is|has)\s+(.+?)\s+paired(?:\s+now)?\??$/i);
|
|
401
|
+
if (pairedMatch?.[1]) {
|
|
402
|
+
return { action: "pairing-status", target: pairedMatch[1].trim() };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const memberMatch = value.match(/^(?:is|has)\s+(.+?)\s+(?:in the project|in the room|a member)(?:\s+now)?\??$/i);
|
|
406
|
+
if (memberMatch?.[1]) {
|
|
407
|
+
return { action: "member-status", target: memberMatch[1].trim() };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
|
|
382
413
|
async function buildTelegramSelfAwarenessMarkup({ cwd, runtime, currentSession, kind = "about" }) {
|
|
383
414
|
const manifest = await buildSelfAwarenessManifest({ cwd, runtime, currentSession });
|
|
384
415
|
if (kind === "state") {
|
|
@@ -1022,21 +1053,92 @@ class TelegramGateway {
|
|
|
1022
1053
|
markup: `Updated <code>${escapeTelegramHtml(target.displayName || target.userId)}</code> to <code>${escapeTelegramHtml(intent.role)}</code> in this shared project.`
|
|
1023
1054
|
};
|
|
1024
1055
|
}
|
|
1025
|
-
const
|
|
1056
|
+
const known = this.listKnownChatPeople(message);
|
|
1057
|
+
const knownPerson = known.find((person) => String(person.userId || "").trim() === target.userId) || null;
|
|
1058
|
+
let pairedNow = false;
|
|
1059
|
+
if (!knownPerson?.paired) {
|
|
1060
|
+
await this.pairKnownTelegramUser(message, target.userId, { skipOwnerCheck: true });
|
|
1061
|
+
pairedNow = true;
|
|
1062
|
+
}
|
|
1063
|
+
const nextProject = await upsertSharedMember(
|
|
1026
1064
|
session.cwd || this.cwd,
|
|
1027
|
-
{ id: target.userId,
|
|
1065
|
+
{ id: target.userId, name: target.displayName || target.userId, role: intent.role, paired: true },
|
|
1028
1066
|
{ actorId, actorName }
|
|
1029
1067
|
);
|
|
1030
1068
|
return {
|
|
1031
|
-
kind: "
|
|
1032
|
-
project:
|
|
1033
|
-
markup:
|
|
1069
|
+
kind: "member",
|
|
1070
|
+
project: nextProject,
|
|
1071
|
+
markup: `${pairedNow ? `Paired <code>${escapeTelegramHtml(target.displayName || target.userId)}</code> <i>(${escapeTelegramHtml(target.userId)})</i> and ` : ""}added <code>${escapeTelegramHtml(target.displayName || target.userId)}</code> to this shared project as <code>${escapeTelegramHtml(intent.role)}</code>.`
|
|
1034
1072
|
};
|
|
1035
1073
|
}
|
|
1036
1074
|
|
|
1037
|
-
async
|
|
1075
|
+
async handleStateIntent(message, sessionId, intent) {
|
|
1076
|
+
const { session, project } = await this.bindSharedRoomForMessage(message, sessionId);
|
|
1077
|
+
const cwd = session.cwd || this.cwd;
|
|
1078
|
+
const known = this.listKnownChatPeople(message);
|
|
1079
|
+
|
|
1080
|
+
if (intent.action === "project-status") {
|
|
1081
|
+
return formatTelegramProjectMarkup({
|
|
1082
|
+
cwd,
|
|
1083
|
+
project,
|
|
1084
|
+
chatId: String(message.chat.id),
|
|
1085
|
+
title: String(message.chat.title || "").trim()
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
if (intent.action === "room-members") {
|
|
1090
|
+
return formatTelegramMembersMarkup(project);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
if (intent.action === "room-mode") {
|
|
1094
|
+
return project?.enabled
|
|
1095
|
+
? `<b>Shared room mode</b>\n<code>${escapeTelegramHtml(project.roomMode || "chat")}</code>`
|
|
1096
|
+
: "This project is not shared.";
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
if (intent.action === "active-operator") {
|
|
1100
|
+
if (!project?.enabled) {
|
|
1101
|
+
return "This project is not shared.";
|
|
1102
|
+
}
|
|
1103
|
+
return project.activeOperator?.id
|
|
1104
|
+
? `<b>Active operator</b>\n<code>${escapeTelegramHtml(project.activeOperator.name || project.activeOperator.id)}</code>`
|
|
1105
|
+
: "<b>Active operator</b>\nnone";
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
if (intent.action === "pairing-status") {
|
|
1109
|
+
const person = this.resolveKnownPerson(message, intent.target);
|
|
1110
|
+
const match = known.find((entry) => String(entry.userId || "").trim() === person.userId) || null;
|
|
1111
|
+
return [
|
|
1112
|
+
`<b>Telegram pairing</b>`,
|
|
1113
|
+
`person: <code>${escapeTelegramHtml(match?.displayName || person.displayName || person.userId)}</code>`,
|
|
1114
|
+
`user id: <code>${escapeTelegramHtml(person.userId)}</code>`,
|
|
1115
|
+
`paired: <code>${escapeTelegramHtml(match?.paired ? "yes" : "no")}</code>`
|
|
1116
|
+
].join("\n");
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
if (intent.action === "member-status") {
|
|
1120
|
+
if (!project?.enabled) {
|
|
1121
|
+
return "This project is not shared.";
|
|
1122
|
+
}
|
|
1123
|
+
const person = this.resolveKnownPerson(message, intent.target);
|
|
1124
|
+
const member = Array.isArray(project.members)
|
|
1125
|
+
? project.members.find((entry) => String(entry?.id || "").trim() === person.userId) || null
|
|
1126
|
+
: null;
|
|
1127
|
+
return [
|
|
1128
|
+
`<b>Shared-project membership</b>`,
|
|
1129
|
+
`person: <code>${escapeTelegramHtml(person.displayName || person.userId)}</code>`,
|
|
1130
|
+
`user id: <code>${escapeTelegramHtml(person.userId)}</code>`,
|
|
1131
|
+
`member: <code>${escapeTelegramHtml(member ? "yes" : "no")}</code>`,
|
|
1132
|
+
member?.role ? `role: <code>${escapeTelegramHtml(member.role)}</code>` : ""
|
|
1133
|
+
].filter(Boolean).join("\n");
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
return null;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
async pairKnownTelegramUser(message, target, { skipOwnerCheck = false } = {}) {
|
|
1038
1140
|
const { session, project } = await this.bindSharedRoomForMessage(message, await this.ensurePeerSession(message));
|
|
1039
|
-
if (project?.enabled) {
|
|
1141
|
+
if (project?.enabled && !skipOwnerCheck) {
|
|
1040
1142
|
const owner = Array.isArray(project.members)
|
|
1041
1143
|
? project.members.find((entry) => String(entry?.id || "").trim() === String(message?.from?.id || "").trim())
|
|
1042
1144
|
: null;
|
|
@@ -2304,6 +2406,19 @@ Ask them to run <code>/whoami</code> and then <code>/accept-invite ${escapeTeleg
|
|
|
2304
2406
|
? (isExplicitRun ? { kind: "execution", reason: "explicit /run" } : classifyTelegramGroupIntent(promptText))
|
|
2305
2407
|
: { kind: "execution", reason: "private chat" };
|
|
2306
2408
|
const sharedBinding = await this.bindSharedRoomForMessage(message, sessionId);
|
|
2409
|
+
const stateIntent = parseTelegramStateIntent(promptText);
|
|
2410
|
+
if (stateIntent) {
|
|
2411
|
+
try {
|
|
2412
|
+
const markup = await this.handleStateIntent(message, sessionId, stateIntent);
|
|
2413
|
+
if (markup) {
|
|
2414
|
+
await this.sendMarkup(message.chat.id, markup, message.message_id);
|
|
2415
|
+
return;
|
|
2416
|
+
}
|
|
2417
|
+
} catch (error) {
|
|
2418
|
+
await this.sendMessage(message.chat.id, escapeTelegramHtml(error instanceof Error ? error.message : String(error)), message.message_id);
|
|
2419
|
+
return;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2307
2422
|
const pairingIntent = parseTelegramPairingIntent(promptText);
|
|
2308
2423
|
if (pairingIntent) {
|
|
2309
2424
|
try {
|