@hienlh/ppm 0.9.48 → 0.9.49
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
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.49] - 2026-04-07
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- **/resume accepts session ID prefix**: `/resume fdc4ddaa` now works alongside `/resume 2` (index). Matches session by ID prefix from `/sessions` list.
|
|
7
|
+
- **/restart actually restarts**: Server now exits with code 42 (restart signal) instead of 0 (clean exit). Supervisor recognizes code 42 and respawns immediately without backoff.
|
|
8
|
+
- **Restart notification delivered**: Supervisor respawns after `/restart`, new server sends "PPM v0.9.49 restarted successfully." to all paired chats.
|
|
9
|
+
- **/project lists all projects**: `getProjectNames()` now merges config projects + unique project names from session history. Previously returned empty when no projects in config.
|
|
10
|
+
|
|
3
11
|
## [0.9.48] - 2026-04-06
|
|
4
12
|
|
|
5
13
|
### Changed
|
package/package.json
CHANGED
|
@@ -1022,6 +1022,13 @@ export function getRecentPPMBotSessions(
|
|
|
1022
1022
|
).all(telegramChatId, limit) as PPMBotSessionRow[];
|
|
1023
1023
|
}
|
|
1024
1024
|
|
|
1025
|
+
export function getDistinctPPMBotProjectNames(): string[] {
|
|
1026
|
+
const rows = getDb().query(
|
|
1027
|
+
"SELECT DISTINCT project_name FROM clawbot_sessions ORDER BY project_name",
|
|
1028
|
+
).all() as { project_name: string }[];
|
|
1029
|
+
return rows.map((r) => r.project_name);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1025
1032
|
// ---------------------------------------------------------------------------
|
|
1026
1033
|
// PPMBot memory helpers
|
|
1027
1034
|
// ---------------------------------------------------------------------------
|
|
@@ -281,12 +281,19 @@ class PPMBotService {
|
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
private async cmdResume(chatId: string, args: string): Promise<void> {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
await this.telegram!.sendMessage(Number(chatId), "Usage: /resume <number>");
|
|
284
|
+
if (!args.trim()) {
|
|
285
|
+
await this.telegram!.sendMessage(Number(chatId), "Usage: /resume <number or session-id>");
|
|
287
286
|
return;
|
|
288
287
|
}
|
|
289
|
-
|
|
288
|
+
|
|
289
|
+
// Support both index (e.g. "2") and session ID prefix (e.g. "fdc4ddaa")
|
|
290
|
+
const index = parseInt(args, 10);
|
|
291
|
+
const isIndex = !isNaN(index) && index >= 1 && String(index) === args.trim();
|
|
292
|
+
|
|
293
|
+
const session = isIndex
|
|
294
|
+
? await this.sessions.resumeSessionById(chatId, index)
|
|
295
|
+
: await this.sessions.resumeSessionByIdPrefix(chatId, args.trim());
|
|
296
|
+
|
|
290
297
|
if (!session) {
|
|
291
298
|
await this.telegram!.sendMessage(Number(chatId), "Session not found.");
|
|
292
299
|
return;
|
|
@@ -368,8 +375,8 @@ class PPMBotService {
|
|
|
368
375
|
const markerPath = join(homedir(), ".ppm", "restart-notify.json");
|
|
369
376
|
writeFileSync(markerPath, JSON.stringify({ chatIds, ts: Date.now() }));
|
|
370
377
|
|
|
371
|
-
console.log("[ppmbot] Restart requested via Telegram, exiting...");
|
|
372
|
-
process.exit(
|
|
378
|
+
console.log("[ppmbot] Restart requested via Telegram, exiting with code 42...");
|
|
379
|
+
process.exit(42);
|
|
373
380
|
}, 500);
|
|
374
381
|
}
|
|
375
382
|
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
deactivatePPMBotSession,
|
|
10
10
|
touchPPMBotSession,
|
|
11
11
|
getRecentPPMBotSessions,
|
|
12
|
+
getDistinctPPMBotProjectNames,
|
|
12
13
|
setSessionTitle,
|
|
13
14
|
} from "../db.service.ts";
|
|
14
15
|
import type { PPMBotActiveSession, PPMBotSessionRow } from "../../types/ppmbot.ts";
|
|
@@ -93,6 +94,23 @@ export class PPMBotSessionManager {
|
|
|
93
94
|
return this.resumeFromDb(chatId, target, project);
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
/** Resume a session by session ID prefix match */
|
|
98
|
+
async resumeSessionByIdPrefix(
|
|
99
|
+
chatId: string,
|
|
100
|
+
prefix: string,
|
|
101
|
+
): Promise<PPMBotActiveSession | null> {
|
|
102
|
+
const sessions = getRecentPPMBotSessions(chatId, 20);
|
|
103
|
+
const target = sessions.find((s) => s.session_id.startsWith(prefix));
|
|
104
|
+
if (!target) return null;
|
|
105
|
+
|
|
106
|
+
await this.closeSession(chatId);
|
|
107
|
+
|
|
108
|
+
const project = this.resolveProject(target.project_name);
|
|
109
|
+
if (!project) return null;
|
|
110
|
+
|
|
111
|
+
return this.resumeFromDb(chatId, target, project);
|
|
112
|
+
}
|
|
113
|
+
|
|
96
114
|
/**
|
|
97
115
|
* Resolve a project name against configured projects.
|
|
98
116
|
* Case-insensitive, supports prefix matching.
|
|
@@ -119,10 +137,12 @@ export class PPMBotSessionManager {
|
|
|
119
137
|
setSessionTitle(sessionId, title);
|
|
120
138
|
}
|
|
121
139
|
|
|
122
|
-
/** Get list of available project names (
|
|
140
|
+
/** Get list of available project names (config + sessions history) */
|
|
123
141
|
getProjectNames(): string[] {
|
|
124
|
-
const
|
|
125
|
-
|
|
142
|
+
const configured = (configService.get("projects") as ProjectConfig[])?.map((p) => p.name) ?? [];
|
|
143
|
+
const fromSessions = getDistinctPPMBotProjectNames();
|
|
144
|
+
const merged = new Set([...configured, ...fromSessions]);
|
|
145
|
+
return [...merged].sort();
|
|
126
146
|
}
|
|
127
147
|
|
|
128
148
|
// ── Private ─────────────────────────────────────────────────────
|
|
@@ -130,11 +130,17 @@ export async function spawnServer(
|
|
|
130
130
|
const exitCode = await serverChild.exited;
|
|
131
131
|
serverChild = null;
|
|
132
132
|
|
|
133
|
-
if (exitCode === 0
|
|
133
|
+
if (exitCode === 0 && shuttingDown) {
|
|
134
134
|
log("INFO", `Server exited cleanly (code ${exitCode})`);
|
|
135
135
|
return;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
// Exit code 42 = restart requested (e.g. /restart from Telegram)
|
|
139
|
+
if (exitCode === 42 || (exitCode === 0 && !shuttingDown)) {
|
|
140
|
+
log("INFO", `Server restart requested (code ${exitCode}), respawning immediately`);
|
|
141
|
+
return spawnServer(serverArgs, logFd);
|
|
142
|
+
}
|
|
143
|
+
|
|
138
144
|
// SIGUSR2 restart — skip backoff, respawn immediately
|
|
139
145
|
if (serverRestartRequested) {
|
|
140
146
|
serverRestartRequested = false;
|