@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 +1 -1
- package/package.json +1 -1
- package/src/cli.js +67 -0
- package/src/gateway.js +295 -2
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
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 <path|name></code> switch to another project or directory",
|
|
215
|
+
"<code>/project new <name></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 <path></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
|
-
|
|
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 <path|name></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 <name></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 (
|