@hienlh/ppm 0.9.49 โ 0.9.50
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/CHANGELOG.md +5 -0
- package/package.json +1 -1
- package/src/services/ppmbot/ppmbot-service.ts +61 -16
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.50] - 2026-04-07
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- **/sessions redesigned**: Filters by current project, shows pinned sessions first with ๐ icon, displays session titles (not project names), supports pagination via `/sessions 2`. Matches PPM web UI behavior.
|
|
7
|
+
|
|
3
8
|
## [0.9.49] - 2026-04-07
|
|
4
9
|
|
|
5
10
|
### Fixed
|
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
getPairingByChatId,
|
|
6
6
|
createPairingRequest,
|
|
7
7
|
getSessionTitles,
|
|
8
|
+
getPinnedSessionIds,
|
|
8
9
|
getApprovedPairedChats,
|
|
9
10
|
} from "../db.service.ts";
|
|
10
11
|
import { PPMBotTelegram } from "./ppmbot-telegram.ts";
|
|
@@ -159,7 +160,7 @@ class PPMBotService {
|
|
|
159
160
|
case "start": await this.cmdStart(chatId); break;
|
|
160
161
|
case "project": await this.cmdProject(chatId, cmd.args); break;
|
|
161
162
|
case "new": await this.cmdNew(chatId); break;
|
|
162
|
-
case "sessions": await this.cmdSessions(chatId); break;
|
|
163
|
+
case "sessions": await this.cmdSessions(chatId, cmd.args); break;
|
|
163
164
|
case "resume": await this.cmdResume(chatId, cmd.args); break;
|
|
164
165
|
case "status": await this.cmdStatus(chatId); break;
|
|
165
166
|
case "stop": await this.cmdStop(chatId); break;
|
|
@@ -254,29 +255,73 @@ class PPMBotService {
|
|
|
254
255
|
);
|
|
255
256
|
}
|
|
256
257
|
|
|
257
|
-
private async cmdSessions(chatId: string): Promise<void> {
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
258
|
+
private async cmdSessions(chatId: string, args: string): Promise<void> {
|
|
259
|
+
const PAGE_SIZE = 8;
|
|
260
|
+
const page = Math.max(1, parseInt(args, 10) || 1);
|
|
261
|
+
|
|
262
|
+
const active = this.sessions.getActiveSession(chatId);
|
|
263
|
+
const project = active?.projectName;
|
|
264
|
+
|
|
265
|
+
// Fetch all sessions for this chat (enough for pagination)
|
|
266
|
+
const allSessions = this.sessions.listRecentSessions(chatId, 50);
|
|
267
|
+
// Filter by current project if one is active
|
|
268
|
+
const filtered = project
|
|
269
|
+
? allSessions.filter((s) => s.project_name === project)
|
|
270
|
+
: allSessions;
|
|
271
|
+
|
|
272
|
+
if (filtered.length === 0) {
|
|
273
|
+
await this.telegram!.sendMessage(Number(chatId), "No sessions yet. Send a message to start.");
|
|
261
274
|
return;
|
|
262
275
|
}
|
|
263
276
|
|
|
264
|
-
//
|
|
265
|
-
const titles = getSessionTitles(
|
|
277
|
+
// Enrich with titles and pin status
|
|
278
|
+
const titles = getSessionTitles(filtered.map((s) => s.session_id));
|
|
279
|
+
const pinnedIds = getPinnedSessionIds();
|
|
280
|
+
|
|
281
|
+
// Sort: pinned first, then by last_message_at desc
|
|
282
|
+
const sorted = [...filtered].sort((a, b) => {
|
|
283
|
+
const aPin = pinnedIds.has(a.session_id) ? 1 : 0;
|
|
284
|
+
const bPin = pinnedIds.has(b.session_id) ? 1 : 0;
|
|
285
|
+
if (aPin !== bPin) return bPin - aPin;
|
|
286
|
+
return b.last_message_at - a.last_message_at;
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const totalPages = Math.ceil(sorted.length / PAGE_SIZE);
|
|
290
|
+
const start = (page - 1) * PAGE_SIZE;
|
|
291
|
+
const pageItems = sorted.slice(start, start + PAGE_SIZE);
|
|
266
292
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
293
|
+
if (pageItems.length === 0) {
|
|
294
|
+
await this.telegram!.sendMessage(Number(chatId), `No sessions on page ${page}.`);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const header = project ? escapeHtml(project) : "All Projects";
|
|
299
|
+
let text = `<b>Sessions โ ${header}</b>`;
|
|
300
|
+
if (totalPages > 1) text += ` <i>(${page}/${totalPages})</i>`;
|
|
301
|
+
text += "\n\n";
|
|
302
|
+
|
|
303
|
+
pageItems.forEach((s, i) => {
|
|
304
|
+
const pin = pinnedIds.has(s.session_id) ? "๐ " : "";
|
|
305
|
+
const activeDot = s.is_active ? " โฌค" : "";
|
|
306
|
+
const rawTitle = titles[s.session_id]?.replace(/^\[PPM\]\s*/, "") || "";
|
|
307
|
+
const title = rawTitle
|
|
308
|
+
? escapeHtml(rawTitle.slice(0, 45))
|
|
309
|
+
: "<i>untitled</i>";
|
|
272
310
|
const date = new Date(s.last_message_at * 1000).toLocaleString(undefined, {
|
|
273
311
|
month: "short", day: "numeric", hour: "2-digit", minute: "2-digit",
|
|
274
312
|
});
|
|
275
313
|
const sid = s.session_id.slice(0, 8);
|
|
276
|
-
|
|
314
|
+
const num = start + i + 1;
|
|
315
|
+
|
|
316
|
+
text += `${pin}${num}. ${title}${activeDot}\n`;
|
|
277
317
|
text += ` <code>${sid}</code> ยท ${date}\n\n`;
|
|
278
318
|
});
|
|
279
|
-
|
|
319
|
+
|
|
320
|
+
text += "Resume: /resume <n> or /resume <id>";
|
|
321
|
+
if (totalPages > 1 && page < totalPages) {
|
|
322
|
+
text += `\nNext: /sessions ${page + 1}`;
|
|
323
|
+
}
|
|
324
|
+
|
|
280
325
|
await this.telegram!.sendMessage(Number(chatId), text);
|
|
281
326
|
}
|
|
282
327
|
|
|
@@ -427,8 +472,8 @@ class PPMBotService {
|
|
|
427
472
|
/start โ Greeting + list projects
|
|
428
473
|
/project <name> โ Switch/list projects
|
|
429
474
|
/new โ Fresh session (current project)
|
|
430
|
-
/sessions โ List
|
|
431
|
-
/resume <n> โ Resume session
|
|
475
|
+
/sessions [page] โ List sessions (current project)
|
|
476
|
+
/resume <n or id> โ Resume session
|
|
432
477
|
/status โ Current project/session info
|
|
433
478
|
/stop โ End current session
|
|
434
479
|
/memory โ Show project memories
|