@tritard/waterbrother 0.16.41 → 0.16.42
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 -0
- package/package.json +1 -1
- package/src/cli.js +28 -1
- package/src/gateway.js +79 -1
- package/src/shared-project.js +11 -0
package/README.md
CHANGED
|
@@ -266,6 +266,7 @@ Shared project foundation is now live:
|
|
|
266
266
|
- inspect it with `waterbrother room status`
|
|
267
267
|
- control conversation vs execution with `waterbrother room mode chat|plan|execute`
|
|
268
268
|
- manage collaborators with `waterbrother room members`, `waterbrother room invites`, `waterbrother room invite`, `waterbrother room invite accept`, and `waterbrother room remove`
|
|
269
|
+
- inspect recent collaboration activity with `waterbrother room events`
|
|
269
270
|
- manage the shared backlog with `waterbrother room tasks`, `waterbrother room task add`, and `waterbrother room task move`
|
|
270
271
|
- assign, claim, and discuss shared work with `waterbrother room task assign`, `waterbrother room task claim`, `waterbrother room task comment`, and `waterbrother room task history`
|
|
271
272
|
- choose a shared execution preset with `waterbrother room runtime <profile>`
|
|
@@ -296,6 +297,7 @@ Current Telegram behavior:
|
|
|
296
297
|
- paired Telegram users drive the same live session and permissions as the terminal operator when the TUI bridge is attached
|
|
297
298
|
- Telegram now supports remote workspace control with `/cwd`, `/use <path>`, `/desktop`, and `/new-project <name>`
|
|
298
299
|
- shared projects now support `/room`, `/members`, `/invites`, `/tasks`, `/mode`, `/claim`, `/release`, `/invite`, `/accept-invite`, `/approve-invite`, `/reject-invite`, `/remove-member`, `/room-runtime`, and `/task ...` from Telegram
|
|
300
|
+
- Telegram now supports `/whoami` for Telegram-id and shared-room membership visibility, plus `/events` for recent shared-room activity
|
|
299
301
|
- `/room` now includes pending invite count plus task ownership summaries
|
|
300
302
|
- local `/status` now includes `sharedRoom` with pending invites, task ownership summary, and recent shared-room event activity
|
|
301
303
|
- the TUI now prints a small Roundtable event feed when new shared-room activity lands
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -81,6 +81,7 @@ import {
|
|
|
81
81
|
formatSharedProjectStatus,
|
|
82
82
|
getSharedTaskHistory,
|
|
83
83
|
getSharedProjectPaths,
|
|
84
|
+
listSharedEvents,
|
|
84
85
|
listSharedInvites,
|
|
85
86
|
listSharedMembers,
|
|
86
87
|
listSharedTasks,
|
|
@@ -178,6 +179,7 @@ const INTERACTIVE_COMMANDS = [
|
|
|
178
179
|
{ name: "/share-project", description: "Enable shared-project mode in the current cwd" },
|
|
179
180
|
{ name: "/unshare-project", description: "Disable shared-project mode in the current cwd" },
|
|
180
181
|
{ name: "/room", description: "Show shared room status for the current project" },
|
|
182
|
+
{ name: "/room events", description: "Show recent Roundtable room events" },
|
|
181
183
|
{ name: "/room members", description: "List shared-project members" },
|
|
182
184
|
{ name: "/room invites", description: "List pending shared-project invites" },
|
|
183
185
|
{ name: "/room add <id> [owner|editor|observer]", description: "Add or update a shared-project member" },
|
|
@@ -4047,6 +4049,22 @@ async function runRoomCommand(positional, { cwd = process.cwd(), asJson = false,
|
|
|
4047
4049
|
return;
|
|
4048
4050
|
}
|
|
4049
4051
|
|
|
4052
|
+
if (sub === "events") {
|
|
4053
|
+
const events = await listSharedEvents(cwd, 12);
|
|
4054
|
+
if (asJson) {
|
|
4055
|
+
printData({ ok: true, events }, true);
|
|
4056
|
+
return;
|
|
4057
|
+
}
|
|
4058
|
+
if (!events.length) {
|
|
4059
|
+
console.log("No recent shared-room events");
|
|
4060
|
+
return;
|
|
4061
|
+
}
|
|
4062
|
+
for (const event of events) {
|
|
4063
|
+
console.log(`${event.createdAt}\t${event.type}\t${event.actorName || event.actorId || "-"}\t${event.text}`);
|
|
4064
|
+
}
|
|
4065
|
+
return;
|
|
4066
|
+
}
|
|
4067
|
+
|
|
4050
4068
|
if (sub === "tasks") {
|
|
4051
4069
|
const tasks = await listSharedTasks(cwd);
|
|
4052
4070
|
if (asJson) {
|
|
@@ -4296,7 +4314,7 @@ async function runRoomCommand(positional, { cwd = process.cwd(), asJson = false,
|
|
|
4296
4314
|
return;
|
|
4297
4315
|
}
|
|
4298
4316
|
|
|
4299
|
-
throw new Error("Usage: waterbrother room status|members|invites|add <member-id> [owner|editor|observer] [display name]|invite <member-id> [owner|editor|observer] [display name]|invite approve <invite-id>|invite accept <invite-id>|invite reject <invite-id>|remove <member-id>|tasks|task add <text>|task assign <id> <member-id>|task claim <id>|task move <id> <open|active|blocked|done>|task comment <id> <text>|task history <id>|runtime [<name>|clear]|mode <chat|plan|execute>|claim|release");
|
|
4317
|
+
throw new Error("Usage: waterbrother room status|events|members|invites|add <member-id> [owner|editor|observer] [display name]|invite <member-id> [owner|editor|observer] [display name]|invite approve <invite-id>|invite accept <invite-id>|invite reject <invite-id>|remove <member-id>|tasks|task add <text>|task assign <id> <member-id>|task claim <id>|task move <id> <open|active|blocked|done>|task comment <id> <text>|task history <id>|runtime [<name>|clear]|mode <chat|plan|execute>|claim|release");
|
|
4300
4318
|
}
|
|
4301
4319
|
|
|
4302
4320
|
async function runMcpCommand(positional, runtime, { cwd, asJson = false } = {}) {
|
|
@@ -8241,6 +8259,15 @@ Be concrete about surfaces — name actual pages/flows. Choose the best stack fo
|
|
|
8241
8259
|
continue;
|
|
8242
8260
|
}
|
|
8243
8261
|
|
|
8262
|
+
if (line === "/room events") {
|
|
8263
|
+
try {
|
|
8264
|
+
await runRoomCommand(["room", "events"], { cwd: context.cwd, asJson: false, runtime: context.runtime, currentSession, agent, context });
|
|
8265
|
+
} catch (error) {
|
|
8266
|
+
console.log(`room events failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
8267
|
+
}
|
|
8268
|
+
continue;
|
|
8269
|
+
}
|
|
8270
|
+
|
|
8244
8271
|
if (line === "/room tasks") {
|
|
8245
8272
|
try {
|
|
8246
8273
|
await runRoomCommand(["room", "tasks"], { cwd: context.cwd, asJson: false, runtime: context.runtime, currentSession, agent, context });
|
package/src/gateway.js
CHANGED
|
@@ -8,7 +8,7 @@ import { createSession, listSessions, loadSession, saveSession } from "./session
|
|
|
8
8
|
import { DEFAULT_PENDING_PAIRING_TTL_MINUTES, loadGatewayBridge, loadGatewayState, prunePendingPairings, saveGatewayBridge, saveGatewayState } from "./gateway-state.js";
|
|
9
9
|
import { getGatewayStatus, getChannelSpec } from "./channels.js";
|
|
10
10
|
import { canonicalizeLoosePath } from "./path-utils.js";
|
|
11
|
-
import { acceptSharedInvite, addSharedTask, approveSharedInvite, assignSharedTask, claimSharedOperator, claimSharedTask, commentSharedTask, createSharedInvite, getSharedTaskHistory, listSharedInvites, listSharedTasks, loadSharedProject, moveSharedTask, rejectSharedInvite, releaseSharedOperator, setSharedRoom, setSharedRoomMode, setSharedRuntimeProfile, removeSharedMember } from "./shared-project.js";
|
|
11
|
+
import { acceptSharedInvite, addSharedTask, approveSharedInvite, assignSharedTask, claimSharedOperator, claimSharedTask, commentSharedTask, createSharedInvite, getSharedTaskHistory, listSharedEvents, listSharedInvites, listSharedTasks, loadSharedProject, moveSharedTask, rejectSharedInvite, releaseSharedOperator, setSharedRoom, setSharedRoomMode, setSharedRuntimeProfile, removeSharedMember } from "./shared-project.js";
|
|
12
12
|
import { buildSelfAwarenessManifest, formatAboutWaterbrother, formatSelfState, resolveLocalConceptQuestion } from "./self-awareness.js";
|
|
13
13
|
|
|
14
14
|
const execFileAsync = promisify(execFile);
|
|
@@ -27,9 +27,11 @@ const TELEGRAM_COMMANDS = [
|
|
|
27
27
|
{ command: "state", description: "Show current Waterbrother self-awareness state" },
|
|
28
28
|
{ command: "run", description: "Execute an explicit remote prompt" },
|
|
29
29
|
{ command: "status", description: "Show the linked remote session" },
|
|
30
|
+
{ command: "whoami", description: "Show your Telegram identity and room membership" },
|
|
30
31
|
{ command: "cwd", description: "Show the current remote working directory" },
|
|
31
32
|
{ command: "runtime", description: "Show active runtime status" },
|
|
32
33
|
{ command: "room", description: "Show shared room status" },
|
|
34
|
+
{ command: "events", description: "Show recent shared room events" },
|
|
33
35
|
{ command: "members", description: "List shared room members" },
|
|
34
36
|
{ command: "invites", description: "List pending shared room invites" },
|
|
35
37
|
{ command: "tasks", description: "List shared Roundtable tasks" },
|
|
@@ -204,12 +206,14 @@ function buildRemoteHelp() {
|
|
|
204
206
|
"<code>/state</code> show current Waterbrother self-awareness state",
|
|
205
207
|
"<code>/run <prompt></code> execute an explicit remote request",
|
|
206
208
|
"<code>/status</code> show the current linked remote session",
|
|
209
|
+
"<code>/whoami</code> show your Telegram identity and room membership",
|
|
207
210
|
"<code>/cwd</code> show the current remote working directory",
|
|
208
211
|
"<code>/use <path></code> switch the linked session to another directory",
|
|
209
212
|
"<code>/desktop</code> switch the linked session to <code>~/Desktop</code>",
|
|
210
213
|
"<code>/new-project <name></code> create a folder on Desktop and switch into it",
|
|
211
214
|
"<code>/runtime</code> show active provider/model/runtime state",
|
|
212
215
|
"<code>/room</code> show shared project room status",
|
|
216
|
+
"<code>/events</code> show recent shared room events",
|
|
213
217
|
"<code>/members</code> list shared project members",
|
|
214
218
|
"<code>/tasks</code> list shared project tasks",
|
|
215
219
|
"<code>/task add <text></code> add a shared Roundtable task",
|
|
@@ -388,6 +392,51 @@ function formatTelegramInvitesMarkup(invites = []) {
|
|
|
388
392
|
].join("\n");
|
|
389
393
|
}
|
|
390
394
|
|
|
395
|
+
function formatTelegramEventsMarkup(events = []) {
|
|
396
|
+
if (!events.length) {
|
|
397
|
+
return "<b>Recent room events</b>\n• none";
|
|
398
|
+
}
|
|
399
|
+
return [
|
|
400
|
+
"<b>Recent room events</b>",
|
|
401
|
+
...events.map((event) => {
|
|
402
|
+
const actor = String(event.actorName || event.actorId || "").trim();
|
|
403
|
+
const when = String(event.createdAt || "").trim();
|
|
404
|
+
return `• ${actor ? `${escapeTelegramHtml(actor)} — ` : ""}${escapeTelegramHtml(event.text || "")}${when ? `\n <code>${escapeTelegramHtml(when)}</code>` : ""}`;
|
|
405
|
+
})
|
|
406
|
+
].join("\n");
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function formatTelegramWhoamiMarkup({ message, member = null, invites = [], sharedEnabled = false }) {
|
|
410
|
+
const from = message?.from || {};
|
|
411
|
+
const userId = String(from.id || "").trim();
|
|
412
|
+
const username = String(from.username || "").trim();
|
|
413
|
+
const displayName = [from.first_name, from.last_name].filter(Boolean).join(" ").trim() || username || userId;
|
|
414
|
+
const pending = invites.filter((invite) => String(invite.memberId || "").trim() === userId);
|
|
415
|
+
const lines = [
|
|
416
|
+
"<b>Telegram identity</b>",
|
|
417
|
+
`name: <code>${escapeTelegramHtml(displayName)}</code>`,
|
|
418
|
+
`user id: <code>${escapeTelegramHtml(userId)}</code>`,
|
|
419
|
+
`username: <code>${escapeTelegramHtml(username || "none")}</code>`
|
|
420
|
+
];
|
|
421
|
+
if (!sharedEnabled) {
|
|
422
|
+
lines.push("shared room: <code>off</code>");
|
|
423
|
+
lines.push("This project is not shared yet. Start with <code>waterbrother project share</code>.");
|
|
424
|
+
return lines.join("\n");
|
|
425
|
+
}
|
|
426
|
+
lines.push(`shared member: <code>${member ? "yes" : "no"}</code>`);
|
|
427
|
+
if (member) {
|
|
428
|
+
lines.push(`role: <code>${escapeTelegramHtml(member.role || "editor")}</code>`);
|
|
429
|
+
}
|
|
430
|
+
if (pending.length) {
|
|
431
|
+
lines.push("<b>Pending invites you can accept</b>");
|
|
432
|
+
lines.push(...pending.map((invite) => `• <code>${escapeTelegramHtml(invite.id)}</code> <i>(${escapeTelegramHtml(invite.role || "editor")})</i>`));
|
|
433
|
+
lines.push("Use <code>/accept-invite <invite-id></code> to join the shared room.");
|
|
434
|
+
} else if (!member) {
|
|
435
|
+
lines.push("No pending Waterbrother room invite for this Telegram user id.");
|
|
436
|
+
}
|
|
437
|
+
return lines.join("\n");
|
|
438
|
+
}
|
|
439
|
+
|
|
391
440
|
function formatTelegramTasksMarkup(tasks = []) {
|
|
392
441
|
if (!tasks.length) {
|
|
393
442
|
return "<b>Shared tasks</b>\n• none";
|
|
@@ -1152,6 +1201,20 @@ class TelegramGateway {
|
|
|
1152
1201
|
return;
|
|
1153
1202
|
}
|
|
1154
1203
|
|
|
1204
|
+
if (text === "/whoami") {
|
|
1205
|
+
const { project } = await this.bindSharedRoomForMessage(message, sessionId);
|
|
1206
|
+
const invites = project?.enabled ? await listSharedInvites(sessionCwd).catch(() => []) : [];
|
|
1207
|
+
const member = Array.isArray(project?.members)
|
|
1208
|
+
? project.members.find((entry) => String(entry?.id || "").trim() === userId) || null
|
|
1209
|
+
: null;
|
|
1210
|
+
await this.sendMessage(
|
|
1211
|
+
message.chat.id,
|
|
1212
|
+
formatTelegramWhoamiMarkup({ message, member, invites, sharedEnabled: project?.enabled === true }),
|
|
1213
|
+
message.message_id
|
|
1214
|
+
);
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1155
1218
|
if (text === "/status") {
|
|
1156
1219
|
await this.sendMessage(
|
|
1157
1220
|
message.chat.id,
|
|
@@ -1195,6 +1258,21 @@ class TelegramGateway {
|
|
|
1195
1258
|
return;
|
|
1196
1259
|
}
|
|
1197
1260
|
|
|
1261
|
+
if (text === "/events") {
|
|
1262
|
+
const { session, project } = await this.bindSharedRoomForMessage(message, sessionId);
|
|
1263
|
+
if (!project?.enabled) {
|
|
1264
|
+
await this.sendMessage(message.chat.id, "This project is not shared.", message.message_id);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
try {
|
|
1268
|
+
const events = await listSharedEvents(session.cwd || this.cwd, 12);
|
|
1269
|
+
await this.sendMessage(message.chat.id, formatTelegramEventsMarkup(events), message.message_id);
|
|
1270
|
+
} catch (error) {
|
|
1271
|
+
await this.sendMessage(message.chat.id, escapeTelegramHtml(error instanceof Error ? error.message : String(error)), message.message_id);
|
|
1272
|
+
}
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1198
1276
|
if (text === "/tasks") {
|
|
1199
1277
|
const { session, project } = await this.bindSharedRoomForMessage(message, sessionId);
|
|
1200
1278
|
if (!project?.enabled) {
|
package/src/shared-project.js
CHANGED
|
@@ -456,6 +456,17 @@ export async function listSharedInvites(cwd) {
|
|
|
456
456
|
return project.pendingInvites || [];
|
|
457
457
|
}
|
|
458
458
|
|
|
459
|
+
export async function listSharedEvents(cwd, limit = 12) {
|
|
460
|
+
const project = await loadSharedProject(cwd);
|
|
461
|
+
requireSharedProject(project);
|
|
462
|
+
const max = Number.isFinite(Number(limit)) && Number(limit) > 0 ? Math.max(1, Math.floor(Number(limit))) : 12;
|
|
463
|
+
return Array.isArray(project.recentEvents)
|
|
464
|
+
? [...project.recentEvents]
|
|
465
|
+
.sort((a, b) => String(b.createdAt || "").localeCompare(String(a.createdAt || "")))
|
|
466
|
+
.slice(0, max)
|
|
467
|
+
: [];
|
|
468
|
+
}
|
|
469
|
+
|
|
459
470
|
export async function upsertSharedMember(cwd, member = {}, options = {}) {
|
|
460
471
|
const existing = await loadSharedProject(cwd);
|
|
461
472
|
requireOwner(existing, options.actorId);
|