@tritard/waterbrother 0.16.68 → 0.16.69

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/gateway.js +90 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tritard/waterbrother",
3
- "version": "0.16.68",
3
+ "version": "0.16.69",
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
@@ -9,7 +9,7 @@ import { DEFAULT_PENDING_PAIRING_TTL_MINUTES, loadGatewayBridge, loadGatewayStat
9
9
  import { getGatewayStatus, getChannelSpec } from "./channels.js";
10
10
  import { getConfigPath, loadConfigLayers, saveConfig } from "./config.js";
11
11
  import { canonicalizeLoosePath } from "./path-utils.js";
12
- import { acceptSharedInvite, addSharedTask, approveSharedInvite, assignSharedTask, claimSharedOperator, claimSharedTask, commentSharedTask, createSharedInvite, enableSharedProject, getSharedTaskHistory, listSharedEvents, listSharedInvites, listSharedTasks, loadSharedProject, moveSharedTask, rejectSharedInvite, releaseSharedOperator, setSharedRoom, setSharedRoomMode, setSharedRuntimeProfile, removeSharedMember, upsertSharedMember } from "./shared-project.js";
12
+ import { acceptSharedInvite, addSharedTask, approveSharedInvite, assignSharedTask, claimSharedOperator, claimSharedTask, commentSharedTask, createSharedInvite, enableSharedProject, getSharedTaskHistory, listSharedEvents, listSharedInvites, listSharedTasks, loadSharedProject, moveSharedTask, rejectSharedInvite, releaseSharedOperator, setSharedRoom, setSharedRoomMode, setSharedRuntimeProfile, removeSharedMember, upsertSharedAgent, upsertSharedMember } from "./shared-project.js";
13
13
  import { buildSelfAwarenessManifest, formatAboutWaterbrother, formatSelfState, resolveLocalConceptQuestion } from "./self-awareness.js";
14
14
 
15
15
  const execFileAsync = promisify(execFile);
@@ -340,6 +340,23 @@ function chooseExecutorAgent(project, fallbackExecutor = {}) {
340
340
  return null;
341
341
  }
342
342
 
343
+ function memberRoleWeight(role = "") {
344
+ const normalized = String(role || "").trim().toLowerCase();
345
+ if (normalized === "owner") return 3;
346
+ if (normalized === "editor") return 2;
347
+ if (normalized === "observer") return 1;
348
+ return 0;
349
+ }
350
+
351
+ function memberHasAtLeastRole(project, memberId = "", role = "editor") {
352
+ const normalizedId = String(memberId || "").trim();
353
+ if (!normalizedId) return false;
354
+ const member = Array.isArray(project?.members)
355
+ ? project.members.find((entry) => String(entry?.id || "").trim() === normalizedId) || null
356
+ : null;
357
+ return memberRoleWeight(member?.role) >= memberRoleWeight(role);
358
+ }
359
+
343
360
  function formatAgentLabel(agent = {}) {
344
361
  const label = String(agent?.label || agent?.name || "").trim();
345
362
  const role = String(agent?.role || "").trim();
@@ -479,6 +496,33 @@ function parseTelegramPairingIntent(text = "") {
479
496
  return null;
480
497
  }
481
498
 
499
+ function parseTelegramAgentIntent(text = "") {
500
+ const value = normalizeTelegramProjectIntentText(text);
501
+ const lowered = value.toLowerCase();
502
+ if (!value) return null;
503
+ if (/^(how|what|why|when|where|who)\b/.test(lowered)) return null;
504
+ const roleMatch = lowered.match(/\b(executor|reviewer|standby)\b/);
505
+ const role = roleMatch?.[1] || "";
506
+ if (!role) return null;
507
+
508
+ const patterns = [
509
+ /^(?:have|make|set)\s+(.+?)['’]s\s+(?:bot|terminal)\s+(?:be|as|to)?\s*(?:the\s+)?(executor|reviewer|standby)\s*$/i,
510
+ /^(?:have|make|set)\s+(.+?)\s+(?:bot|terminal)\s+(?:be|as|to)?\s*(?:the\s+)?(executor|reviewer|standby)\s*$/i,
511
+ /^(.+?)['’]s\s+(?:bot|terminal)\s+should\s+(?:be\s+)?(?:the\s+)?(executor|reviewer|standby)\s*$/i,
512
+ /^(.+?)\s+(?:bot|terminal)\s+should\s+(?:be\s+)?(?:the\s+)?(executor|reviewer|standby)\s*$/i
513
+ ];
514
+ for (const pattern of patterns) {
515
+ const match = value.match(pattern);
516
+ if (!match) continue;
517
+ const target = String(match[1] || "").trim();
518
+ const nextRole = String(match[2] || role).trim().toLowerCase();
519
+ if (target && nextRole) {
520
+ return { action: "agent-role", target, role: nextRole };
521
+ }
522
+ }
523
+ return null;
524
+ }
525
+
482
526
  function parseTelegramStateIntent(text = "") {
483
527
  const value = normalizeTelegramProjectIntentText(text);
484
528
  const lower = value.toLowerCase();
@@ -1490,6 +1534,41 @@ class TelegramGateway {
1490
1534
  };
1491
1535
  }
1492
1536
 
1537
+ async handleConversationalAgentIntent(message, sessionId, intent) {
1538
+ const { session, project } = await this.bindSharedRoomForMessage(message, sessionId);
1539
+ if (!project?.enabled) {
1540
+ throw new Error("This project is not shared yet. Use /project share first.");
1541
+ }
1542
+ const actorId = String(message?.from?.id || "").trim();
1543
+ if (!memberHasAtLeastRole(project, actorId, "owner")) {
1544
+ throw new Error("Only a shared-project owner can change terminal roles.");
1545
+ }
1546
+ const actor = this.describeTelegramUser(message?.from || {});
1547
+ const targetAgent = resolveProjectAgent(project, intent.target);
1548
+ if (!targetAgent) {
1549
+ throw new Error(`No terminal found for ${intent.target}. Ask them to connect their Waterbrother bot/terminal first.`);
1550
+ }
1551
+ const nextProject = await upsertSharedAgent(session.cwd || this.cwd, {
1552
+ ...targetAgent,
1553
+ role: intent.role
1554
+ }, {
1555
+ actorId,
1556
+ actorName: actor.displayName || actorId
1557
+ });
1558
+ return {
1559
+ kind: "agent",
1560
+ project: nextProject,
1561
+ markup: [
1562
+ "<b>Roundtable terminal updated</b>",
1563
+ `owner: <code>${escapeTelegramHtml(targetAgent.ownerName || targetAgent.ownerId || targetAgent.label || targetAgent.id || "-")}</code>`,
1564
+ `terminal: <code>${escapeTelegramHtml(targetAgent.label || targetAgent.id || "-")}</code>`,
1565
+ `role: <code>${escapeTelegramHtml(intent.role)}</code>`,
1566
+ `runtime: <code>${escapeTelegramHtml(targetAgent.provider && targetAgent.model ? `${targetAgent.provider}/${targetAgent.model}` : "unknown")}</code>`,
1567
+ `project: <code>${escapeTelegramHtml(nextProject.projectName || path.basename(session.cwd || this.cwd))}</code>`
1568
+ ].join("\n")
1569
+ };
1570
+ }
1571
+
1493
1572
  async handleStateIntent(message, sessionId, intent) {
1494
1573
  const { session, project } = await this.bindSharedRoomForMessage(message, sessionId);
1495
1574
  const cwd = session.cwd || this.cwd;
@@ -2962,6 +3041,16 @@ Ask them to run <code>/whoami</code> and then <code>/accept-invite ${escapeTeleg
2962
3041
  }
2963
3042
  return;
2964
3043
  }
3044
+ const agentIntent = parseTelegramAgentIntent(promptText);
3045
+ if (agentIntent) {
3046
+ try {
3047
+ const result = await this.handleConversationalAgentIntent(message, sessionId, agentIntent);
3048
+ await this.sendMarkup(message.chat.id, result.markup, message.message_id);
3049
+ } catch (error) {
3050
+ await this.sendMessage(message.chat.id, escapeTelegramHtml(error instanceof Error ? error.message : String(error)), message.message_id);
3051
+ }
3052
+ return;
3053
+ }
2965
3054
  const memberIntent = parseTelegramMemberIntent(promptText);
2966
3055
  if (memberIntent) {
2967
3056
  try {