@hienlh/ppm 0.9.30 → 0.9.32
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 +18 -5
- package/dist/web/assets/{browser-tab-D0o6oSlt.js → browser-tab-B9nNKjZX.js} +1 -1
- package/dist/web/assets/{chat-tab-Boo_H1k9.js → chat-tab-6XGhEKaC.js} +2 -2
- package/dist/web/assets/{code-editor-DayGetAZ.js → code-editor-DMZMpzt2.js} +1 -1
- package/dist/web/assets/{database-viewer-CaxAp1qK.js → database-viewer-CnP1FFS2.js} +1 -1
- package/dist/web/assets/{diff-viewer-BvEXe_B4.js → diff-viewer-Cvwd0XBO.js} +1 -1
- package/dist/web/assets/{extension-webview-6XProGzB.js → extension-webview-DkhsRepr.js} +1 -1
- package/dist/web/assets/{git-graph-CvgIIt2x.js → git-graph-C3670Nxm.js} +1 -1
- package/dist/web/assets/index-CcFDEPCo.css +2 -0
- package/dist/web/assets/index-DjIQL8ar.js +30 -0
- package/dist/web/assets/keybindings-store-DHh6rwm-.js +1 -0
- package/dist/web/assets/{markdown-renderer-UCGYJpI-.js → markdown-renderer-Co04dDdI.js} +1 -1
- package/dist/web/assets/{postgres-viewer-TV6kyo6B.js → postgres-viewer-D8K1qnnA.js} +1 -1
- package/dist/web/assets/{settings-tab-EziN5Pco.js → settings-tab-64ODAeQZ.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-D7LPvSkU.js → sqlite-viewer-ClX7FICB.js} +1 -1
- package/dist/web/assets/{terminal-tab-C7Hdv1nq.js → terminal-tab-Dw4IKWGM.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-CI4vTUsh.js → use-monaco-theme-DA7EyR70.js} +1 -1
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +33 -3
- package/docs/project-changelog.md +47 -0
- package/docs/project-roadmap.md +14 -7
- package/docs/streaming-input-guide.md +267 -0
- package/docs/system-architecture.md +65 -2
- package/package.json +1 -1
- package/snapshot-state.md +1526 -0
- package/src/server/index.ts +8 -1
- package/src/server/routes/settings.ts +72 -1
- package/src/services/clawbot/clawbot-formatter.ts +88 -0
- package/src/services/clawbot/clawbot-memory.ts +333 -0
- package/src/services/clawbot/clawbot-service.ts +500 -0
- package/src/services/clawbot/clawbot-session.ts +188 -0
- package/src/services/clawbot/clawbot-streamer.ts +245 -0
- package/src/services/clawbot/clawbot-telegram.ts +251 -0
- package/src/services/config.service.ts +1 -1
- package/src/services/db.service.ts +279 -1
- package/src/services/supervisor.ts +10 -0
- package/src/types/clawbot.ts +103 -0
- package/src/types/config.ts +22 -0
- package/src/web/components/chat/chat-history-bar.tsx +8 -3
- package/src/web/components/settings/clawbot-settings-section.tsx +270 -0
- package/src/web/components/settings/settings-tab.tsx +4 -1
- package/test-session-ops.mjs +444 -0
- package/test-tokens.mjs +212 -0
- package/dist/web/assets/index-CJvp0DJT.css +0 -2
- package/dist/web/assets/index-DocPzjV6.js +0 -30
- package/dist/web/assets/keybindings-store-2KURy8S3.js +0 -1
|
@@ -4,7 +4,7 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import { mkdirSync, existsSync } from "node:fs";
|
|
5
5
|
|
|
6
6
|
const PPM_DIR = process.env.PPM_HOME || resolve(homedir(), ".ppm");
|
|
7
|
-
const CURRENT_SCHEMA_VERSION =
|
|
7
|
+
const CURRENT_SCHEMA_VERSION = 13;
|
|
8
8
|
|
|
9
9
|
let db: Database | null = null;
|
|
10
10
|
let dbProfile: string | null = null;
|
|
@@ -313,6 +313,80 @@ function runMigrations(database: Database): void {
|
|
|
313
313
|
PRAGMA user_version = 12;
|
|
314
314
|
`);
|
|
315
315
|
}
|
|
316
|
+
|
|
317
|
+
if (current < 13) {
|
|
318
|
+
database.exec(`
|
|
319
|
+
CREATE TABLE IF NOT EXISTS clawbot_sessions (
|
|
320
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
321
|
+
telegram_chat_id TEXT NOT NULL,
|
|
322
|
+
session_id TEXT NOT NULL,
|
|
323
|
+
provider_id TEXT NOT NULL DEFAULT 'claude',
|
|
324
|
+
project_name TEXT NOT NULL,
|
|
325
|
+
project_path TEXT NOT NULL,
|
|
326
|
+
is_active INTEGER NOT NULL DEFAULT 1,
|
|
327
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
328
|
+
last_message_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
CREATE INDEX IF NOT EXISTS idx_clawbot_sessions_chat
|
|
332
|
+
ON clawbot_sessions(telegram_chat_id, is_active);
|
|
333
|
+
CREATE INDEX IF NOT EXISTS idx_clawbot_sessions_session
|
|
334
|
+
ON clawbot_sessions(session_id);
|
|
335
|
+
|
|
336
|
+
CREATE TABLE IF NOT EXISTS clawbot_memories (
|
|
337
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
338
|
+
project TEXT NOT NULL,
|
|
339
|
+
content TEXT NOT NULL,
|
|
340
|
+
category TEXT NOT NULL DEFAULT 'fact',
|
|
341
|
+
importance REAL NOT NULL DEFAULT 1.0,
|
|
342
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
343
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
344
|
+
session_id TEXT,
|
|
345
|
+
superseded_by INTEGER REFERENCES clawbot_memories(id)
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
CREATE INDEX IF NOT EXISTS idx_clawbot_memories_project
|
|
349
|
+
ON clawbot_memories(project, superseded_by);
|
|
350
|
+
CREATE INDEX IF NOT EXISTS idx_clawbot_memories_importance
|
|
351
|
+
ON clawbot_memories(importance DESC);
|
|
352
|
+
|
|
353
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS clawbot_memories_fts USING fts5(
|
|
354
|
+
content,
|
|
355
|
+
content='clawbot_memories',
|
|
356
|
+
content_rowid='id'
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
-- FTS5 sync triggers
|
|
360
|
+
CREATE TRIGGER IF NOT EXISTS clawbot_memories_ai AFTER INSERT ON clawbot_memories BEGIN
|
|
361
|
+
INSERT INTO clawbot_memories_fts(rowid, content) VALUES (new.id, new.content);
|
|
362
|
+
END;
|
|
363
|
+
|
|
364
|
+
CREATE TRIGGER IF NOT EXISTS clawbot_memories_ad AFTER DELETE ON clawbot_memories BEGIN
|
|
365
|
+
INSERT INTO clawbot_memories_fts(clawbot_memories_fts, rowid, content) VALUES('delete', old.id, old.content);
|
|
366
|
+
END;
|
|
367
|
+
|
|
368
|
+
CREATE TRIGGER IF NOT EXISTS clawbot_memories_au AFTER UPDATE ON clawbot_memories BEGIN
|
|
369
|
+
INSERT INTO clawbot_memories_fts(clawbot_memories_fts, rowid, content) VALUES('delete', old.id, old.content);
|
|
370
|
+
INSERT INTO clawbot_memories_fts(rowid, content) VALUES (new.id, new.content);
|
|
371
|
+
END;
|
|
372
|
+
|
|
373
|
+
CREATE TABLE IF NOT EXISTS clawbot_paired_chats (
|
|
374
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
375
|
+
telegram_chat_id TEXT NOT NULL UNIQUE,
|
|
376
|
+
telegram_user_id TEXT,
|
|
377
|
+
display_name TEXT,
|
|
378
|
+
pairing_code TEXT,
|
|
379
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
380
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
381
|
+
approved_at INTEGER
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
CREATE INDEX IF NOT EXISTS idx_clawbot_paired_status
|
|
385
|
+
ON clawbot_paired_chats(status);
|
|
386
|
+
|
|
387
|
+
PRAGMA user_version = 13;
|
|
388
|
+
`);
|
|
389
|
+
}
|
|
316
390
|
}
|
|
317
391
|
|
|
318
392
|
// ---------------------------------------------------------------------------
|
|
@@ -894,5 +968,209 @@ export function deleteExtensionStorage(extId: string): void {
|
|
|
894
968
|
getDb().query("DELETE FROM extension_storage WHERE ext_id = ?").run(extId);
|
|
895
969
|
}
|
|
896
970
|
|
|
971
|
+
// ---------------------------------------------------------------------------
|
|
972
|
+
// ClawBot session helpers
|
|
973
|
+
// ---------------------------------------------------------------------------
|
|
974
|
+
|
|
975
|
+
import type { ClawBotSessionRow, ClawBotMemoryRow, ClawBotPairedChat } from "../types/clawbot.ts";
|
|
976
|
+
|
|
977
|
+
export function getActiveClawBotSession(
|
|
978
|
+
telegramChatId: string,
|
|
979
|
+
projectName: string,
|
|
980
|
+
): ClawBotSessionRow | null {
|
|
981
|
+
return getDb().query(
|
|
982
|
+
`SELECT * FROM clawbot_sessions
|
|
983
|
+
WHERE telegram_chat_id = ? AND project_name = ? AND is_active = 1
|
|
984
|
+
ORDER BY last_message_at DESC LIMIT 1`,
|
|
985
|
+
).get(telegramChatId, projectName) as ClawBotSessionRow | null;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
export function createClawBotSession(
|
|
989
|
+
telegramChatId: string,
|
|
990
|
+
sessionId: string,
|
|
991
|
+
providerId: string,
|
|
992
|
+
projectName: string,
|
|
993
|
+
projectPath: string,
|
|
994
|
+
): void {
|
|
995
|
+
getDb().query(
|
|
996
|
+
`INSERT INTO clawbot_sessions
|
|
997
|
+
(telegram_chat_id, session_id, provider_id, project_name, project_path)
|
|
998
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
999
|
+
).run(telegramChatId, sessionId, providerId, projectName, projectPath);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
export function deactivateClawBotSession(sessionId: string): void {
|
|
1003
|
+
getDb().query(
|
|
1004
|
+
"UPDATE clawbot_sessions SET is_active = 0 WHERE session_id = ?",
|
|
1005
|
+
).run(sessionId);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
export function touchClawBotSession(sessionId: string): void {
|
|
1009
|
+
getDb().query(
|
|
1010
|
+
"UPDATE clawbot_sessions SET last_message_at = unixepoch() WHERE session_id = ?",
|
|
1011
|
+
).run(sessionId);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
export function getRecentClawBotSessions(
|
|
1015
|
+
telegramChatId: string,
|
|
1016
|
+
limit = 10,
|
|
1017
|
+
): ClawBotSessionRow[] {
|
|
1018
|
+
return getDb().query(
|
|
1019
|
+
`SELECT * FROM clawbot_sessions
|
|
1020
|
+
WHERE telegram_chat_id = ?
|
|
1021
|
+
ORDER BY last_message_at DESC LIMIT ?`,
|
|
1022
|
+
).all(telegramChatId, limit) as ClawBotSessionRow[];
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// ---------------------------------------------------------------------------
|
|
1026
|
+
// ClawBot memory helpers
|
|
1027
|
+
// ---------------------------------------------------------------------------
|
|
1028
|
+
|
|
1029
|
+
export function insertClawBotMemory(
|
|
1030
|
+
project: string,
|
|
1031
|
+
content: string,
|
|
1032
|
+
category: string,
|
|
1033
|
+
importance: number,
|
|
1034
|
+
sessionId?: string,
|
|
1035
|
+
): number {
|
|
1036
|
+
const result = getDb().query(
|
|
1037
|
+
`INSERT INTO clawbot_memories (project, content, category, importance, session_id)
|
|
1038
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
1039
|
+
).run(project, content, category, importance, sessionId ?? null);
|
|
1040
|
+
return Number(result.lastInsertRowid);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
export function searchClawBotMemories(
|
|
1044
|
+
project: string,
|
|
1045
|
+
query: string,
|
|
1046
|
+
limit = 20,
|
|
1047
|
+
): Array<ClawBotMemoryRow & { rank: number }> {
|
|
1048
|
+
return getDb().query(
|
|
1049
|
+
`SELECT m.*, fts.rank
|
|
1050
|
+
FROM clawbot_memories m
|
|
1051
|
+
JOIN clawbot_memories_fts fts ON m.id = fts.rowid
|
|
1052
|
+
WHERE clawbot_memories_fts MATCH ?
|
|
1053
|
+
AND m.project IN (?, '_global')
|
|
1054
|
+
AND m.superseded_by IS NULL
|
|
1055
|
+
ORDER BY fts.rank
|
|
1056
|
+
LIMIT ?`,
|
|
1057
|
+
).all(query, project, limit) as Array<ClawBotMemoryRow & { rank: number }>;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
export function getClawBotMemories(
|
|
1061
|
+
project: string,
|
|
1062
|
+
limit = 20,
|
|
1063
|
+
): ClawBotMemoryRow[] {
|
|
1064
|
+
return getDb().query(
|
|
1065
|
+
`SELECT * FROM clawbot_memories
|
|
1066
|
+
WHERE project IN (?, '_global')
|
|
1067
|
+
AND superseded_by IS NULL
|
|
1068
|
+
ORDER BY importance DESC, updated_at DESC
|
|
1069
|
+
LIMIT ?`,
|
|
1070
|
+
).all(project, limit) as ClawBotMemoryRow[];
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
export function supersedeClawBotMemory(
|
|
1074
|
+
oldId: number,
|
|
1075
|
+
newId: number,
|
|
1076
|
+
): void {
|
|
1077
|
+
getDb().query(
|
|
1078
|
+
"UPDATE clawbot_memories SET superseded_by = ? WHERE id = ?",
|
|
1079
|
+
).run(newId, oldId);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
export function deleteClawBotMemoriesByTopic(
|
|
1083
|
+
project: string,
|
|
1084
|
+
topic: string,
|
|
1085
|
+
): number {
|
|
1086
|
+
const matches = getDb().query(
|
|
1087
|
+
`SELECT m.id FROM clawbot_memories m
|
|
1088
|
+
JOIN clawbot_memories_fts fts ON m.id = fts.rowid
|
|
1089
|
+
WHERE clawbot_memories_fts MATCH ?
|
|
1090
|
+
AND m.project IN (?, '_global')
|
|
1091
|
+
AND m.superseded_by IS NULL`,
|
|
1092
|
+
).all(topic, project) as { id: number }[];
|
|
1093
|
+
|
|
1094
|
+
for (const row of matches) {
|
|
1095
|
+
getDb().query("DELETE FROM clawbot_memories WHERE id = ?").run(row.id);
|
|
1096
|
+
}
|
|
1097
|
+
return matches.length;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
export function decayClawBotMemories(): void {
|
|
1101
|
+
getDb().query(
|
|
1102
|
+
`UPDATE clawbot_memories
|
|
1103
|
+
SET importance = importance * 0.95,
|
|
1104
|
+
updated_at = unixepoch()
|
|
1105
|
+
WHERE superseded_by IS NULL
|
|
1106
|
+
AND category NOT IN ('preference', 'architecture')
|
|
1107
|
+
AND updated_at < unixepoch() - 604800`,
|
|
1108
|
+
).run();
|
|
1109
|
+
getDb().query(
|
|
1110
|
+
`DELETE FROM clawbot_memories WHERE importance < 0.1 AND superseded_by IS NULL`,
|
|
1111
|
+
).run();
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// ---------------------------------------------------------------------------
|
|
1115
|
+
// ClawBot pairing helpers
|
|
1116
|
+
// ---------------------------------------------------------------------------
|
|
1117
|
+
|
|
1118
|
+
export function createPairingRequest(
|
|
1119
|
+
chatId: string,
|
|
1120
|
+
userId: string,
|
|
1121
|
+
displayName: string,
|
|
1122
|
+
code: string,
|
|
1123
|
+
): void {
|
|
1124
|
+
getDb().query(
|
|
1125
|
+
`INSERT INTO clawbot_paired_chats (telegram_chat_id, telegram_user_id, display_name, pairing_code, status)
|
|
1126
|
+
VALUES (?, ?, ?, ?, 'pending')
|
|
1127
|
+
ON CONFLICT(telegram_chat_id) DO UPDATE SET
|
|
1128
|
+
telegram_user_id = excluded.telegram_user_id,
|
|
1129
|
+
display_name = excluded.display_name,
|
|
1130
|
+
pairing_code = excluded.pairing_code,
|
|
1131
|
+
status = 'pending',
|
|
1132
|
+
approved_at = NULL`,
|
|
1133
|
+
).run(chatId, userId, displayName, code);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
export function approvePairing(chatId: string): void {
|
|
1137
|
+
getDb().query(
|
|
1138
|
+
`UPDATE clawbot_paired_chats
|
|
1139
|
+
SET status = 'approved', pairing_code = NULL, approved_at = unixepoch()
|
|
1140
|
+
WHERE telegram_chat_id = ? AND status = 'pending'`,
|
|
1141
|
+
).run(chatId);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
export function revokePairing(chatId: string): void {
|
|
1145
|
+
getDb().query(
|
|
1146
|
+
"UPDATE clawbot_paired_chats SET status = 'revoked' WHERE telegram_chat_id = ?",
|
|
1147
|
+
).run(chatId);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
export function getPairingByCode(code: string): ClawBotPairedChat | null {
|
|
1151
|
+
return getDb().query(
|
|
1152
|
+
"SELECT * FROM clawbot_paired_chats WHERE pairing_code = ? AND status = 'pending'",
|
|
1153
|
+
).get(code) as ClawBotPairedChat | null;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
export function getPairingByChatId(chatId: string): ClawBotPairedChat | null {
|
|
1157
|
+
return getDb().query(
|
|
1158
|
+
"SELECT * FROM clawbot_paired_chats WHERE telegram_chat_id = ?",
|
|
1159
|
+
).get(chatId) as ClawBotPairedChat | null;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
export function listPairedChats(): ClawBotPairedChat[] {
|
|
1163
|
+
return getDb().query(
|
|
1164
|
+
"SELECT * FROM clawbot_paired_chats WHERE status != 'revoked' ORDER BY created_at DESC",
|
|
1165
|
+
).all() as ClawBotPairedChat[];
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
export function isPairedChat(chatId: string): boolean {
|
|
1169
|
+
const row = getDb().query(
|
|
1170
|
+
"SELECT 1 FROM clawbot_paired_chats WHERE telegram_chat_id = ? AND status = 'approved'",
|
|
1171
|
+
).get(chatId);
|
|
1172
|
+
return row != null;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
897
1175
|
// Auto-close on process exit
|
|
898
1176
|
process.on("beforeExit", closeDb);
|
|
@@ -9,6 +9,7 @@ import { resolve } from "node:path";
|
|
|
9
9
|
import { homedir } from "node:os";
|
|
10
10
|
import {
|
|
11
11
|
readFileSync, writeFileSync, existsSync, mkdirSync, openSync, appendFileSync,
|
|
12
|
+
unlinkSync,
|
|
12
13
|
} from "node:fs";
|
|
13
14
|
import { isCompiledBinary } from "./autostart-generator.ts";
|
|
14
15
|
|
|
@@ -30,6 +31,7 @@ const PPM_DIR = resolve(process.env.PPM_HOME || resolve(homedir(), ".ppm"));
|
|
|
30
31
|
const STATUS_FILE = resolve(PPM_DIR, "status.json");
|
|
31
32
|
const PID_FILE = resolve(PPM_DIR, "ppm.pid");
|
|
32
33
|
const LOG_FILE = resolve(PPM_DIR, "ppm.log");
|
|
34
|
+
const RESTARTING_FLAG = resolve(PPM_DIR, ".restarting");
|
|
33
35
|
|
|
34
36
|
// ─── State ─────────────────────────────────────────────────────────────
|
|
35
37
|
let serverChild: Subprocess | null = null;
|
|
@@ -417,6 +419,9 @@ async function selfReplace(): Promise<{ success: boolean; error?: string }> {
|
|
|
417
419
|
supervisorState = "upgrading";
|
|
418
420
|
updateStatus({ state: "upgrading" });
|
|
419
421
|
|
|
422
|
+
// Set restarting flag so server child's stopTunnel() skips killing the tunnel
|
|
423
|
+
try { writeFileSync(RESTARTING_FLAG, ""); } catch {}
|
|
424
|
+
|
|
420
425
|
// Kill server child to free the port; keep tunnel alive for domain continuity
|
|
421
426
|
log("INFO", "Stopping server before spawning new supervisor (tunnel kept alive)");
|
|
422
427
|
if (serverChild) { try { serverChild.kill(); } catch {} serverChild = null; }
|
|
@@ -456,6 +461,7 @@ async function selfReplace(): Promise<{ success: boolean; error?: string }> {
|
|
|
456
461
|
// Timeout — new supervisor didn't start, restore old supervisor
|
|
457
462
|
log("ERROR", "Self-replace timeout: new supervisor did not start");
|
|
458
463
|
try { child.kill(); } catch {}
|
|
464
|
+
try { unlinkSync(RESTARTING_FLAG); } catch {}
|
|
459
465
|
shuttingDown = false;
|
|
460
466
|
notifyStateChange("upgrading", "running", "upgrade_failed");
|
|
461
467
|
supervisorState = "running";
|
|
@@ -463,6 +469,7 @@ async function selfReplace(): Promise<{ success: boolean; error?: string }> {
|
|
|
463
469
|
return { success: false, error: "New supervisor failed to start within 30s" };
|
|
464
470
|
} catch (e) {
|
|
465
471
|
log("ERROR", `Self-replace error: ${e}`);
|
|
472
|
+
try { unlinkSync(RESTARTING_FLAG); } catch {}
|
|
466
473
|
shuttingDown = false;
|
|
467
474
|
notifyStateChange("upgrading", "running", "upgrade_failed");
|
|
468
475
|
supervisorState = "running";
|
|
@@ -631,6 +638,9 @@ export async function runSupervisor(opts: {
|
|
|
631
638
|
}) {
|
|
632
639
|
if (!existsSync(PPM_DIR)) mkdirSync(PPM_DIR, { recursive: true });
|
|
633
640
|
|
|
641
|
+
// Clean up restarting flag from previous upgrade/restart
|
|
642
|
+
try { unlinkSync(RESTARTING_FLAG); } catch {}
|
|
643
|
+
|
|
634
644
|
// Save original argv for self-replace
|
|
635
645
|
originalArgv = [...process.argv];
|
|
636
646
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/** Telegram update object (subset we care about) */
|
|
2
|
+
export interface TelegramUpdate {
|
|
3
|
+
update_id: number;
|
|
4
|
+
message?: TelegramMessage;
|
|
5
|
+
edited_message?: TelegramMessage;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface TelegramMessage {
|
|
9
|
+
message_id: number;
|
|
10
|
+
from?: { id: number; first_name: string; username?: string };
|
|
11
|
+
chat: { id: number; type: "private" | "group" | "supergroup" };
|
|
12
|
+
date: number;
|
|
13
|
+
text?: string;
|
|
14
|
+
caption?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Sent message result from Telegram API */
|
|
18
|
+
export interface TelegramSentMessage {
|
|
19
|
+
message_id: number;
|
|
20
|
+
chat: { id: number };
|
|
21
|
+
date: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** ClawBot session row from SQLite */
|
|
25
|
+
export interface ClawBotSessionRow {
|
|
26
|
+
id: number;
|
|
27
|
+
telegram_chat_id: string;
|
|
28
|
+
session_id: string;
|
|
29
|
+
provider_id: string;
|
|
30
|
+
project_name: string;
|
|
31
|
+
project_path: string;
|
|
32
|
+
is_active: number;
|
|
33
|
+
created_at: number;
|
|
34
|
+
last_message_at: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** ClawBot memory row from SQLite */
|
|
38
|
+
export interface ClawBotMemoryRow {
|
|
39
|
+
id: number;
|
|
40
|
+
project: string;
|
|
41
|
+
content: string;
|
|
42
|
+
category: ClawBotMemoryCategory;
|
|
43
|
+
importance: number;
|
|
44
|
+
created_at: number;
|
|
45
|
+
updated_at: number;
|
|
46
|
+
session_id: string | null;
|
|
47
|
+
superseded_by: number | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type ClawBotMemoryCategory =
|
|
51
|
+
| "fact"
|
|
52
|
+
| "decision"
|
|
53
|
+
| "preference"
|
|
54
|
+
| "architecture"
|
|
55
|
+
| "issue";
|
|
56
|
+
|
|
57
|
+
/** Active session state tracked in memory (not DB) */
|
|
58
|
+
export interface ClawBotActiveSession {
|
|
59
|
+
telegramChatId: string;
|
|
60
|
+
sessionId: string;
|
|
61
|
+
providerId: string;
|
|
62
|
+
projectName: string;
|
|
63
|
+
projectPath: string;
|
|
64
|
+
/** Telegram message ID being edited for streaming */
|
|
65
|
+
currentMessageId?: number;
|
|
66
|
+
/** Debounce timer for rapid messages */
|
|
67
|
+
debounceTimer?: ReturnType<typeof setTimeout>;
|
|
68
|
+
/** Accumulated debounced text */
|
|
69
|
+
debouncedText?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Parsed command from Telegram message */
|
|
73
|
+
export interface ClawBotCommand {
|
|
74
|
+
command: string;
|
|
75
|
+
args: string;
|
|
76
|
+
chatId: number;
|
|
77
|
+
messageId: number;
|
|
78
|
+
userId: number;
|
|
79
|
+
username?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Memory recall result with relevance score */
|
|
83
|
+
export interface MemoryRecallResult {
|
|
84
|
+
id: number;
|
|
85
|
+
content: string;
|
|
86
|
+
category: ClawBotMemoryCategory;
|
|
87
|
+
importance: number;
|
|
88
|
+
project: string;
|
|
89
|
+
/** FTS5 rank score (lower = more relevant) */
|
|
90
|
+
rank?: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Paired chat row from SQLite */
|
|
94
|
+
export interface ClawBotPairedChat {
|
|
95
|
+
id: number;
|
|
96
|
+
telegram_chat_id: string;
|
|
97
|
+
telegram_user_id: string | null;
|
|
98
|
+
display_name: string | null;
|
|
99
|
+
pairing_code: string | null;
|
|
100
|
+
status: "pending" | "approved" | "revoked";
|
|
101
|
+
created_at: number;
|
|
102
|
+
approved_at: number | null;
|
|
103
|
+
}
|
package/src/types/config.ts
CHANGED
|
@@ -9,6 +9,17 @@ export interface TelegramConfig {
|
|
|
9
9
|
chat_id: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
export interface ClawBotConfig {
|
|
13
|
+
enabled: boolean;
|
|
14
|
+
default_provider: string;
|
|
15
|
+
default_project: string;
|
|
16
|
+
system_prompt: string;
|
|
17
|
+
show_tool_calls: boolean;
|
|
18
|
+
show_thinking: boolean;
|
|
19
|
+
permission_mode: string;
|
|
20
|
+
debounce_ms: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
12
23
|
export type ThemeConfig = "light" | "dark" | "system";
|
|
13
24
|
|
|
14
25
|
export interface PpmConfig {
|
|
@@ -21,6 +32,7 @@ export interface PpmConfig {
|
|
|
21
32
|
ai: AIConfig;
|
|
22
33
|
push?: PushConfig;
|
|
23
34
|
telegram?: TelegramConfig;
|
|
35
|
+
clawbot?: ClawBotConfig;
|
|
24
36
|
cloud_url?: string;
|
|
25
37
|
}
|
|
26
38
|
|
|
@@ -85,6 +97,16 @@ export const DEFAULT_CONFIG: PpmConfig = {
|
|
|
85
97
|
},
|
|
86
98
|
},
|
|
87
99
|
},
|
|
100
|
+
clawbot: {
|
|
101
|
+
enabled: false,
|
|
102
|
+
default_provider: "claude",
|
|
103
|
+
default_project: "",
|
|
104
|
+
system_prompt: "",
|
|
105
|
+
show_tool_calls: true,
|
|
106
|
+
show_thinking: false,
|
|
107
|
+
permission_mode: "bypassPermissions",
|
|
108
|
+
debounce_ms: 2000,
|
|
109
|
+
},
|
|
88
110
|
};
|
|
89
111
|
|
|
90
112
|
const VALID_TYPES = ["agent-sdk", "cli", "mock"] as const;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useEffect, useCallback, useRef } from "react";
|
|
2
|
-
import { History, Settings2, Loader2, MessageSquare, RefreshCw, Search, Pencil, Check, X, BellOff, Bug, ClipboardCheck, Pin, PinOff, Trash2, Users } from "lucide-react";
|
|
2
|
+
import { History, Settings2, Loader2, MessageSquare, RefreshCw, Search, Pencil, Check, X, BellOff, Bug, ClipboardCheck, Pin, PinOff, Trash2, Users, Bot } from "lucide-react";
|
|
3
3
|
import { Activity } from "lucide-react";
|
|
4
4
|
import { api, projectUrl } from "@/lib/api-client";
|
|
5
5
|
import { useTabStore } from "@/stores/tab-store";
|
|
@@ -398,9 +398,14 @@ export function ChatHistoryBar({
|
|
|
398
398
|
<>
|
|
399
399
|
<button
|
|
400
400
|
onClick={() => openSession(session)}
|
|
401
|
-
className="text-[11px] truncate flex-1 text-left"
|
|
401
|
+
className="text-[11px] truncate flex-1 text-left flex items-center gap-1"
|
|
402
402
|
>
|
|
403
|
-
{session.title
|
|
403
|
+
{session.title?.startsWith("[Claw]") && (
|
|
404
|
+
<Bot className="size-3 text-muted-foreground shrink-0" />
|
|
405
|
+
)}
|
|
406
|
+
{session.title?.startsWith("[Claw]")
|
|
407
|
+
? session.title.slice(7)
|
|
408
|
+
: session.title || "Untitled"}
|
|
404
409
|
</button>
|
|
405
410
|
<button
|
|
406
411
|
onClick={(e) => togglePin(e, session)}
|