@hienlh/ppm 0.9.33 → 0.9.35
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 +23 -0
- package/dist/web/assets/{browser-tab-B9nNKjZX.js → browser-tab-DnIsHiCc.js} +1 -1
- package/dist/web/assets/{chat-tab-6XGhEKaC.js → chat-tab-il6D4jql.js} +2 -2
- package/dist/web/assets/{code-editor-DMZMpzt2.js → code-editor-BUc1jBqm.js} +1 -1
- package/dist/web/assets/{database-viewer-CnP1FFS2.js → database-viewer-TjRo2b8_.js} +1 -1
- package/dist/web/assets/{diff-viewer-Cvwd0XBO.js → diff-viewer-BMhCz0xk.js} +1 -1
- package/dist/web/assets/{extension-webview-DkhsRepr.js → extension-webview-DiVdlE2r.js} +1 -1
- package/dist/web/assets/{git-graph-C3670Nxm.js → git-graph-4eGJ8B1A.js} +1 -1
- package/dist/web/assets/{index-DjIQL8ar.js → index-BmcV1di6.js} +4 -4
- package/dist/web/assets/keybindings-store--5T5hsAj.js +1 -0
- package/dist/web/assets/{markdown-renderer-Co04dDdI.js → markdown-renderer-IyEzLrC6.js} +1 -1
- package/dist/web/assets/{postgres-viewer-D8K1qnnA.js → postgres-viewer-CSynGGkJ.js} +1 -1
- package/dist/web/assets/{settings-tab-64ODAeQZ.js → settings-tab-BdI4HhRa.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-ClX7FICB.js → sqlite-viewer-C5mviyU5.js} +1 -1
- package/dist/web/assets/{terminal-tab-Dw4IKWGM.js → terminal-tab-CDyC1grg.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-DA7EyR70.js → use-monaco-theme-DcVicB_i.js} +1 -1
- package/dist/web/index.html +1 -1
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/src/server/index.ts +4 -4
- package/src/server/routes/settings.ts +14 -14
- package/src/services/db.service.ts +29 -29
- package/src/services/{clawbot/clawbot-memory.ts → ppmbot/ppmbot-memory.ts} +29 -29
- package/src/services/{clawbot/clawbot-service.ts → ppmbot/ppmbot-service.ts} +55 -38
- package/src/services/{clawbot/clawbot-session.ts → ppmbot/ppmbot-session.ts} +48 -37
- package/src/services/{clawbot/clawbot-streamer.ts → ppmbot/ppmbot-streamer.ts} +114 -80
- package/src/services/{clawbot/clawbot-telegram.ts → ppmbot/ppmbot-telegram.ts} +46 -18
- package/src/types/config.ts +3 -3
- package/src/types/{clawbot.ts → ppmbot.ts} +10 -10
- package/src/web/components/chat/chat-history-bar.tsx +2 -2
- package/src/web/components/settings/{clawbot-settings-section.tsx → ppmbot-settings-section.tsx} +7 -7
- package/src/web/components/settings/settings-tab.tsx +3 -3
- package/dist/web/assets/keybindings-store-DHh6rwm-.js +0 -1
- /package/src/services/{clawbot/clawbot-formatter.ts → ppmbot/ppmbot-formatter.ts} +0 -0
|
@@ -969,23 +969,23 @@ export function deleteExtensionStorage(extId: string): void {
|
|
|
969
969
|
}
|
|
970
970
|
|
|
971
971
|
// ---------------------------------------------------------------------------
|
|
972
|
-
//
|
|
972
|
+
// PPMBot session helpers
|
|
973
973
|
// ---------------------------------------------------------------------------
|
|
974
974
|
|
|
975
|
-
import type {
|
|
975
|
+
import type { PPMBotSessionRow, PPMBotMemoryRow, PPMBotPairedChat } from "../types/ppmbot.ts";
|
|
976
976
|
|
|
977
|
-
export function
|
|
977
|
+
export function getActivePPMBotSession(
|
|
978
978
|
telegramChatId: string,
|
|
979
979
|
projectName: string,
|
|
980
|
-
):
|
|
980
|
+
): PPMBotSessionRow | null {
|
|
981
981
|
return getDb().query(
|
|
982
982
|
`SELECT * FROM clawbot_sessions
|
|
983
983
|
WHERE telegram_chat_id = ? AND project_name = ? AND is_active = 1
|
|
984
984
|
ORDER BY last_message_at DESC LIMIT 1`,
|
|
985
|
-
).get(telegramChatId, projectName) as
|
|
985
|
+
).get(telegramChatId, projectName) as PPMBotSessionRow | null;
|
|
986
986
|
}
|
|
987
987
|
|
|
988
|
-
export function
|
|
988
|
+
export function createPPMBotSession(
|
|
989
989
|
telegramChatId: string,
|
|
990
990
|
sessionId: string,
|
|
991
991
|
providerId: string,
|
|
@@ -999,34 +999,34 @@ export function createClawBotSession(
|
|
|
999
999
|
).run(telegramChatId, sessionId, providerId, projectName, projectPath);
|
|
1000
1000
|
}
|
|
1001
1001
|
|
|
1002
|
-
export function
|
|
1002
|
+
export function deactivatePPMBotSession(sessionId: string): void {
|
|
1003
1003
|
getDb().query(
|
|
1004
1004
|
"UPDATE clawbot_sessions SET is_active = 0 WHERE session_id = ?",
|
|
1005
1005
|
).run(sessionId);
|
|
1006
1006
|
}
|
|
1007
1007
|
|
|
1008
|
-
export function
|
|
1008
|
+
export function touchPPMBotSession(sessionId: string): void {
|
|
1009
1009
|
getDb().query(
|
|
1010
1010
|
"UPDATE clawbot_sessions SET last_message_at = unixepoch() WHERE session_id = ?",
|
|
1011
1011
|
).run(sessionId);
|
|
1012
1012
|
}
|
|
1013
1013
|
|
|
1014
|
-
export function
|
|
1014
|
+
export function getRecentPPMBotSessions(
|
|
1015
1015
|
telegramChatId: string,
|
|
1016
1016
|
limit = 10,
|
|
1017
|
-
):
|
|
1017
|
+
): PPMBotSessionRow[] {
|
|
1018
1018
|
return getDb().query(
|
|
1019
1019
|
`SELECT * FROM clawbot_sessions
|
|
1020
1020
|
WHERE telegram_chat_id = ?
|
|
1021
1021
|
ORDER BY last_message_at DESC LIMIT ?`,
|
|
1022
|
-
).all(telegramChatId, limit) as
|
|
1022
|
+
).all(telegramChatId, limit) as PPMBotSessionRow[];
|
|
1023
1023
|
}
|
|
1024
1024
|
|
|
1025
1025
|
// ---------------------------------------------------------------------------
|
|
1026
|
-
//
|
|
1026
|
+
// PPMBot memory helpers
|
|
1027
1027
|
// ---------------------------------------------------------------------------
|
|
1028
1028
|
|
|
1029
|
-
export function
|
|
1029
|
+
export function insertPPMBotMemory(
|
|
1030
1030
|
project: string,
|
|
1031
1031
|
content: string,
|
|
1032
1032
|
category: string,
|
|
@@ -1040,11 +1040,11 @@ export function insertClawBotMemory(
|
|
|
1040
1040
|
return Number(result.lastInsertRowid);
|
|
1041
1041
|
}
|
|
1042
1042
|
|
|
1043
|
-
export function
|
|
1043
|
+
export function searchPPMBotMemories(
|
|
1044
1044
|
project: string,
|
|
1045
1045
|
query: string,
|
|
1046
1046
|
limit = 20,
|
|
1047
|
-
): Array<
|
|
1047
|
+
): Array<PPMBotMemoryRow & { rank: number }> {
|
|
1048
1048
|
return getDb().query(
|
|
1049
1049
|
`SELECT m.*, fts.rank
|
|
1050
1050
|
FROM clawbot_memories m
|
|
@@ -1054,23 +1054,23 @@ export function searchClawBotMemories(
|
|
|
1054
1054
|
AND m.superseded_by IS NULL
|
|
1055
1055
|
ORDER BY fts.rank
|
|
1056
1056
|
LIMIT ?`,
|
|
1057
|
-
).all(query, project, limit) as Array<
|
|
1057
|
+
).all(query, project, limit) as Array<PPMBotMemoryRow & { rank: number }>;
|
|
1058
1058
|
}
|
|
1059
1059
|
|
|
1060
|
-
export function
|
|
1060
|
+
export function getPPMBotMemories(
|
|
1061
1061
|
project: string,
|
|
1062
1062
|
limit = 20,
|
|
1063
|
-
):
|
|
1063
|
+
): PPMBotMemoryRow[] {
|
|
1064
1064
|
return getDb().query(
|
|
1065
1065
|
`SELECT * FROM clawbot_memories
|
|
1066
1066
|
WHERE project IN (?, '_global')
|
|
1067
1067
|
AND superseded_by IS NULL
|
|
1068
1068
|
ORDER BY importance DESC, updated_at DESC
|
|
1069
1069
|
LIMIT ?`,
|
|
1070
|
-
).all(project, limit) as
|
|
1070
|
+
).all(project, limit) as PPMBotMemoryRow[];
|
|
1071
1071
|
}
|
|
1072
1072
|
|
|
1073
|
-
export function
|
|
1073
|
+
export function supersedePPMBotMemory(
|
|
1074
1074
|
oldId: number,
|
|
1075
1075
|
newId: number,
|
|
1076
1076
|
): void {
|
|
@@ -1079,7 +1079,7 @@ export function supersedeClawBotMemory(
|
|
|
1079
1079
|
).run(newId, oldId);
|
|
1080
1080
|
}
|
|
1081
1081
|
|
|
1082
|
-
export function
|
|
1082
|
+
export function deletePPMBotMemoriesByTopic(
|
|
1083
1083
|
project: string,
|
|
1084
1084
|
topic: string,
|
|
1085
1085
|
): number {
|
|
@@ -1097,7 +1097,7 @@ export function deleteClawBotMemoriesByTopic(
|
|
|
1097
1097
|
return matches.length;
|
|
1098
1098
|
}
|
|
1099
1099
|
|
|
1100
|
-
export function
|
|
1100
|
+
export function decayPPMBotMemories(): void {
|
|
1101
1101
|
getDb().query(
|
|
1102
1102
|
`UPDATE clawbot_memories
|
|
1103
1103
|
SET importance = importance * 0.95,
|
|
@@ -1112,7 +1112,7 @@ export function decayClawBotMemories(): void {
|
|
|
1112
1112
|
}
|
|
1113
1113
|
|
|
1114
1114
|
// ---------------------------------------------------------------------------
|
|
1115
|
-
//
|
|
1115
|
+
// PPMBot pairing helpers
|
|
1116
1116
|
// ---------------------------------------------------------------------------
|
|
1117
1117
|
|
|
1118
1118
|
export function createPairingRequest(
|
|
@@ -1147,22 +1147,22 @@ export function revokePairing(chatId: string): void {
|
|
|
1147
1147
|
).run(chatId);
|
|
1148
1148
|
}
|
|
1149
1149
|
|
|
1150
|
-
export function getPairingByCode(code: string):
|
|
1150
|
+
export function getPairingByCode(code: string): PPMBotPairedChat | null {
|
|
1151
1151
|
return getDb().query(
|
|
1152
1152
|
"SELECT * FROM clawbot_paired_chats WHERE pairing_code = ? AND status = 'pending'",
|
|
1153
|
-
).get(code) as
|
|
1153
|
+
).get(code) as PPMBotPairedChat | null;
|
|
1154
1154
|
}
|
|
1155
1155
|
|
|
1156
|
-
export function getPairingByChatId(chatId: string):
|
|
1156
|
+
export function getPairingByChatId(chatId: string): PPMBotPairedChat | null {
|
|
1157
1157
|
return getDb().query(
|
|
1158
1158
|
"SELECT * FROM clawbot_paired_chats WHERE telegram_chat_id = ?",
|
|
1159
|
-
).get(chatId) as
|
|
1159
|
+
).get(chatId) as PPMBotPairedChat | null;
|
|
1160
1160
|
}
|
|
1161
1161
|
|
|
1162
|
-
export function listPairedChats():
|
|
1162
|
+
export function listPairedChats(): PPMBotPairedChat[] {
|
|
1163
1163
|
return getDb().query(
|
|
1164
1164
|
"SELECT * FROM clawbot_paired_chats WHERE status != 'revoked' ORDER BY created_at DESC",
|
|
1165
|
-
).all() as
|
|
1165
|
+
).all() as PPMBotPairedChat[];
|
|
1166
1166
|
}
|
|
1167
1167
|
|
|
1168
1168
|
export function isPairedChat(chatId: string): boolean {
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
insertPPMBotMemory,
|
|
3
|
+
searchPPMBotMemories,
|
|
4
|
+
getPPMBotMemories,
|
|
5
|
+
supersedePPMBotMemory,
|
|
6
|
+
deletePPMBotMemoriesByTopic,
|
|
7
|
+
decayPPMBotMemories,
|
|
8
8
|
getDb,
|
|
9
9
|
} from "../db.service.ts";
|
|
10
10
|
import { configService } from "../config.service.ts";
|
|
11
11
|
import type {
|
|
12
|
-
|
|
12
|
+
PPMBotMemoryCategory,
|
|
13
13
|
MemoryRecallResult,
|
|
14
|
-
} from "../../types/
|
|
14
|
+
} from "../../types/ppmbot.ts";
|
|
15
15
|
import type { ProjectConfig } from "../../types/config.ts";
|
|
16
16
|
|
|
17
17
|
/** Max memories per project before pruning */
|
|
@@ -20,11 +20,11 @@ const MAX_MEMORIES_PER_PROJECT = 500;
|
|
|
20
20
|
/** Fact extracted from AI response */
|
|
21
21
|
interface ExtractedFact {
|
|
22
22
|
content: string;
|
|
23
|
-
category:
|
|
23
|
+
category: PPMBotMemoryCategory;
|
|
24
24
|
importance?: number;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export class
|
|
27
|
+
export class PPMBotMemory {
|
|
28
28
|
/**
|
|
29
29
|
* Recall relevant memories for a project.
|
|
30
30
|
* If query provided, use FTS5 search. Otherwise return top by importance.
|
|
@@ -34,7 +34,7 @@ export class ClawBotMemory {
|
|
|
34
34
|
const sanitized = this.sanitizeFtsQuery(query);
|
|
35
35
|
if (sanitized) {
|
|
36
36
|
try {
|
|
37
|
-
const results =
|
|
37
|
+
const results = searchPPMBotMemories(project, sanitized, limit);
|
|
38
38
|
return results.map((r) => ({
|
|
39
39
|
id: r.id,
|
|
40
40
|
content: r.content,
|
|
@@ -49,7 +49,7 @@ export class ClawBotMemory {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
const rows =
|
|
52
|
+
const rows = getPPMBotMemories(project, limit);
|
|
53
53
|
return rows.map((r) => ({
|
|
54
54
|
id: r.id,
|
|
55
55
|
content: r.content,
|
|
@@ -94,7 +94,7 @@ export class ClawBotMemory {
|
|
|
94
94
|
|
|
95
95
|
const existingId = this.findSimilar(project, fact.content);
|
|
96
96
|
|
|
97
|
-
const newId =
|
|
97
|
+
const newId = insertPPMBotMemory(
|
|
98
98
|
project,
|
|
99
99
|
fact.content.trim(),
|
|
100
100
|
fact.category || "fact",
|
|
@@ -103,7 +103,7 @@ export class ClawBotMemory {
|
|
|
103
103
|
);
|
|
104
104
|
|
|
105
105
|
if (existingId) {
|
|
106
|
-
|
|
106
|
+
supersedePPMBotMemory(existingId, newId);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
inserted++;
|
|
@@ -117,13 +117,13 @@ export class ClawBotMemory {
|
|
|
117
117
|
saveOne(
|
|
118
118
|
project: string,
|
|
119
119
|
content: string,
|
|
120
|
-
category:
|
|
120
|
+
category: PPMBotMemoryCategory = "fact",
|
|
121
121
|
sessionId?: string,
|
|
122
122
|
): number {
|
|
123
123
|
const existingId = this.findSimilar(project, content);
|
|
124
|
-
const newId =
|
|
124
|
+
const newId = insertPPMBotMemory(project, content.trim(), category, 1.0, sessionId);
|
|
125
125
|
if (existingId) {
|
|
126
|
-
|
|
126
|
+
supersedePPMBotMemory(existingId, newId);
|
|
127
127
|
}
|
|
128
128
|
return newId;
|
|
129
129
|
}
|
|
@@ -132,12 +132,12 @@ export class ClawBotMemory {
|
|
|
132
132
|
forget(project: string, topic: string): number {
|
|
133
133
|
const sanitized = this.sanitizeFtsQuery(topic);
|
|
134
134
|
if (!sanitized) return 0;
|
|
135
|
-
return
|
|
135
|
+
return deletePPMBotMemoriesByTopic(project, sanitized);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
/** Get summary of all active memories for a project */
|
|
139
139
|
getSummary(project: string, limit = 30): MemoryRecallResult[] {
|
|
140
|
-
const rows =
|
|
140
|
+
const rows = getPPMBotMemories(project, limit);
|
|
141
141
|
return rows.map((r) => ({
|
|
142
142
|
id: r.id,
|
|
143
143
|
content: r.content,
|
|
@@ -214,7 +214,7 @@ If nothing worth remembering, return []`;
|
|
|
214
214
|
}))
|
|
215
215
|
.filter((f) => f.content.length > 0);
|
|
216
216
|
} catch {
|
|
217
|
-
console.warn("[
|
|
217
|
+
console.warn("[ppmbot-memory] Failed to parse extraction response");
|
|
218
218
|
return [];
|
|
219
219
|
}
|
|
220
220
|
}
|
|
@@ -225,7 +225,7 @@ If nothing worth remembering, return []`;
|
|
|
225
225
|
*/
|
|
226
226
|
extractiveMemoryFallback(conversationText: string): ExtractedFact[] {
|
|
227
227
|
const facts: ExtractedFact[] = [];
|
|
228
|
-
const patterns: Array<{ re: RegExp; category:
|
|
228
|
+
const patterns: Array<{ re: RegExp; category: PPMBotMemoryCategory }> = [
|
|
229
229
|
{ re: /(?:decided|chose|went with|picked|selected)\s+(.{10,100})/gi, category: "decision" },
|
|
230
230
|
{ re: /(?:prefer|always use|like to|rather)\s+(.{10,80})/gi, category: "preference" },
|
|
231
231
|
{ re: /(?:uses?|built with|stack is|powered by|database is)\s+(.{5,80})/gi, category: "architecture" },
|
|
@@ -246,9 +246,9 @@ If nothing worth remembering, return []`;
|
|
|
246
246
|
/** Run importance decay on old memories */
|
|
247
247
|
runDecay(): void {
|
|
248
248
|
try {
|
|
249
|
-
|
|
249
|
+
decayPPMBotMemories();
|
|
250
250
|
} catch (err) {
|
|
251
|
-
console.error("[
|
|
251
|
+
console.error("[ppmbot-memory] Decay error:", (err as Error).message);
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
|
|
@@ -272,7 +272,7 @@ If nothing worth remembering, return []`;
|
|
|
272
272
|
)`,
|
|
273
273
|
).run(project, excess);
|
|
274
274
|
} catch (err) {
|
|
275
|
-
console.error("[
|
|
275
|
+
console.error("[ppmbot-memory] Prune error:", (err as Error).message);
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
|
|
@@ -301,7 +301,7 @@ If nothing worth remembering, return []`;
|
|
|
301
301
|
if (!query) return null;
|
|
302
302
|
|
|
303
303
|
try {
|
|
304
|
-
const results =
|
|
304
|
+
const results = searchPPMBotMemories(project, query, 3);
|
|
305
305
|
if (results.length > 0 && results[0]!.rank < -5) {
|
|
306
306
|
return results[0]!.id;
|
|
307
307
|
}
|
|
@@ -322,12 +322,12 @@ If nothing worth remembering, return []`;
|
|
|
322
322
|
}
|
|
323
323
|
|
|
324
324
|
/** Validate category string against known values */
|
|
325
|
-
private validateCategory(cat: string):
|
|
326
|
-
const valid:
|
|
325
|
+
private validateCategory(cat: string): PPMBotMemoryCategory {
|
|
326
|
+
const valid: PPMBotMemoryCategory[] = [
|
|
327
327
|
"fact", "decision", "preference", "architecture", "issue",
|
|
328
328
|
];
|
|
329
|
-
return valid.includes(cat as
|
|
330
|
-
? (cat as
|
|
329
|
+
return valid.includes(cat as PPMBotMemoryCategory)
|
|
330
|
+
? (cat as PPMBotMemoryCategory)
|
|
331
331
|
: "fact";
|
|
332
332
|
}
|
|
333
333
|
}
|
|
@@ -6,21 +6,21 @@ import {
|
|
|
6
6
|
createPairingRequest,
|
|
7
7
|
approvePairing,
|
|
8
8
|
} from "../db.service.ts";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { streamToTelegram } from "./
|
|
13
|
-
import { escapeHtml } from "./
|
|
14
|
-
import type { TelegramUpdate,
|
|
15
|
-
import type {
|
|
9
|
+
import { PPMBotTelegram } from "./ppmbot-telegram.ts";
|
|
10
|
+
import { PPMBotSessionManager } from "./ppmbot-session.ts";
|
|
11
|
+
import { PPMBotMemory } from "./ppmbot-memory.ts";
|
|
12
|
+
import { streamToTelegram } from "./ppmbot-streamer.ts";
|
|
13
|
+
import { escapeHtml } from "./ppmbot-formatter.ts";
|
|
14
|
+
import type { TelegramUpdate, PPMBotCommand } from "../../types/ppmbot.ts";
|
|
15
|
+
import type { PPMBotConfig, TelegramConfig, PermissionMode } from "../../types/config.ts";
|
|
16
16
|
import type { SendMessageOpts } from "../../types/chat.ts";
|
|
17
17
|
|
|
18
18
|
const CONTEXT_WINDOW_THRESHOLD = 80;
|
|
19
19
|
|
|
20
|
-
class
|
|
21
|
-
private telegram:
|
|
22
|
-
private sessions = new
|
|
23
|
-
private memory = new
|
|
20
|
+
class PPMBotService {
|
|
21
|
+
private telegram: PPMBotTelegram | null = null;
|
|
22
|
+
private sessions = new PPMBotSessionManager();
|
|
23
|
+
private memory = new PPMBotMemory();
|
|
24
24
|
private running = false;
|
|
25
25
|
|
|
26
26
|
/** Debounce timers per chatId */
|
|
@@ -39,20 +39,20 @@ class ClawBotService {
|
|
|
39
39
|
// ── Lifecycle ─────────────────────────────────────────────────
|
|
40
40
|
|
|
41
41
|
async start(): Promise<void> {
|
|
42
|
-
const
|
|
43
|
-
if (!
|
|
44
|
-
console.log("[
|
|
42
|
+
const ppmbotConfig = this.getConfig();
|
|
43
|
+
if (!ppmbotConfig?.enabled) {
|
|
44
|
+
console.log("[ppmbot] Disabled in config");
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
const telegramConfig = configService.get("telegram") as TelegramConfig | undefined;
|
|
49
49
|
if (!telegramConfig?.bot_token) {
|
|
50
|
-
console.log("[
|
|
50
|
+
console.log("[ppmbot] No bot token configured");
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
try {
|
|
55
|
-
this.telegram = new
|
|
55
|
+
this.telegram = new PPMBotTelegram(telegramConfig.bot_token);
|
|
56
56
|
this.running = true;
|
|
57
57
|
|
|
58
58
|
// Run memory decay on startup
|
|
@@ -61,9 +61,9 @@ class ClawBotService {
|
|
|
61
61
|
// Start polling (non-blocking)
|
|
62
62
|
this.telegram.startPolling((update) => this.handleUpdate(update));
|
|
63
63
|
|
|
64
|
-
console.log("[
|
|
64
|
+
console.log("[ppmbot] Started");
|
|
65
65
|
} catch (err) {
|
|
66
|
-
console.error("[
|
|
66
|
+
console.error("[ppmbot] Start failed:", (err as Error).message);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
@@ -78,7 +78,7 @@ class ClawBotService {
|
|
|
78
78
|
this.processing.clear();
|
|
79
79
|
this.messageQueue.clear();
|
|
80
80
|
|
|
81
|
-
console.log("[
|
|
81
|
+
console.log("[ppmbot] Stopped");
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
get isRunning(): boolean {
|
|
@@ -89,7 +89,7 @@ class ClawBotService {
|
|
|
89
89
|
async notifyPairingApproved(chatId: string): Promise<void> {
|
|
90
90
|
await this.telegram?.sendMessage(
|
|
91
91
|
Number(chatId),
|
|
92
|
-
"✅ Pairing approved! You can now chat with
|
|
92
|
+
"✅ Pairing approved! You can now chat with PPMBot.\n\nSend /start to begin.",
|
|
93
93
|
);
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -113,14 +113,14 @@ class ClawBotService {
|
|
|
113
113
|
createPairingRequest(chatId, String(userId), displayName, code);
|
|
114
114
|
await this.telegram!.sendMessage(
|
|
115
115
|
Number(chatId),
|
|
116
|
-
`🔐 Pairing required.\n\nYour pairing code: <code>${code}</code>\n\nEnter this code in PPM Settings →
|
|
116
|
+
`🔐 Pairing required.\n\nYour pairing code: <code>${code}</code>\n\nEnter this code in PPM Settings → PPMBot → Pair Device to approve access.`,
|
|
117
117
|
);
|
|
118
118
|
return;
|
|
119
119
|
}
|
|
120
120
|
if (pairing.status === "pending") {
|
|
121
121
|
await this.telegram!.sendMessage(
|
|
122
122
|
Number(chatId),
|
|
123
|
-
`⏳ Pairing pending approval.\n\nCode: <code>${pairing.pairing_code}</code>\nAsk the PPM owner to approve in Settings →
|
|
123
|
+
`⏳ Pairing pending approval.\n\nCode: <code>${pairing.pairing_code}</code>\nAsk the PPM owner to approve in Settings → PPMBot.`,
|
|
124
124
|
);
|
|
125
125
|
return;
|
|
126
126
|
}
|
|
@@ -130,7 +130,7 @@ class ClawBotService {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
// Try parsing as command
|
|
133
|
-
const command =
|
|
133
|
+
const command = PPMBotTelegram.parseCommand(message);
|
|
134
134
|
if (command) {
|
|
135
135
|
await this.handleCommand(command);
|
|
136
136
|
return;
|
|
@@ -145,7 +145,7 @@ class ClawBotService {
|
|
|
145
145
|
|
|
146
146
|
// ── Command Handlers ────────────────────────────────────────────
|
|
147
147
|
|
|
148
|
-
private async handleCommand(cmd:
|
|
148
|
+
private async handleCommand(cmd: PPMBotCommand): Promise<void> {
|
|
149
149
|
const chatId = String(cmd.chatId);
|
|
150
150
|
const tg = this.telegram!;
|
|
151
151
|
|
|
@@ -174,18 +174,35 @@ class ClawBotService {
|
|
|
174
174
|
|
|
175
175
|
private async cmdStart(chatId: string): Promise<void> {
|
|
176
176
|
const projects = this.sessions.getProjectNames();
|
|
177
|
-
let text = "<b>🤖
|
|
177
|
+
let text = "<b>🤖 PPMBot</b>\n\n";
|
|
178
|
+
text += "Hey! I'm your AI coding assistant, right here in Telegram.\n";
|
|
179
|
+
text += "Ask me anything — code questions, debugging, project tasks.\n\n";
|
|
178
180
|
if (projects.length) {
|
|
179
|
-
text += "<b>
|
|
181
|
+
text += "<b>Your projects:</b>\n";
|
|
180
182
|
for (const name of projects) {
|
|
181
|
-
text +=
|
|
183
|
+
text += ` • <code>${escapeHtml(name)}</code>\n`;
|
|
182
184
|
}
|
|
183
|
-
text += "\nSwitch
|
|
185
|
+
text += "\nSwitch: /project <name>";
|
|
184
186
|
} else {
|
|
185
|
-
text += "No projects configured
|
|
187
|
+
text += "No projects configured — I'll use a default workspace.";
|
|
186
188
|
}
|
|
187
|
-
text += "\n\
|
|
189
|
+
text += "\n\nJust send a message to start chatting, or /help for commands.";
|
|
188
190
|
await this.telegram!.sendMessage(Number(chatId), text);
|
|
191
|
+
|
|
192
|
+
// Identity onboarding: if no identity memories exist, ask user
|
|
193
|
+
const identityMemories = this.memory.recall("_global", "user identity name role");
|
|
194
|
+
if (identityMemories.length === 0) {
|
|
195
|
+
await this.telegram!.sendMessage(
|
|
196
|
+
Number(chatId),
|
|
197
|
+
"📝 <b>Quick intro?</b>\n\n" +
|
|
198
|
+
"I don't know much about you yet! Tell me:\n" +
|
|
199
|
+
"• Your name\n" +
|
|
200
|
+
"• What you work on (language, stack, role)\n" +
|
|
201
|
+
"• Preferred response language (English, Vietnamese, etc.)\n\n" +
|
|
202
|
+
"I'll remember your preferences for future chats.\n" +
|
|
203
|
+
"Or skip this and just start chatting!",
|
|
204
|
+
);
|
|
205
|
+
}
|
|
189
206
|
}
|
|
190
207
|
|
|
191
208
|
private async cmdProject(chatId: string, args: string): Promise<void> {
|
|
@@ -310,7 +327,7 @@ class ClawBotService {
|
|
|
310
327
|
}
|
|
311
328
|
|
|
312
329
|
private async cmdHelp(chatId: string): Promise<void> {
|
|
313
|
-
const text = `<b>
|
|
330
|
+
const text = `<b>PPMBot Commands</b>
|
|
314
331
|
|
|
315
332
|
/start — Greeting + list projects
|
|
316
333
|
/project <name> — Switch project
|
|
@@ -421,7 +438,7 @@ class ClawBotService {
|
|
|
421
438
|
await this.rotateSession(chatId, session.projectName);
|
|
422
439
|
}
|
|
423
440
|
} catch (err) {
|
|
424
|
-
console.error(`[
|
|
441
|
+
console.error(`[ppmbot] processMessage error for ${chatId}:`, (err as Error).message);
|
|
425
442
|
await this.telegram?.sendMessage(
|
|
426
443
|
Number(chatId),
|
|
427
444
|
`❌ ${escapeHtml((err as Error).message)}`,
|
|
@@ -462,15 +479,15 @@ class ClawBotService {
|
|
|
462
479
|
const facts = this.memory.parseExtractionResponse(responseText);
|
|
463
480
|
if (facts.length > 0) {
|
|
464
481
|
const count = this.memory.save(session.projectName, facts, session.sessionId);
|
|
465
|
-
console.log(`[
|
|
482
|
+
console.log(`[ppmbot] Saved ${count} memories for ${session.projectName}`);
|
|
466
483
|
} else {
|
|
467
484
|
// Fallback: regex-based extraction
|
|
468
485
|
// Note: we don't have conversation history text here easily,
|
|
469
486
|
// so regex fallback only triggers when AI extraction fails
|
|
470
|
-
console.log("[
|
|
487
|
+
console.log("[ppmbot] No memories extracted via AI");
|
|
471
488
|
}
|
|
472
489
|
} catch (err) {
|
|
473
|
-
console.warn("[
|
|
490
|
+
console.warn("[ppmbot] Memory save failed:", (err as Error).message);
|
|
474
491
|
}
|
|
475
492
|
}
|
|
476
493
|
|
|
@@ -492,9 +509,9 @@ class ClawBotService {
|
|
|
492
509
|
return Array.from(bytes, (b) => chars[b % chars.length]).join("");
|
|
493
510
|
}
|
|
494
511
|
|
|
495
|
-
private getConfig():
|
|
496
|
-
return configService.get("clawbot") as
|
|
512
|
+
private getConfig(): PPMBotConfig | undefined {
|
|
513
|
+
return configService.get("clawbot") as PPMBotConfig | undefined;
|
|
497
514
|
}
|
|
498
515
|
}
|
|
499
516
|
|
|
500
|
-
export const
|
|
517
|
+
export const ppmbotService = new PPMBotService();
|