@tritard/waterbrother 0.16.46 → 0.16.47

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 CHANGED
@@ -296,7 +296,7 @@ Current Telegram behavior:
296
296
  - the gateway sends typing activity and edits one in-progress message into the final reply before falling back to chunked follow-up messages
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
- - Telegram now supports remote workspace control with `/cwd`, `/use <path>`, `/desktop`, and `/new-project <name>`
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
300
  - 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
301
301
  - Telegram now supports `/whoami` for Telegram-id and shared-room membership visibility, plus `/events` for recent shared-room activity
302
302
  - `/room` now includes pending invite count plus task ownership summaries
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tritard/waterbrother",
3
- "version": "0.16.46",
3
+ "version": "0.16.47",
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/cli.js CHANGED
@@ -124,6 +124,10 @@ const INTERACTIVE_COMMANDS = [
124
124
  { name: "/sessions", description: "List recent sessions" },
125
125
  { name: "/new", description: "Start a new session" },
126
126
  { name: "/resume <id>", description: "Resume a saved session" },
127
+ { name: "/project", description: "Show current project cwd and shared-room state" },
128
+ { name: "/project use <path|name>", description: "Switch the live session to another project or directory" },
129
+ { name: "/project new <name>", description: "Create a Desktop project and switch into it" },
130
+ { name: "/project share", description: "Enable shared-project mode in the current project" },
127
131
  { name: "/run <prompt>", description: "Run a normal prompt without treating it as a slash command" },
128
132
  { name: "/fork", description: "Fork current session into a new session" },
129
133
  { name: "/clear", description: "Clear current conversation history" },
@@ -8019,6 +8023,18 @@ Be concrete about surfaces — name actual pages/flows. Choose the best stack fo
8019
8023
  continue;
8020
8024
  }
8021
8025
 
8026
+ if (line === "/project") {
8027
+ const sharedProject = await loadSharedProject(context.cwd).catch(() => null);
8028
+ console.log(JSON.stringify({
8029
+ cwd: context.cwd,
8030
+ project: path.basename(context.cwd),
8031
+ shared: sharedProject?.enabled === true,
8032
+ roomMode: sharedProject?.enabled ? sharedProject.roomMode || "chat" : null,
8033
+ room: sharedProject?.enabled ? sharedProject.room || null : null
8034
+ }, null, 2));
8035
+ continue;
8036
+ }
8037
+
8022
8038
  if (line === "/desktop") {
8023
8039
  try {
8024
8040
  const desktopPath = path.join(process.env.HOME || process.env.USERPROFILE || "", "Desktop");
@@ -8045,6 +8061,24 @@ Be concrete about surfaces — name actual pages/flows. Choose the best stack fo
8045
8061
  continue;
8046
8062
  }
8047
8063
 
8064
+ if (line.startsWith("/project use ")) {
8065
+ const rawPath = line.replace("/project use", "").trim();
8066
+ if (!rawPath) {
8067
+ console.log("Usage: /project use <path|name>");
8068
+ continue;
8069
+ }
8070
+ try {
8071
+ const projectPath = /^(~\/|\/|\.\/|\.\.\/)/.test(rawPath) || /^\/(?:desktop|downloads|documents)\b/i.test(rawPath)
8072
+ ? rawPath
8073
+ : path.join(process.env.HOME || process.env.USERPROFILE || "", "Desktop", rawPath);
8074
+ const nextCwd = await switchSessionWorkspace({ agent, session: currentSession, context, nextCwd: projectPath });
8075
+ console.log(`cwd set to ${nextCwd}`);
8076
+ } catch (error) {
8077
+ console.log(`project switch failed: ${error instanceof Error ? error.message : String(error)}`);
8078
+ }
8079
+ continue;
8080
+ }
8081
+
8048
8082
  if (line.startsWith("/new-project ")) {
8049
8083
  const projectName = line.replace("/new-project", "").trim();
8050
8084
  if (!projectName) {
@@ -8079,6 +8113,30 @@ Be concrete about surfaces — name actual pages/flows. Choose the best stack fo
8079
8113
  continue;
8080
8114
  }
8081
8115
 
8116
+ if (line.startsWith("/project new ")) {
8117
+ const projectName = line.replace("/project new", "").trim();
8118
+ if (!projectName) {
8119
+ console.log("Usage: /project new <name>");
8120
+ continue;
8121
+ }
8122
+ try {
8123
+ const target = canonicalizeLoosePath(path.join(process.env.HOME || process.env.USERPROFILE || "", "Desktop", projectName));
8124
+ await fs.mkdir(target, { recursive: true });
8125
+ await saveCurrentSession(currentSession, agent);
8126
+ const fresh = await createSession({ cwd: target, model: agent.getModel(), agentProfile: agent.getProfile() });
8127
+ currentSession = fresh;
8128
+ agent.resetConversation();
8129
+ await switchSessionWorkspace({ agent, session: currentSession, context, nextCwd: target });
8130
+ context.lastUsage = null;
8131
+ context.costTracker = createCostTracker();
8132
+ console.log(`started new project session: ${currentSession.id}`);
8133
+ console.log(`cwd set to ${target}`);
8134
+ } catch (error) {
8135
+ console.log(`new project failed: ${error instanceof Error ? error.message : String(error)}`);
8136
+ }
8137
+ continue;
8138
+ }
8139
+
8082
8140
  if (line.startsWith("/run ")) {
8083
8141
  const promptText = line.replace("/run", "").trim();
8084
8142
  if (!promptText) {
@@ -8275,6 +8333,15 @@ Be concrete about surfaces — name actual pages/flows. Choose the best stack fo
8275
8333
  continue;
8276
8334
  }
8277
8335
 
8336
+ if (line === "/project share") {
8337
+ try {
8338
+ await runProjectCommand(["project", "share"], { cwd: context.cwd, asJson: false });
8339
+ } catch (error) {
8340
+ console.log(`project share failed: ${error instanceof Error ? error.message : String(error)}`);
8341
+ }
8342
+ continue;
8343
+ }
8344
+
8278
8345
  if (line === "/unshare-project") {
8279
8346
  try {
8280
8347
  await runProjectCommand(["project", "unshare"], { cwd: context.cwd, asJson: false });
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, listSharedEvents, listSharedInvites, listSharedTasks, loadSharedProject, moveSharedTask, rejectSharedInvite, releaseSharedOperator, setSharedRoom, setSharedRoomMode, setSharedRuntimeProfile, removeSharedMember } from "./shared-project.js";
11
+ 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
12
  import { buildSelfAwarenessManifest, formatAboutWaterbrother, formatSelfState, resolveLocalConceptQuestion } from "./self-awareness.js";
13
13
 
14
14
  const execFileAsync = promisify(execFile);
@@ -29,6 +29,7 @@ const TELEGRAM_COMMANDS = [
29
29
  { command: "status", description: "Show the linked remote session" },
30
30
  { command: "whoami", description: "Show your Telegram identity and room membership" },
31
31
  { command: "people", description: "Show recently seen Telegram people in this chat" },
32
+ { command: "project", description: "Show or change the linked project" },
32
33
  { command: "cwd", description: "Show the current remote working directory" },
33
34
  { command: "runtime", description: "Show active runtime status" },
34
35
  { command: "room", description: "Show shared room status" },
@@ -209,6 +210,10 @@ function buildRemoteHelp() {
209
210
  "<code>/status</code> show the current linked remote session",
210
211
  "<code>/whoami</code> show your Telegram identity and room membership",
211
212
  "<code>/people</code> list recent Telegram users in this chat for easier invites",
213
+ "<code>/project</code> show the linked project and sharing state",
214
+ "<code>/project use &lt;path|name&gt;</code> switch to another project or directory",
215
+ "<code>/project new &lt;name&gt;</code> create a Desktop project and switch into it",
216
+ "<code>/project share</code> enable Roundtable in the current project and bind this chat",
212
217
  "<code>/cwd</code> show the current remote working directory",
213
218
  "<code>/use &lt;path&gt;</code> switch the linked session to another directory",
214
219
  "<code>/desktop</code> switch the linked session to <code>~/Desktop</code>",
@@ -262,6 +267,67 @@ function formatGatewaySessionStatus({ sessionId, userId, username, cwd, runtimeP
262
267
  return bits.join("\n");
263
268
  }
264
269
 
270
+ function formatTelegramProjectMarkup({ cwd, project, chatId = "", title = "" }) {
271
+ const lines = [
272
+ "<b>Linked project</b>",
273
+ `cwd: <code>${escapeTelegramHtml(cwd || "-")}</code>`,
274
+ `name: <code>${escapeTelegramHtml(project?.projectName || path.basename(cwd || "") || "-")}</code>`,
275
+ `shared: <code>${project?.enabled ? "yes" : "no"}</code>`
276
+ ];
277
+ if (project?.enabled) {
278
+ lines.push(`room mode: <code>${escapeTelegramHtml(project.roomMode || "chat")}</code>`);
279
+ lines.push(
280
+ `bound chat: <code>${escapeTelegramHtml(project.room?.provider === "telegram" && project.room?.chatId ? `${project.room.provider} ${project.room.chatId}` : "none")}</code>`
281
+ );
282
+ lines.push(`members: <code>${escapeTelegramHtml(String((project.members || []).length))}</code>`);
283
+ }
284
+ if (!project?.enabled) {
285
+ lines.push("Use <code>/project share</code> to turn on Roundtable for this project in this chat.");
286
+ } else if (chatId && String(project.room?.chatId || "") !== String(chatId || "")) {
287
+ lines.push(`This chat is not bound yet. Run <code>/project share</code> to bind ${escapeTelegramHtml(title || "this chat")} to the current project.`);
288
+ }
289
+ return lines.join("\n");
290
+ }
291
+
292
+ function normalizeTelegramProjectIntentText(text = "") {
293
+ return String(text || "").trim().replace(/\s+/g, " ");
294
+ }
295
+
296
+ function parseTelegramProjectIntent(text = "") {
297
+ const value = normalizeTelegramProjectIntentText(text);
298
+ const lowered = value.toLowerCase();
299
+ if (!value) return null;
300
+ if (/^(how|what|why|when|where|who)\b/.test(lowered)) return null;
301
+
302
+ if (
303
+ /\b(?:share|enable|turn on|set up)\b.*\b(?:roundtable|shared room|shared project)\b/.test(lowered)
304
+ || /\bshare (?:this|the) (?:repo|project)\b.*\b(?:chat|group|telegram|room|here)\b/.test(lowered)
305
+ ) {
306
+ return { action: "share-project" };
307
+ }
308
+
309
+ const newProjectMatch = value.match(/^(?:make|create|start)(?:\s+(?:me|a|an))?(?:\s+new)?\s+project(?:\s+(?:called|named))?\s+["“]?([^"”]+?)["”]?\s*$/i);
310
+ if (newProjectMatch?.[1]) {
311
+ return { action: "new-project", name: newProjectMatch[1].trim(), share: /\b(shared|roundtable)\b/i.test(value) };
312
+ }
313
+
314
+ if (/^(?:switch|change|move|use)\b/.test(lowered)) {
315
+ if (/\bdesktop\b/.test(lowered)) {
316
+ return { action: "use-path", target: "~/Desktop" };
317
+ }
318
+ const projectMatch = value.match(/^(?:switch|change|move|use)(?:\s+(?:the|my))?(?:\s+(?:project|repo|repository|folder|directory|workspace|cwd))?\s+(?:to\s+)?["“]?([^"”]+?)["”]?\s*$/i);
319
+ if (projectMatch?.[1]) {
320
+ return { action: "use-path", target: projectMatch[1].trim() };
321
+ }
322
+ }
323
+
324
+ if (/^(?:show|check)\s+(?:the\s+)?project\b/i.test(value) || /^what project\b/i.test(lowered)) {
325
+ return { action: "show-project" };
326
+ }
327
+
328
+ return null;
329
+ }
330
+
265
331
  async function buildTelegramSelfAwarenessMarkup({ cwd, runtime, currentSession, kind = "about" }) {
266
332
  const manifest = await buildSelfAwarenessManifest({ cwd, runtime, currentSession });
267
333
  if (kind === "state") {
@@ -1027,19 +1093,96 @@ class TelegramGateway {
1027
1093
  return { session, project };
1028
1094
  }
1029
1095
 
1096
+ canBootstrapTelegramOwner(project, userId) {
1097
+ const normalizedUserId = String(userId || "").trim();
1098
+ const members = Array.isArray(project?.members) ? project.members : [];
1099
+ if (!project?.enabled || !normalizedUserId || !members.length) return false;
1100
+ if (members.some((member) => String(member?.id || "").trim() === normalizedUserId)) return false;
1101
+ if (members.some((member) => !String(member?.id || "").trim().startsWith("local:"))) return false;
1102
+ return members.some((member) => String(member?.role || "").trim() === "owner");
1103
+ }
1104
+
1105
+ async maybeBootstrapTelegramOwner(cwd, project, actor = {}) {
1106
+ const normalizedCwd = String(cwd || "").trim();
1107
+ const userId = String(actor.userId || "").trim();
1108
+ if (!this.canBootstrapTelegramOwner(project, userId)) {
1109
+ return project;
1110
+ }
1111
+ const bootstrapOwner = (project.members || []).find((member) => String(member?.role || "").trim() === "owner");
1112
+ if (!bootstrapOwner?.id) return project;
1113
+ return upsertSharedMember(
1114
+ normalizedCwd,
1115
+ { id: userId, name: actor.displayName || userId, role: "owner", paired: true },
1116
+ { actorId: bootstrapOwner.id, actorName: bootstrapOwner.name || bootstrapOwner.id }
1117
+ );
1118
+ }
1119
+
1030
1120
  async bindSharedRoomForMessage(message, sessionId) {
1031
1121
  const { session, project } = await this.loadSharedProjectForSession(sessionId);
1032
1122
  if (!project?.enabled) {
1033
1123
  return { session, project };
1034
1124
  }
1035
- const next = await setSharedRoom(session.cwd || this.cwd, {
1125
+ let next = await setSharedRoom(session.cwd || this.cwd, {
1036
1126
  provider: "telegram",
1037
1127
  chatId: String(message.chat.id),
1038
1128
  title: String(message.chat.title || "").trim()
1039
1129
  });
1130
+ next = await this.maybeBootstrapTelegramOwner(session.cwd || this.cwd, next, this.describeTelegramUser(message?.from || {}));
1040
1131
  return { session, project: next };
1041
1132
  }
1042
1133
 
1134
+ async ensureProjectSharedForChat(message, sessionId) {
1135
+ const session = await loadSession(sessionId);
1136
+ const cwd = session.cwd || this.cwd;
1137
+ const actor = this.describeTelegramUser(message?.from || {});
1138
+ let project = await loadSharedProject(cwd);
1139
+ if (!project?.enabled) {
1140
+ project = await enableSharedProject(cwd, {
1141
+ userId: actor.userId,
1142
+ userName: actor.displayName || actor.userId,
1143
+ role: "owner"
1144
+ });
1145
+ } else {
1146
+ project = await this.maybeBootstrapTelegramOwner(cwd, project, actor);
1147
+ }
1148
+ project = await setSharedRoom(cwd, {
1149
+ provider: "telegram",
1150
+ chatId: String(message.chat.id),
1151
+ title: String(message.chat.title || "").trim()
1152
+ });
1153
+ return { session, project };
1154
+ }
1155
+
1156
+ async resolveTelegramProjectTarget(rawTarget) {
1157
+ const value = String(rawTarget || "").trim();
1158
+ if (!value) {
1159
+ throw new Error("project path is required");
1160
+ }
1161
+ if (/^~\/?desktop$/i.test(value) || /^desktop$/i.test(value) || /^\/desktop$/i.test(value)) {
1162
+ return canonicalizeLoosePath(path.join(process.env.HOME || process.env.USERPROFILE || "", "Desktop"));
1163
+ }
1164
+ if (/^(~\/|\/|\.\/|\.\.\/)/.test(value) || /^\/(?:desktop|downloads|documents)\b/i.test(value)) {
1165
+ return canonicalizeLoosePath(value);
1166
+ }
1167
+ return canonicalizeLoosePath(path.join(process.env.HOME || process.env.USERPROFILE || "", "Desktop", value));
1168
+ }
1169
+
1170
+ async switchPeerProject(message, target, { create = false, share = false } = {}) {
1171
+ const nextCwd = await this.resolveTelegramProjectTarget(target);
1172
+ if (create) {
1173
+ await fs.mkdir(nextCwd, { recursive: true });
1174
+ }
1175
+ const sessionId = await this.ensurePeerSession(message);
1176
+ await this.updatePeerSessionCwd(message, nextCwd);
1177
+ let project = await loadSharedProject(nextCwd);
1178
+ if (share) {
1179
+ ({ project } = await this.ensureProjectSharedForChat(message, sessionId));
1180
+ } else if (project?.enabled) {
1181
+ ({ project } = await this.bindSharedRoomForMessage(message, sessionId));
1182
+ }
1183
+ return { sessionId, cwd: nextCwd, project };
1184
+ }
1185
+
1043
1186
  async buildRoomMarkup(message, sessionId) {
1044
1187
  const { project } = await this.bindSharedRoomForMessage(message, sessionId);
1045
1188
  const host = await this.getLiveBridgeHost();
@@ -1364,6 +1507,88 @@ class TelegramGateway {
1364
1507
  return;
1365
1508
  }
1366
1509
 
1510
+ if (text === "/project") {
1511
+ const { session, project } = await this.bindSharedRoomForMessage(message, sessionId);
1512
+ await this.sendMarkup(
1513
+ message.chat.id,
1514
+ formatTelegramProjectMarkup({
1515
+ cwd: session.cwd || this.cwd,
1516
+ project,
1517
+ chatId: String(message.chat.id),
1518
+ title: String(message.chat.title || "").trim()
1519
+ }),
1520
+ message.message_id
1521
+ );
1522
+ return;
1523
+ }
1524
+
1525
+ if (text === "/project share") {
1526
+ try {
1527
+ const { session, project } = await this.ensureProjectSharedForChat(message, sessionId);
1528
+ await this.sendMarkup(
1529
+ message.chat.id,
1530
+ `${formatTelegramProjectMarkup({
1531
+ cwd: session.cwd || this.cwd,
1532
+ project,
1533
+ chatId: String(message.chat.id),
1534
+ title: String(message.chat.title || "").trim()
1535
+ })}\n\nRoundtable is now enabled for this project in this chat.`,
1536
+ message.message_id
1537
+ );
1538
+ } catch (error) {
1539
+ await this.sendMessage(message.chat.id, escapeTelegramHtml(error instanceof Error ? error.message : String(error)), message.message_id);
1540
+ }
1541
+ return;
1542
+ }
1543
+
1544
+ if (text.startsWith("/project use ")) {
1545
+ const rawPath = text.replace("/project use", "").trim();
1546
+ if (!rawPath) {
1547
+ await this.sendMarkup(message.chat.id, "Usage: <code>/project use &lt;path|name&gt;</code>", message.message_id);
1548
+ return;
1549
+ }
1550
+ try {
1551
+ const result = await this.switchPeerProject(message, rawPath);
1552
+ await this.sendMarkup(
1553
+ message.chat.id,
1554
+ `${formatTelegramProjectMarkup({
1555
+ cwd: result.cwd,
1556
+ project: result.project,
1557
+ chatId: String(message.chat.id),
1558
+ title: String(message.chat.title || "").trim()
1559
+ })}\n\nLinked project switched successfully.`,
1560
+ message.message_id
1561
+ );
1562
+ } catch (error) {
1563
+ await this.sendMessage(message.chat.id, escapeTelegramHtml(error instanceof Error ? error.message : String(error)), message.message_id);
1564
+ }
1565
+ return;
1566
+ }
1567
+
1568
+ if (text.startsWith("/project new ")) {
1569
+ const projectName = text.replace("/project new", "").trim();
1570
+ if (!projectName) {
1571
+ await this.sendMarkup(message.chat.id, "Usage: <code>/project new &lt;name&gt;</code>", message.message_id);
1572
+ return;
1573
+ }
1574
+ try {
1575
+ const result = await this.switchPeerProject(message, projectName, { create: true });
1576
+ await this.sendMarkup(
1577
+ message.chat.id,
1578
+ `${formatTelegramProjectMarkup({
1579
+ cwd: result.cwd,
1580
+ project: result.project,
1581
+ chatId: String(message.chat.id),
1582
+ title: String(message.chat.title || "").trim()
1583
+ })}\n\nCreated and switched to a new Desktop project.`,
1584
+ message.message_id
1585
+ );
1586
+ } catch (error) {
1587
+ await this.sendMessage(message.chat.id, escapeTelegramHtml(error instanceof Error ? error.message : String(error)), message.message_id);
1588
+ }
1589
+ return;
1590
+ }
1591
+
1367
1592
  if (text === "/status") {
1368
1593
  await this.sendMarkup(
1369
1594
  message.chat.id,
@@ -1901,6 +2126,74 @@ Ask them to run <code>/whoami</code> and then <code>/accept-invite ${escapeTeleg
1901
2126
  ? (isExplicitRun ? { kind: "execution", reason: "explicit /run" } : classifyTelegramGroupIntent(promptText))
1902
2127
  : { kind: "execution", reason: "private chat" };
1903
2128
  const sharedBinding = await this.bindSharedRoomForMessage(message, sessionId);
2129
+ const projectIntent = parseTelegramProjectIntent(promptText);
2130
+ if (projectIntent?.action === "show-project") {
2131
+ await this.sendMarkup(
2132
+ message.chat.id,
2133
+ formatTelegramProjectMarkup({
2134
+ cwd: sharedBinding.session.cwd || this.cwd,
2135
+ project: sharedBinding.project,
2136
+ chatId: String(message.chat.id),
2137
+ title: String(message.chat.title || "").trim()
2138
+ }),
2139
+ message.message_id
2140
+ );
2141
+ return;
2142
+ }
2143
+ if (projectIntent?.action === "share-project") {
2144
+ try {
2145
+ const { session, project } = await this.ensureProjectSharedForChat(message, sessionId);
2146
+ await this.sendMarkup(
2147
+ message.chat.id,
2148
+ `${formatTelegramProjectMarkup({
2149
+ cwd: session.cwd || this.cwd,
2150
+ project,
2151
+ chatId: String(message.chat.id),
2152
+ title: String(message.chat.title || "").trim()
2153
+ })}\n\nRoundtable is now enabled for this project in this chat.`,
2154
+ message.message_id
2155
+ );
2156
+ } catch (error) {
2157
+ await this.sendMessage(message.chat.id, escapeTelegramHtml(error instanceof Error ? error.message : String(error)), message.message_id);
2158
+ }
2159
+ return;
2160
+ }
2161
+ if (projectIntent?.action === "use-path") {
2162
+ try {
2163
+ const result = await this.switchPeerProject(message, projectIntent.target);
2164
+ await this.sendMarkup(
2165
+ message.chat.id,
2166
+ `${formatTelegramProjectMarkup({
2167
+ cwd: result.cwd,
2168
+ project: result.project,
2169
+ chatId: String(message.chat.id),
2170
+ title: String(message.chat.title || "").trim()
2171
+ })}\n\nLinked project switched successfully.`,
2172
+ message.message_id
2173
+ );
2174
+ } catch (error) {
2175
+ await this.sendMessage(message.chat.id, escapeTelegramHtml(error instanceof Error ? error.message : String(error)), message.message_id);
2176
+ }
2177
+ return;
2178
+ }
2179
+ if (projectIntent?.action === "new-project") {
2180
+ try {
2181
+ const result = await this.switchPeerProject(message, projectIntent.name, { create: true, share: projectIntent.share });
2182
+ await this.sendMarkup(
2183
+ message.chat.id,
2184
+ `${formatTelegramProjectMarkup({
2185
+ cwd: result.cwd,
2186
+ project: result.project,
2187
+ chatId: String(message.chat.id),
2188
+ title: String(message.chat.title || "").trim()
2189
+ })}\n\nCreated and switched to a new Desktop project${projectIntent.share ? ", with Roundtable enabled for this chat" : ""}.`,
2190
+ message.message_id
2191
+ );
2192
+ } catch (error) {
2193
+ await this.sendMessage(message.chat.id, escapeTelegramHtml(error instanceof Error ? error.message : String(error)), message.message_id);
2194
+ }
2195
+ return;
2196
+ }
1904
2197
  const manifest = await buildSelfAwarenessManifest({ cwd: sharedBinding.session.cwd || this.cwd, runtime: this.runtime, currentSession: sharedBinding.session });
1905
2198
  const localConceptAnswer = resolveLocalConceptQuestion(promptText, manifest);
1906
2199
  if (