@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.
Files changed (75) hide show
  1. package/README.md +366 -0
  2. package/config/config.default.json +51 -0
  3. package/dist/adapters/base.d.ts +85 -0
  4. package/dist/adapters/base.js +34 -0
  5. package/dist/adapters/base.js.map +1 -0
  6. package/dist/adapters/discord.d.ts +53 -0
  7. package/dist/adapters/discord.js +224 -0
  8. package/dist/adapters/discord.js.map +1 -0
  9. package/dist/adapters/index.d.ts +13 -0
  10. package/dist/adapters/index.js +8 -0
  11. package/dist/adapters/index.js.map +1 -0
  12. package/dist/adapters/slack.d.ts +49 -0
  13. package/dist/adapters/slack.js +231 -0
  14. package/dist/adapters/slack.js.map +1 -0
  15. package/dist/adapters/telegram.d.ts +64 -0
  16. package/dist/adapters/telegram.js +274 -0
  17. package/dist/adapters/telegram.js.map +1 -0
  18. package/dist/adapters/twitch.d.ts +75 -0
  19. package/dist/adapters/twitch.js +222 -0
  20. package/dist/adapters/twitch.js.map +1 -0
  21. package/dist/adapters/websocket.d.ts +30 -0
  22. package/dist/adapters/websocket.js +132 -0
  23. package/dist/adapters/websocket.js.map +1 -0
  24. package/dist/adapters/whatsapp.d.ts +49 -0
  25. package/dist/adapters/whatsapp.js +251 -0
  26. package/dist/adapters/whatsapp.js.map +1 -0
  27. package/dist/background/index.d.ts +1 -0
  28. package/dist/background/index.js +2 -0
  29. package/dist/background/index.js.map +1 -0
  30. package/dist/background/manager.d.ts +70 -0
  31. package/dist/background/manager.js +291 -0
  32. package/dist/background/manager.js.map +1 -0
  33. package/dist/index.d.ts +18 -0
  34. package/dist/index.js +1275 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/logger.d.ts +12 -0
  37. package/dist/logger.js +39 -0
  38. package/dist/logger.js.map +1 -0
  39. package/dist/paths.d.ts +14 -0
  40. package/dist/paths.js +32 -0
  41. package/dist/paths.js.map +1 -0
  42. package/dist/security/auth.d.ts +91 -0
  43. package/dist/security/auth.js +316 -0
  44. package/dist/security/auth.js.map +1 -0
  45. package/dist/security/index.d.ts +1 -0
  46. package/dist/security/index.js +2 -0
  47. package/dist/security/index.js.map +1 -0
  48. package/dist/security/tool-policy.d.ts +66 -0
  49. package/dist/security/tool-policy.js +467 -0
  50. package/dist/security/tool-policy.js.map +1 -0
  51. package/dist/sessions/index.d.ts +1 -0
  52. package/dist/sessions/index.js +2 -0
  53. package/dist/sessions/index.js.map +1 -0
  54. package/dist/sessions/store.d.ts +64 -0
  55. package/dist/sessions/store.js +247 -0
  56. package/dist/sessions/store.js.map +1 -0
  57. package/package.json +57 -0
  58. package/src/adapters/base.ts +124 -0
  59. package/src/adapters/discord.ts +270 -0
  60. package/src/adapters/index.ts +13 -0
  61. package/src/adapters/slack.ts +313 -0
  62. package/src/adapters/telegram.ts +394 -0
  63. package/src/adapters/twitch.ts +316 -0
  64. package/src/adapters/websocket.ts +158 -0
  65. package/src/adapters/whatsapp.ts +296 -0
  66. package/src/background/index.ts +1 -0
  67. package/src/background/manager.ts +395 -0
  68. package/src/index.ts +1665 -0
  69. package/src/logger.ts +43 -0
  70. package/src/paths.ts +40 -0
  71. package/src/security/auth.ts +458 -0
  72. package/src/security/index.ts +1 -0
  73. package/src/security/tool-policy.ts +568 -0
  74. package/src/sessions/index.ts +1 -0
  75. 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
+ }