@gamalan/pi-gateway 1.0.0
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 +366 -0
- package/config/config.default.json +51 -0
- package/dist/adapters/base.d.ts +85 -0
- package/dist/adapters/base.js +34 -0
- package/dist/adapters/base.js.map +1 -0
- package/dist/adapters/discord.d.ts +53 -0
- package/dist/adapters/discord.js +224 -0
- package/dist/adapters/discord.js.map +1 -0
- package/dist/adapters/index.d.ts +13 -0
- package/dist/adapters/index.js +8 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/slack.d.ts +49 -0
- package/dist/adapters/slack.js +231 -0
- package/dist/adapters/slack.js.map +1 -0
- package/dist/adapters/telegram.d.ts +64 -0
- package/dist/adapters/telegram.js +274 -0
- package/dist/adapters/telegram.js.map +1 -0
- package/dist/adapters/twitch.d.ts +75 -0
- package/dist/adapters/twitch.js +222 -0
- package/dist/adapters/twitch.js.map +1 -0
- package/dist/adapters/websocket.d.ts +30 -0
- package/dist/adapters/websocket.js +132 -0
- package/dist/adapters/websocket.js.map +1 -0
- package/dist/adapters/whatsapp.d.ts +49 -0
- package/dist/adapters/whatsapp.js +251 -0
- package/dist/adapters/whatsapp.js.map +1 -0
- package/dist/background/index.d.ts +1 -0
- package/dist/background/index.js +2 -0
- package/dist/background/index.js.map +1 -0
- package/dist/background/manager.d.ts +70 -0
- package/dist/background/manager.js +291 -0
- package/dist/background/manager.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +1275 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +12 -0
- package/dist/logger.js +39 -0
- package/dist/logger.js.map +1 -0
- package/dist/paths.d.ts +14 -0
- package/dist/paths.js +32 -0
- package/dist/paths.js.map +1 -0
- package/dist/security/auth.d.ts +91 -0
- package/dist/security/auth.js +316 -0
- package/dist/security/auth.js.map +1 -0
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.js +2 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/tool-policy.d.ts +66 -0
- package/dist/security/tool-policy.js +467 -0
- package/dist/security/tool-policy.js.map +1 -0
- package/dist/sessions/index.d.ts +1 -0
- package/dist/sessions/index.js +2 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/sessions/store.d.ts +64 -0
- package/dist/sessions/store.js +247 -0
- package/dist/sessions/store.js.map +1 -0
- package/package.json +57 -0
- package/src/adapters/base.ts +124 -0
- package/src/adapters/discord.ts +270 -0
- package/src/adapters/index.ts +13 -0
- package/src/adapters/slack.ts +313 -0
- package/src/adapters/telegram.ts +394 -0
- package/src/adapters/twitch.ts +316 -0
- package/src/adapters/websocket.ts +158 -0
- package/src/adapters/whatsapp.ts +296 -0
- package/src/background/index.ts +1 -0
- package/src/background/manager.ts +395 -0
- package/src/index.ts +1665 -0
- package/src/logger.ts +43 -0
- package/src/paths.ts +40 -0
- package/src/security/auth.ts +458 -0
- package/src/security/index.ts +1 -0
- package/src/security/tool-policy.ts +568 -0
- package/src/sessions/index.ts +1 -0
- package/src/sessions/store.ts +360 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Store - Hermes-style per-chat session management
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Per-chat sessions with unique IDs
|
|
6
|
+
* - Reset policies: daily (hour-based) and idle (minutes-based)
|
|
7
|
+
* - Session persistence across restarts
|
|
8
|
+
* - Background session isolation
|
|
9
|
+
*/
|
|
10
|
+
import Database from "better-sqlite3";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
import { homedir } from "os";
|
|
13
|
+
import { existsSync, mkdirSync } from "fs";
|
|
14
|
+
import { logger } from "../logger.js";
|
|
15
|
+
const GATEWAY_DIR = join(homedir(), ".pi");
|
|
16
|
+
const SESSIONS_DB = join(GATEWAY_DIR, "gateway-sessions.db");
|
|
17
|
+
let db = null;
|
|
18
|
+
/**
|
|
19
|
+
* Initialize session database
|
|
20
|
+
*/
|
|
21
|
+
export function initSessionStore() {
|
|
22
|
+
if (db)
|
|
23
|
+
return db;
|
|
24
|
+
if (!existsSync(GATEWAY_DIR)) {
|
|
25
|
+
mkdirSync(GATEWAY_DIR, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
db = new Database(SESSIONS_DB);
|
|
28
|
+
db.exec("PRAGMA journal_mode = WAL;");
|
|
29
|
+
// Sessions table
|
|
30
|
+
db.exec(`
|
|
31
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
32
|
+
id TEXT PRIMARY KEY,
|
|
33
|
+
platform TEXT NOT NULL,
|
|
34
|
+
channel_id TEXT NOT NULL,
|
|
35
|
+
user_id TEXT NOT NULL,
|
|
36
|
+
reset_policy TEXT NOT NULL DEFAULT 'idle',
|
|
37
|
+
daily_hour INTEGER NOT NULL DEFAULT 4,
|
|
38
|
+
idle_minutes INTEGER NOT NULL DEFAULT 1440,
|
|
39
|
+
last_activity INTEGER NOT NULL,
|
|
40
|
+
created_at INTEGER NOT NULL,
|
|
41
|
+
is_background INTEGER NOT NULL DEFAULT 0,
|
|
42
|
+
parent_session_id TEXT,
|
|
43
|
+
FOREIGN KEY (parent_session_id) REFERENCES sessions(id)
|
|
44
|
+
)
|
|
45
|
+
`);
|
|
46
|
+
// Indexes for fast lookups
|
|
47
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_platform_channel ON sessions(platform, channel_id)`);
|
|
48
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_parent ON sessions(parent_session_id)`);
|
|
49
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_sessions_activity ON sessions(last_activity)`);
|
|
50
|
+
logger.info("[SessionStore] Database initialized");
|
|
51
|
+
return db;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Generate unique session ID
|
|
55
|
+
*/
|
|
56
|
+
export function generateSessionId() {
|
|
57
|
+
return `sess-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get or create session for a platform/channel
|
|
61
|
+
*/
|
|
62
|
+
export function getOrCreateSession(platform, channelId, userId, config) {
|
|
63
|
+
const database = initSessionStore();
|
|
64
|
+
// Try to find existing active session
|
|
65
|
+
const existing = database
|
|
66
|
+
.prepare(`
|
|
67
|
+
SELECT * FROM sessions
|
|
68
|
+
WHERE platform = ? AND channel_id = ? AND is_background = 0
|
|
69
|
+
ORDER BY last_activity DESC
|
|
70
|
+
LIMIT 1
|
|
71
|
+
`)
|
|
72
|
+
.get(platform, channelId);
|
|
73
|
+
if (existing) {
|
|
74
|
+
// Check if session needs reset
|
|
75
|
+
if (shouldResetSession(existing)) {
|
|
76
|
+
// Delete old session, create fresh one
|
|
77
|
+
database.prepare("DELETE FROM sessions WHERE id = ?").run(existing.id);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Update last activity and return
|
|
81
|
+
database
|
|
82
|
+
.prepare("UPDATE sessions SET last_activity = ? WHERE id = ?")
|
|
83
|
+
.run(Date.now(), existing.id);
|
|
84
|
+
return rowToSession(existing);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Create new session
|
|
88
|
+
const id = generateSessionId();
|
|
89
|
+
const now = Date.now();
|
|
90
|
+
const session = {
|
|
91
|
+
id,
|
|
92
|
+
platform,
|
|
93
|
+
channelId,
|
|
94
|
+
userId,
|
|
95
|
+
resetPolicy: config?.resetPolicy ?? "idle",
|
|
96
|
+
dailyHour: config?.dailyHour ?? 4,
|
|
97
|
+
idleMinutes: config?.idleMinutes ?? 1440,
|
|
98
|
+
lastActivity: now,
|
|
99
|
+
createdAt: now,
|
|
100
|
+
isBackground: false,
|
|
101
|
+
...config,
|
|
102
|
+
};
|
|
103
|
+
database
|
|
104
|
+
.prepare(`
|
|
105
|
+
INSERT INTO sessions (id, platform, channel_id, user_id, reset_policy, daily_hour, idle_minutes, last_activity, created_at, is_background, parent_session_id)
|
|
106
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
107
|
+
`)
|
|
108
|
+
.run(session.id, session.platform, session.channelId, session.userId, session.resetPolicy, session.dailyHour, session.idleMinutes, session.lastActivity, session.createdAt, session.isBackground ? 1 : 0, session.parentSessionId ?? null);
|
|
109
|
+
logger.info(`[SessionStore] Created session ${id.slice(0, 12)}... for ${platform}/${channelId}`);
|
|
110
|
+
return session;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Create a background session (isolated from parent)
|
|
114
|
+
*/
|
|
115
|
+
export function createBackgroundSession(platform, channelId, userId, parentSessionId) {
|
|
116
|
+
const database = initSessionStore();
|
|
117
|
+
const id = generateSessionId();
|
|
118
|
+
const now = Date.now();
|
|
119
|
+
const session = {
|
|
120
|
+
id,
|
|
121
|
+
platform,
|
|
122
|
+
channelId,
|
|
123
|
+
userId,
|
|
124
|
+
resetPolicy: "idle",
|
|
125
|
+
dailyHour: 4,
|
|
126
|
+
idleMinutes: 1440,
|
|
127
|
+
lastActivity: now,
|
|
128
|
+
createdAt: now,
|
|
129
|
+
isBackground: true,
|
|
130
|
+
parentSessionId,
|
|
131
|
+
};
|
|
132
|
+
database
|
|
133
|
+
.prepare(`
|
|
134
|
+
INSERT INTO sessions (id, platform, channel_id, user_id, reset_policy, daily_hour, idle_minutes, last_activity, created_at, is_background, parent_session_id)
|
|
135
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
136
|
+
`)
|
|
137
|
+
.run(session.id, session.platform, session.channelId, session.userId, session.resetPolicy, session.dailyHour, session.idleMinutes, session.lastActivity, session.createdAt, 1, // is_background
|
|
138
|
+
session.parentSessionId ?? null);
|
|
139
|
+
logger.info(`[SessionStore] Created background session ${id.slice(0, 12)}...`);
|
|
140
|
+
return session;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Check if session should be reset
|
|
144
|
+
*/
|
|
145
|
+
function shouldResetSession(row) {
|
|
146
|
+
const now = Date.now();
|
|
147
|
+
// Check idle timeout
|
|
148
|
+
const idleMs = row.idle_minutes * 60 * 1000;
|
|
149
|
+
if (now - row.last_activity > idleMs) {
|
|
150
|
+
logger.info(`[SessionStore] Session ${row.id.slice(0, 8)} reset: idle timeout`);
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
// Check daily reset
|
|
154
|
+
if (row.reset_policy === "daily" || row.reset_policy === "both") {
|
|
155
|
+
const lastActivity = new Date(row.last_activity);
|
|
156
|
+
const nowDate = new Date(now);
|
|
157
|
+
// Check if we crossed the daily reset hour since last activity
|
|
158
|
+
if (lastActivity.getHours() < row.daily_hour &&
|
|
159
|
+
nowDate.getHours() >= row.daily_hour) {
|
|
160
|
+
logger.info(`[SessionStore] Session ${row.id.slice(0, 8)} reset: daily at ${row.daily_hour}:00`);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Update session last activity
|
|
168
|
+
*/
|
|
169
|
+
export function touchSession(sessionId) {
|
|
170
|
+
const database = initSessionStore();
|
|
171
|
+
database
|
|
172
|
+
.prepare("UPDATE sessions SET last_activity = ? WHERE id = ?")
|
|
173
|
+
.run(Date.now(), sessionId);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Get session by ID
|
|
177
|
+
*/
|
|
178
|
+
export function getSession(sessionId) {
|
|
179
|
+
const database = initSessionStore();
|
|
180
|
+
const row = database
|
|
181
|
+
.prepare("SELECT * FROM sessions WHERE id = ?")
|
|
182
|
+
.get(sessionId);
|
|
183
|
+
return row ? rowToSession(row) : null;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Delete session
|
|
187
|
+
*/
|
|
188
|
+
export function deleteSession(sessionId) {
|
|
189
|
+
const database = initSessionStore();
|
|
190
|
+
database.prepare("DELETE FROM sessions WHERE id = ?").run(sessionId);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* List sessions by platform
|
|
194
|
+
*/
|
|
195
|
+
export function listSessions(platform) {
|
|
196
|
+
const database = initSessionStore();
|
|
197
|
+
const query = platform
|
|
198
|
+
? "SELECT * FROM sessions WHERE platform = ? AND is_background = 0 ORDER BY last_activity DESC"
|
|
199
|
+
: "SELECT * FROM sessions WHERE is_background = 0 ORDER BY last_activity DESC";
|
|
200
|
+
const rows = platform
|
|
201
|
+
? database.prepare(query).all(platform)
|
|
202
|
+
: database.prepare(query).all();
|
|
203
|
+
return rows.map(rowToSession);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Clean up stale sessions
|
|
207
|
+
*/
|
|
208
|
+
export function cleanupStaleSessions() {
|
|
209
|
+
const database = initSessionStore();
|
|
210
|
+
const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
211
|
+
const result = database
|
|
212
|
+
.prepare("DELETE FROM sessions WHERE last_activity < ?")
|
|
213
|
+
.run(cutoff);
|
|
214
|
+
return result.changes;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get background sessions for delivery
|
|
218
|
+
*/
|
|
219
|
+
export function getPendingBackgroundResults() {
|
|
220
|
+
const database = initSessionStore();
|
|
221
|
+
// Background sessions that exist but parent still needs delivery
|
|
222
|
+
const rows = database
|
|
223
|
+
.prepare(`
|
|
224
|
+
SELECT s.* FROM sessions s
|
|
225
|
+
WHERE s.is_background = 1
|
|
226
|
+
ORDER BY s.created_at ASC
|
|
227
|
+
`)
|
|
228
|
+
.all();
|
|
229
|
+
return rows.map(rowToSession);
|
|
230
|
+
}
|
|
231
|
+
// Helper to convert DB row to SessionConfig
|
|
232
|
+
function rowToSession(row) {
|
|
233
|
+
return {
|
|
234
|
+
id: row.id,
|
|
235
|
+
platform: row.platform,
|
|
236
|
+
channelId: row.channel_id,
|
|
237
|
+
userId: row.user_id,
|
|
238
|
+
resetPolicy: row.reset_policy,
|
|
239
|
+
dailyHour: row.daily_hour,
|
|
240
|
+
idleMinutes: row.idle_minutes,
|
|
241
|
+
lastActivity: row.last_activity,
|
|
242
|
+
createdAt: row.created_at,
|
|
243
|
+
isBackground: row.is_background === 1,
|
|
244
|
+
parentSessionId: row.parent_session_id ?? undefined,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/sessions/store.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gamalan/pi-gateway",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Hermes-style messaging gateway for pi - multi-platform agent with sessions, security, and background tasks",
|
|
5
|
+
"author": "0xKobold <original>; forked by gamalan",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["pi-package", "pi-extension", "gateway", "messaging", "hermes", "multi-platform"],
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "dist/index.js",
|
|
13
|
+
"types": "dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/",
|
|
22
|
+
"src/",
|
|
23
|
+
"config/",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"dev": "tsc --watch",
|
|
29
|
+
"test": "npx tsx tests/index.ts",
|
|
30
|
+
"demo": "npx tsx tests/demo-server.ts",
|
|
31
|
+
"client": "npx tsx tests/client-demo.ts",
|
|
32
|
+
"prepublishOnly": "rm -rf dist && tsc"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"better-sqlite3": "^11.0.0",
|
|
36
|
+
"ws": "^8.18.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^20.19.37",
|
|
40
|
+
"@types/ws": "^8.18.1",
|
|
41
|
+
"@whiskeysockets/baileys": "^7.0.0-rc13",
|
|
42
|
+
"@types/better-sqlite3": "^7.6.0",
|
|
43
|
+
"tsx": "^4.19.0",
|
|
44
|
+
"typescript": "^6.0.2"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@earendil-works/pi-coding-agent": ">=0.80.3",
|
|
48
|
+
"@sinclair/typebox": ">=0.32.0"
|
|
49
|
+
},
|
|
50
|
+
"pi": {
|
|
51
|
+
"extensions": ["./dist/index.js"]
|
|
52
|
+
},
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "https://github.com/gamalan/pi-gateway.git"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform Adapter Base - Interface for Hermes-style platform adapters
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface PlatformMessage {
|
|
6
|
+
id: string;
|
|
7
|
+
platform: string;
|
|
8
|
+
channelId: string;
|
|
9
|
+
userId: string;
|
|
10
|
+
content: string;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
metadata?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PlatformConfig {
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
platform: string;
|
|
18
|
+
token?: string;
|
|
19
|
+
botToken?: string;
|
|
20
|
+
webhookSecret?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AdapterCallbacks {
|
|
24
|
+
onMessage: (message: PlatformMessage) => Promise<void>;
|
|
25
|
+
onTyping?: (userId: string, isTyping: boolean) => void;
|
|
26
|
+
onDisconnect?: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface PlatformAdapter {
|
|
30
|
+
readonly platform: string;
|
|
31
|
+
readonly config: PlatformConfig;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Initialize the adapter
|
|
35
|
+
*/
|
|
36
|
+
initialize(): Promise<void>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Start listening for messages
|
|
40
|
+
*/
|
|
41
|
+
start(callbacks: AdapterCallbacks): Promise<void>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Stop listening
|
|
45
|
+
*/
|
|
46
|
+
stop(): Promise<void>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Send a message to a channel
|
|
50
|
+
*/
|
|
51
|
+
sendMessage(channelId: string, content: string): Promise<string>; // Returns message ID
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Edit an existing message
|
|
55
|
+
*/
|
|
56
|
+
editMessage(
|
|
57
|
+
channelId: string,
|
|
58
|
+
messageId: string,
|
|
59
|
+
content: string,
|
|
60
|
+
): Promise<void>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Delete a message
|
|
64
|
+
*/
|
|
65
|
+
deleteMessage(channelId: string, messageId: string): Promise<void>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Set typing indicator
|
|
69
|
+
*/
|
|
70
|
+
setTyping(channelId: string, isTyping: boolean): Promise<void>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get adapter health status
|
|
74
|
+
*/
|
|
75
|
+
getStatus(): Promise<{ connected: boolean; latency?: number }>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Abstract base class for adapters
|
|
80
|
+
*/
|
|
81
|
+
export abstract class BaseAdapter implements PlatformAdapter {
|
|
82
|
+
abstract readonly platform: string;
|
|
83
|
+
abstract config: PlatformConfig;
|
|
84
|
+
protected callbacks: AdapterCallbacks | null = null;
|
|
85
|
+
protected running = false;
|
|
86
|
+
|
|
87
|
+
async initialize(): Promise<void> {
|
|
88
|
+
// Override in subclass
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async start(callbacks: AdapterCallbacks): Promise<void> {
|
|
92
|
+
this.callbacks = callbacks;
|
|
93
|
+
this.running = true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async stop(): Promise<void> {
|
|
97
|
+
this.running = false;
|
|
98
|
+
this.callbacks = null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
abstract sendMessage(channelId: string, content: string): Promise<string>;
|
|
102
|
+
abstract editMessage(
|
|
103
|
+
channelId: string,
|
|
104
|
+
messageId: string,
|
|
105
|
+
content: string,
|
|
106
|
+
): Promise<void>;
|
|
107
|
+
abstract deleteMessage(channelId: string, messageId: string): Promise<void>;
|
|
108
|
+
abstract setTyping(channelId: string, isTyping: boolean): Promise<void>;
|
|
109
|
+
abstract getStatus(): Promise<{ connected: boolean; latency?: number }>;
|
|
110
|
+
|
|
111
|
+
protected emitMessage(message: PlatformMessage): Promise<void> {
|
|
112
|
+
if (this.callbacks?.onMessage) {
|
|
113
|
+
return this.callbacks.onMessage(message).catch((err) => {
|
|
114
|
+
// Log but don't crash the adapter loop
|
|
115
|
+
console.error(`[${this.platform}] Error in onMessage callback:`, err);
|
|
116
|
+
}) as Promise<void>;
|
|
117
|
+
}
|
|
118
|
+
return Promise.resolve();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
protected generateMessageId(): string {
|
|
122
|
+
return `${this.platform}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
123
|
+
}
|
|
124
|
+
}
|