instar 0.26.3 → 0.26.4

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 (91) hide show
  1. package/dashboard/index.html +55 -0
  2. package/dist/commands/init.d.ts.map +1 -1
  3. package/dist/commands/init.js +2 -1
  4. package/dist/commands/init.js.map +1 -1
  5. package/dist/commands/server.d.ts.map +1 -1
  6. package/dist/commands/server.js +45 -2
  7. package/dist/commands/server.js.map +1 -1
  8. package/dist/commands/slack-cli.d.ts.map +1 -1
  9. package/dist/commands/slack-cli.js +6 -0
  10. package/dist/commands/slack-cli.js.map +1 -1
  11. package/dist/core/CapabilityMapper.d.ts.map +1 -1
  12. package/dist/core/CapabilityMapper.js +2 -0
  13. package/dist/core/CapabilityMapper.js.map +1 -1
  14. package/dist/core/EvolutionManager.d.ts +17 -0
  15. package/dist/core/EvolutionManager.d.ts.map +1 -1
  16. package/dist/core/EvolutionManager.js +64 -0
  17. package/dist/core/EvolutionManager.js.map +1 -1
  18. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  19. package/dist/core/PostUpdateMigrator.js +37 -0
  20. package/dist/core/PostUpdateMigrator.js.map +1 -1
  21. package/dist/core/SessionManager.d.ts.map +1 -1
  22. package/dist/core/SessionManager.js +9 -0
  23. package/dist/core/SessionManager.js.map +1 -1
  24. package/dist/core/types.d.ts +4 -0
  25. package/dist/core/types.d.ts.map +1 -1
  26. package/dist/core/types.js.map +1 -1
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/messaging/imessage/IMessageAdapter.d.ts +112 -0
  32. package/dist/messaging/imessage/IMessageAdapter.d.ts.map +1 -0
  33. package/dist/messaging/imessage/IMessageAdapter.js +483 -0
  34. package/dist/messaging/imessage/IMessageAdapter.js.map +1 -0
  35. package/dist/messaging/imessage/NativeBackend.d.ts +82 -0
  36. package/dist/messaging/imessage/NativeBackend.d.ts.map +1 -0
  37. package/dist/messaging/imessage/NativeBackend.js +353 -0
  38. package/dist/messaging/imessage/NativeBackend.js.map +1 -0
  39. package/dist/messaging/imessage/OutboundAuditLog.d.ts +49 -0
  40. package/dist/messaging/imessage/OutboundAuditLog.d.ts.map +1 -0
  41. package/dist/messaging/imessage/OutboundAuditLog.js +58 -0
  42. package/dist/messaging/imessage/OutboundAuditLog.js.map +1 -0
  43. package/dist/messaging/imessage/OutboundRateLimiter.d.ts +54 -0
  44. package/dist/messaging/imessage/OutboundRateLimiter.d.ts.map +1 -0
  45. package/dist/messaging/imessage/OutboundRateLimiter.js +111 -0
  46. package/dist/messaging/imessage/OutboundRateLimiter.js.map +1 -0
  47. package/dist/messaging/imessage/index.d.ts +10 -0
  48. package/dist/messaging/imessage/index.d.ts.map +1 -0
  49. package/dist/messaging/imessage/index.js +13 -0
  50. package/dist/messaging/imessage/index.js.map +1 -0
  51. package/dist/messaging/imessage/normalize-phone.d.ts +26 -0
  52. package/dist/messaging/imessage/normalize-phone.d.ts.map +1 -0
  53. package/dist/messaging/imessage/normalize-phone.js +55 -0
  54. package/dist/messaging/imessage/normalize-phone.js.map +1 -0
  55. package/dist/messaging/imessage/types.d.ts +84 -0
  56. package/dist/messaging/imessage/types.d.ts.map +1 -0
  57. package/dist/messaging/imessage/types.js +5 -0
  58. package/dist/messaging/imessage/types.js.map +1 -0
  59. package/dist/messaging/shared/MessagingEventBus.d.ts +5 -0
  60. package/dist/messaging/shared/MessagingEventBus.d.ts.map +1 -1
  61. package/dist/messaging/shared/MessagingEventBus.js.map +1 -1
  62. package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
  63. package/dist/messaging/slack/SlackAdapter.js +31 -1
  64. package/dist/messaging/slack/SlackAdapter.js.map +1 -1
  65. package/dist/messaging/slack/SocketModeClient.d.ts.map +1 -1
  66. package/dist/messaging/slack/SocketModeClient.js +6 -0
  67. package/dist/messaging/slack/SocketModeClient.js.map +1 -1
  68. package/dist/monitoring/PresenceProxy.d.ts +6 -0
  69. package/dist/monitoring/PresenceProxy.d.ts.map +1 -1
  70. package/dist/monitoring/PresenceProxy.js +46 -0
  71. package/dist/monitoring/PresenceProxy.js.map +1 -1
  72. package/dist/monitoring/QuotaExhaustionDetector.d.ts +15 -0
  73. package/dist/monitoring/QuotaExhaustionDetector.d.ts.map +1 -1
  74. package/dist/monitoring/QuotaExhaustionDetector.js +26 -3
  75. package/dist/monitoring/QuotaExhaustionDetector.js.map +1 -1
  76. package/dist/monitoring/SessionMonitor.d.ts.map +1 -1
  77. package/dist/monitoring/SessionMonitor.js +5 -2
  78. package/dist/monitoring/SessionMonitor.js.map +1 -1
  79. package/dist/monitoring/SessionRecovery.d.ts +16 -1
  80. package/dist/monitoring/SessionRecovery.d.ts.map +1 -1
  81. package/dist/monitoring/SessionRecovery.js +71 -8
  82. package/dist/monitoring/SessionRecovery.js.map +1 -1
  83. package/dist/scheduler/JobScheduler.js +1 -1
  84. package/dist/scheduler/JobScheduler.js.map +1 -1
  85. package/dist/server/routes.d.ts.map +1 -1
  86. package/dist/server/routes.js +14 -0
  87. package/dist/server/routes.js.map +1 -1
  88. package/package.json +1 -1
  89. package/src/data/builtin-manifest.json +65 -65
  90. package/src/templates/hooks/settings-template.json +11 -0
  91. package/upgrades/0.26.4.md +17 -0
@@ -0,0 +1,353 @@
1
+ /**
2
+ * NativeBackend — Read-only macOS Messages database integration.
3
+ *
4
+ * Provides:
5
+ * - SQLite reads from ~/Library/Messages/chat.db (via better-sqlite3)
6
+ * - Polling for new messages (watches max ROWID)
7
+ * - Conversation context formatting for session bootstrap
8
+ *
9
+ * Does NOT send messages. Sending happens from Claude Code sessions
10
+ * via imessage-reply.sh → imsg send CLI, because AppleScript Automation
11
+ * permission doesn't propagate through LaunchAgent process trees.
12
+ *
13
+ * Requires:
14
+ * - Full Disk Access for the process reading chat.db
15
+ */
16
+ import { EventEmitter } from 'node:events';
17
+ import fs from 'node:fs';
18
+ import path from 'node:path';
19
+ import os from 'node:os';
20
+ // Apple Cocoa epoch: 2001-01-01T00:00:00Z in Unix epoch seconds
21
+ const APPLE_EPOCH_OFFSET = 978307200;
22
+ const DEFAULT_POLL_INTERVAL_MS = 2_000;
23
+ const DEFAULT_DB_PATH = path.join(os.homedir(), 'Library', 'Messages', 'chat.db');
24
+ export class NativeBackend extends EventEmitter {
25
+ db = null;
26
+ pollTimer = null;
27
+ lastRowId = 0;
28
+ _state = 'disconnected';
29
+ dbPath;
30
+ pollIntervalMs;
31
+ includeAttachments;
32
+ offsetPath;
33
+ authorizedContacts;
34
+ // Prepared statements (cached for performance)
35
+ stmtNewMessages = null;
36
+ stmtChats = null;
37
+ stmtHistory = null;
38
+ stmtMaxRowId = null;
39
+ stmtContextHistory = null;
40
+ constructor(options = {}) {
41
+ super();
42
+ this.dbPath = options.dbPath || DEFAULT_DB_PATH;
43
+ this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
44
+ this.includeAttachments = options.includeAttachments ?? true;
45
+ this.offsetPath = options.offsetPath ?? null;
46
+ this.authorizedContacts = options.authorizedContacts ?? [];
47
+ }
48
+ get state() {
49
+ return this._state;
50
+ }
51
+ /**
52
+ * Open the Messages database and start polling for new messages.
53
+ */
54
+ async connect() {
55
+ if (this._state === 'connected')
56
+ return;
57
+ this._setState('connecting');
58
+ try {
59
+ const Database = (await import('better-sqlite3')).default;
60
+ // Open without readonly flag — readonly mode cannot read the WAL (write-ahead log).
61
+ // Messages.app writes to WAL continuously; new messages only appear in WAL until
62
+ // a checkpoint flushes them to the main db file. query_only pragma prevents writes
63
+ // while still allowing WAL reads.
64
+ this.db = new Database(this.dbPath, { fileMustExist: true });
65
+ this.db.pragma('query_only = ON');
66
+ // Scope poll query to authorized contacts for defense-in-depth.
67
+ // Even if the adapter's auth check is bypassed, the SQL itself won't return
68
+ // unauthorized messages. Falls back to unscoped if no contacts configured.
69
+ if (this.authorizedContacts.length > 0) {
70
+ const placeholders = this.authorizedContacts.map(() => '?').join(', ');
71
+ this.stmtNewMessages = this.db.prepare(`
72
+ SELECT m.ROWID, m.guid, m.text, m.date, m.is_from_me, m.service,
73
+ m.associated_message_type,
74
+ h.id AS sender,
75
+ c.chat_identifier AS chat_id, c.display_name AS chat_name
76
+ FROM message m
77
+ LEFT JOIN handle h ON m.handle_id = h.ROWID
78
+ LEFT JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
79
+ LEFT JOIN chat c ON cmj.chat_id = c.ROWID
80
+ WHERE m.ROWID > ?
81
+ AND (m.is_from_me = 1 OR h.id IN (${placeholders}))
82
+ ORDER BY m.ROWID ASC
83
+ `);
84
+ }
85
+ else {
86
+ this.stmtNewMessages = this.db.prepare(`
87
+ SELECT m.ROWID, m.guid, m.text, m.date, m.is_from_me, m.service,
88
+ m.associated_message_type,
89
+ h.id AS sender,
90
+ c.chat_identifier AS chat_id, c.display_name AS chat_name
91
+ FROM message m
92
+ LEFT JOIN handle h ON m.handle_id = h.ROWID
93
+ LEFT JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
94
+ LEFT JOIN chat c ON cmj.chat_id = c.ROWID
95
+ WHERE m.ROWID > ?
96
+ ORDER BY m.ROWID ASC
97
+ `);
98
+ }
99
+ // Scope chat listing to authorized contacts
100
+ if (this.authorizedContacts.length > 0) {
101
+ const placeholders = this.authorizedContacts.map(() => '?').join(', ');
102
+ this.stmtChats = this.db.prepare(`
103
+ SELECT c.ROWID AS id, c.chat_identifier, c.display_name, c.service_name,
104
+ c.guid, c.is_archived,
105
+ (SELECT MAX(m.date) FROM message m
106
+ JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
107
+ WHERE cmj.chat_id = c.ROWID) AS last_message_date
108
+ FROM chat c
109
+ WHERE c.chat_identifier IN (${placeholders})
110
+ ORDER BY last_message_date DESC
111
+ LIMIT ?
112
+ `);
113
+ }
114
+ else {
115
+ this.stmtChats = this.db.prepare(`
116
+ SELECT c.ROWID AS id, c.chat_identifier, c.display_name, c.service_name,
117
+ c.guid, c.is_archived,
118
+ (SELECT MAX(m.date) FROM message m
119
+ JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
120
+ WHERE cmj.chat_id = c.ROWID) AS last_message_date
121
+ FROM chat c
122
+ ORDER BY last_message_date DESC
123
+ LIMIT ?
124
+ `);
125
+ }
126
+ this.stmtHistory = this.db.prepare(`
127
+ SELECT m.ROWID, m.guid, m.text, m.date, m.is_from_me, m.service,
128
+ h.id AS sender
129
+ FROM message m
130
+ LEFT JOIN handle h ON m.handle_id = h.ROWID
131
+ LEFT JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
132
+ LEFT JOIN chat c ON cmj.chat_id = c.ROWID
133
+ WHERE c.chat_identifier = ?
134
+ ORDER BY m.date DESC
135
+ LIMIT ?
136
+ `);
137
+ // Context history query — filters by sender handle (phone/email)
138
+ this.stmtContextHistory = this.db.prepare(`
139
+ SELECT m.ROWID, m.text, m.date, m.is_from_me, h.id AS sender
140
+ FROM message m
141
+ LEFT JOIN handle h ON m.handle_id = h.ROWID
142
+ LEFT JOIN chat_message_join cmj ON m.ROWID = cmj.message_id
143
+ LEFT JOIN chat c ON cmj.chat_id = c.ROWID
144
+ WHERE (h.id = ? OR c.chat_identifier = ?)
145
+ AND m.text IS NOT NULL
146
+ AND m.associated_message_type = 0
147
+ ORDER BY m.date DESC
148
+ LIMIT ?
149
+ `);
150
+ this.stmtMaxRowId = this.db.prepare('SELECT MAX(ROWID) AS max_id FROM message');
151
+ // Restore persisted poll offset if available; otherwise use a 50-message
152
+ // lookback so messages received while the server was down are processed.
153
+ const persistedOffset = this._loadOffset();
154
+ if (persistedOffset !== null) {
155
+ this.lastRowId = persistedOffset;
156
+ }
157
+ else {
158
+ const maxRow = this.stmtMaxRowId.get();
159
+ const maxId = maxRow?.max_id ?? 0;
160
+ this.lastRowId = Math.max(0, maxId - 50);
161
+ }
162
+ this._setState('connected');
163
+ this._startPolling();
164
+ }
165
+ catch (err) {
166
+ this._setState('error');
167
+ throw new Error(`Failed to open Messages database: ${err.message}`);
168
+ }
169
+ }
170
+ /**
171
+ * Stop polling and close the database.
172
+ */
173
+ async disconnect() {
174
+ this._stopPolling();
175
+ if (this.db) {
176
+ try {
177
+ this.db.close();
178
+ }
179
+ catch { /* already closed */ }
180
+ this.db = null;
181
+ }
182
+ this.stmtNewMessages = null;
183
+ this.stmtChats = null;
184
+ this.stmtHistory = null;
185
+ this.stmtContextHistory = null;
186
+ this.stmtMaxRowId = null;
187
+ this._setState('disconnected');
188
+ }
189
+ /**
190
+ * List recent chats.
191
+ */
192
+ listChats(limit = 20) {
193
+ if (!this.db || !this.stmtChats) {
194
+ throw new Error('Database not connected');
195
+ }
196
+ const chatBindParams = this.authorizedContacts.length > 0
197
+ ? [...this.authorizedContacts, limit]
198
+ : [limit];
199
+ const rows = this.stmtChats.all(...chatBindParams);
200
+ return rows.map((row) => ({
201
+ chatId: row.chat_identifier,
202
+ displayName: row.display_name || undefined,
203
+ participants: [row.chat_identifier],
204
+ lastMessageDate: row.last_message_date
205
+ ? this._cocoaToIso(row.last_message_date)
206
+ : undefined,
207
+ service: row.service_name,
208
+ }));
209
+ }
210
+ /**
211
+ * Get message history for a chat.
212
+ */
213
+ getChatHistory(chatId, limit = 50) {
214
+ if (!this.db || !this.stmtHistory) {
215
+ throw new Error('Database not connected');
216
+ }
217
+ // Defense-in-depth: only return history for authorized contacts
218
+ if (this.authorizedContacts.length > 0 && !this.authorizedContacts.includes(chatId)) {
219
+ return [];
220
+ }
221
+ const rows = this.stmtHistory.all(chatId, limit);
222
+ return rows.map((row) => ({
223
+ chatId,
224
+ messageId: row.guid,
225
+ sender: row.sender || chatId,
226
+ text: row.text || '',
227
+ timestamp: this._cocoaToUnix(row.date),
228
+ isFromMe: row.is_from_me === 1,
229
+ service: row.service,
230
+ }));
231
+ }
232
+ /**
233
+ * Format conversation context for session bootstrap.
234
+ * Returns a formatted string of recent messages suitable for injection
235
+ * into a Claude Code session as conversation history.
236
+ */
237
+ getConversationContext(sender, limit = 20) {
238
+ if (!this.db || !this.stmtContextHistory) {
239
+ return '';
240
+ }
241
+ try {
242
+ const rows = this.stmtContextHistory.all(sender, sender, limit);
243
+ if (rows.length === 0)
244
+ return '';
245
+ // Reverse to chronological order (query returns newest first)
246
+ rows.reverse();
247
+ const lines = rows.map((row) => {
248
+ const time = new Date(this._cocoaToUnix(row.date) * 1000);
249
+ const hh = time.getHours().toString().padStart(2, '0');
250
+ const mm = time.getMinutes().toString().padStart(2, '0');
251
+ const who = row.is_from_me ? 'Agent' : (row.sender || sender);
252
+ const text = row.text || '(attachment)';
253
+ return `[${hh}:${mm}] ${who}: ${text}`;
254
+ });
255
+ return `--- Conversation History (last ${rows.length} messages) ---\n${lines.join('\n')}\n--- End History ---`;
256
+ }
257
+ catch {
258
+ return '';
259
+ }
260
+ }
261
+ // ── Internal ──
262
+ _startPolling() {
263
+ if (this.pollTimer)
264
+ return;
265
+ this.pollTimer = setInterval(() => this._poll(), this.pollIntervalMs);
266
+ }
267
+ _stopPolling() {
268
+ if (this.pollTimer) {
269
+ clearInterval(this.pollTimer);
270
+ this.pollTimer = null;
271
+ }
272
+ }
273
+ _poll() {
274
+ if (!this.db || !this.stmtNewMessages)
275
+ return;
276
+ try {
277
+ // Pass authorized contacts as bind params when SQL is scoped
278
+ const bindParams = this.authorizedContacts.length > 0
279
+ ? [this.lastRowId, ...this.authorizedContacts]
280
+ : [this.lastRowId];
281
+ const rows = this.stmtNewMessages.all(...bindParams);
282
+ const startRowId = this.lastRowId;
283
+ for (const row of rows) {
284
+ this.lastRowId = row.ROWID;
285
+ // Skip non-text messages (reactions, edits, etc.)
286
+ if (row.associated_message_type !== 0)
287
+ continue;
288
+ // Skip empty messages (typing indicators, read receipts)
289
+ if (!row.text && !this.includeAttachments)
290
+ continue;
291
+ const msg = {
292
+ chatId: row.chat_id || row.sender || 'unknown',
293
+ messageId: row.guid,
294
+ sender: row.sender || 'unknown',
295
+ senderName: row.chat_name || undefined,
296
+ text: row.text || '',
297
+ timestamp: this._cocoaToUnix(row.date),
298
+ isFromMe: row.is_from_me === 1,
299
+ service: row.service,
300
+ };
301
+ this.emit('message', msg);
302
+ }
303
+ // Persist offset if it advanced
304
+ if (this.lastRowId > startRowId) {
305
+ this._saveOffset(this.lastRowId);
306
+ }
307
+ }
308
+ catch (err) {
309
+ const msg = err.message;
310
+ if (!msg.includes('SQLITE_BUSY') && !msg.includes('database is locked')) {
311
+ console.error(`[imessage-native] Poll error: ${msg}`);
312
+ }
313
+ }
314
+ }
315
+ _setState(state) {
316
+ const prev = this._state;
317
+ this._state = state;
318
+ if (prev !== state) {
319
+ this.emit('stateChange', state, prev);
320
+ }
321
+ }
322
+ /** Convert Apple Cocoa nanosecond timestamp to Unix epoch seconds. */
323
+ _cocoaToUnix(cocoaNanos) {
324
+ return Math.floor(cocoaNanos / 1e9) + APPLE_EPOCH_OFFSET;
325
+ }
326
+ /** Load persisted poll offset from disk. */
327
+ _loadOffset() {
328
+ if (!this.offsetPath)
329
+ return null;
330
+ try {
331
+ const data = JSON.parse(fs.readFileSync(this.offsetPath, 'utf-8'));
332
+ if (typeof data.lastRowId === 'number' && data.lastRowId > 0) {
333
+ return data.lastRowId;
334
+ }
335
+ }
336
+ catch { /* first run or corrupted — use lookback */ }
337
+ return null;
338
+ }
339
+ /** Save poll offset to disk. */
340
+ _saveOffset(rowId) {
341
+ if (!this.offsetPath)
342
+ return;
343
+ try {
344
+ fs.writeFileSync(this.offsetPath, JSON.stringify({ lastRowId: rowId, savedAt: new Date().toISOString() }) + '\n');
345
+ }
346
+ catch { /* non-critical */ }
347
+ }
348
+ /** Convert Apple Cocoa nanosecond timestamp to ISO string. */
349
+ _cocoaToIso(cocoaNanos) {
350
+ return new Date(this._cocoaToUnix(cocoaNanos) * 1000).toISOString();
351
+ }
352
+ }
353
+ //# sourceMappingURL=NativeBackend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NativeBackend.js","sourceRoot":"","sources":["../../../src/messaging/imessage/NativeBackend.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * OutboundAuditLog — Append-only JSONL logger for all outbound message attempts.
3
+ *
4
+ * Logs every outbound attempt (allowed or blocked) with:
5
+ * - Masked recipient (PII protection)
6
+ * - SHA-256 hashes for correlation (recipient + content)
7
+ * - Which enforcement layer blocked (if blocked)
8
+ * - Rate limit status at time of attempt
9
+ * - Send mode (reactive vs proactive)
10
+ *
11
+ * No plaintext message content or phone numbers are stored.
12
+ */
13
+ export interface AuditEntry {
14
+ timestamp: string;
15
+ recipient: string;
16
+ recipientHash: string;
17
+ textLength: number;
18
+ textHash: string;
19
+ allowed: boolean;
20
+ blockedBy: string | null;
21
+ sendMode: 'reactive' | 'proactive';
22
+ sessionName: string | null;
23
+ sendToken: string | null;
24
+ rateStatus: {
25
+ contactHour: number;
26
+ globalDay: number;
27
+ };
28
+ }
29
+ export declare class OutboundAuditLog {
30
+ private readonly logPath;
31
+ constructor(logPath: string);
32
+ /**
33
+ * Record an outbound attempt.
34
+ */
35
+ record(entry: {
36
+ recipient: string;
37
+ text: string;
38
+ allowed: boolean;
39
+ blockedBy?: string;
40
+ sendMode?: 'reactive' | 'proactive';
41
+ sessionName?: string;
42
+ sendToken?: string;
43
+ rateStatus?: {
44
+ contactHour: number;
45
+ globalDay: number;
46
+ };
47
+ }): void;
48
+ }
49
+ //# sourceMappingURL=OutboundAuditLog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OutboundAuditLog.d.ts","sourceRoot":"","sources":["../../../src/messaging/imessage/OutboundAuditLog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,UAAU,GAAG,WAAW,CAAC;IACnC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CACxD;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,OAAO,EAAE,MAAM;IAI3B;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE;QACZ,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,OAAO,CAAC;QACjB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,UAAU,GAAG,WAAW,CAAC;QACpC,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE;YAAE,WAAW,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,CAAC;KACzD,GAAG,IAAI;CAqBT"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * OutboundAuditLog — Append-only JSONL logger for all outbound message attempts.
3
+ *
4
+ * Logs every outbound attempt (allowed or blocked) with:
5
+ * - Masked recipient (PII protection)
6
+ * - SHA-256 hashes for correlation (recipient + content)
7
+ * - Which enforcement layer blocked (if blocked)
8
+ * - Rate limit status at time of attempt
9
+ * - Send mode (reactive vs proactive)
10
+ *
11
+ * No plaintext message content or phone numbers are stored.
12
+ */
13
+ import fs from 'node:fs';
14
+ import crypto from 'node:crypto';
15
+ export class OutboundAuditLog {
16
+ logPath;
17
+ constructor(logPath) {
18
+ this.logPath = logPath;
19
+ }
20
+ /**
21
+ * Record an outbound attempt.
22
+ */
23
+ record(entry) {
24
+ const auditEntry = {
25
+ timestamp: new Date().toISOString(),
26
+ recipient: maskIdentifier(entry.recipient),
27
+ recipientHash: sha256Prefix(entry.recipient),
28
+ textLength: entry.text.length,
29
+ textHash: sha256Prefix(entry.text),
30
+ allowed: entry.allowed,
31
+ blockedBy: entry.blockedBy || null,
32
+ sendMode: entry.sendMode || 'reactive',
33
+ sessionName: entry.sessionName || null,
34
+ sendToken: entry.sendToken ? entry.sendToken.slice(0, 8) : null,
35
+ rateStatus: entry.rateStatus || { contactHour: 0, globalDay: 0 },
36
+ };
37
+ try {
38
+ fs.appendFileSync(this.logPath, JSON.stringify(auditEntry) + '\n');
39
+ }
40
+ catch (err) {
41
+ console.error(`[imessage-audit] Failed to write audit log: ${err.message}`);
42
+ }
43
+ }
44
+ }
45
+ function sha256Prefix(input) {
46
+ return crypto.createHash('sha256').update(input).digest('hex').slice(0, 8);
47
+ }
48
+ function maskIdentifier(id) {
49
+ if (id.startsWith('+') && id.length > 6) {
50
+ return id.slice(0, 4) + '***' + id.slice(-4);
51
+ }
52
+ if (id.includes('@')) {
53
+ const [local, domain] = id.split('@');
54
+ return local.slice(0, 2) + '***@' + domain;
55
+ }
56
+ return '***';
57
+ }
58
+ //# sourceMappingURL=OutboundAuditLog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OutboundAuditLog.js","sourceRoot":"","sources":["../../../src/messaging/imessage/OutboundAuditLog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,MAAM,MAAM,aAAa,CAAC;AAgBjC,MAAM,OAAO,gBAAgB;IACV,OAAO,CAAS;IAEjC,YAAY,OAAe;QACzB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,KASN;QACC,MAAM,UAAU,GAAe;YAC7B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,SAAS,EAAE,cAAc,CAAC,KAAK,CAAC,SAAS,CAAC;YAC1C,aAAa,EAAE,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC;YAC5C,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM;YAC7B,QAAQ,EAAE,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC;YAClC,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI;YAClC,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,UAAU;YACtC,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,IAAI;YACtC,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;YAC/D,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,EAAE,WAAW,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE;SACjE,CAAC;QAEF,IAAI,CAAC;YACH,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;QACrE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,+CAAgD,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACzF,CAAC;IACH,CAAC;CACF;AAED,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED,SAAS,cAAc,CAAC,EAAU;IAChC,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACtC,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC;IAC7C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * OutboundRateLimiter — In-memory sliding-window rate limiter for outbound messages.
3
+ *
4
+ * Two independent limits:
5
+ * 1. Per-contact: max messages per hour (sliding window)
6
+ * 2. Global: max messages per day (rolling 24h window)
7
+ *
8
+ * Resets on server restart (in-memory only). This is acceptable because
9
+ * limits are a safety net, not a billing mechanism.
10
+ */
11
+ export interface RateLimiterConfig {
12
+ /** Max outbound messages per contact per hour (default: 20) */
13
+ maxPerHour: number;
14
+ /** Max outbound messages globally per day (default: 100) */
15
+ maxPerDay: number;
16
+ }
17
+ export interface RateLimitStatus {
18
+ /** Per-contact counts for the current hour window */
19
+ perContact: Map<string, number>;
20
+ /** Global count for the current day window */
21
+ globalToday: number;
22
+ }
23
+ export declare class OutboundRateLimiter {
24
+ private readonly maxPerHour;
25
+ private readonly maxPerDay;
26
+ private contactTimestamps;
27
+ private globalTimestamps;
28
+ constructor(config?: Partial<RateLimiterConfig>);
29
+ /**
30
+ * Check if a send to this recipient is allowed under rate limits.
31
+ * Does NOT record the send — call record() after successful delivery.
32
+ */
33
+ check(recipient: string): {
34
+ allowed: boolean;
35
+ reason?: string;
36
+ };
37
+ /**
38
+ * Record a sent message for rate tracking.
39
+ */
40
+ record(recipient: string): void;
41
+ /**
42
+ * Get current rate limit status.
43
+ */
44
+ status(): RateLimitStatus;
45
+ /**
46
+ * Get counts for a specific contact (for audit log inclusion).
47
+ */
48
+ countsFor(recipient: string): {
49
+ contactHour: number;
50
+ globalDay: number;
51
+ };
52
+ private _cleanup;
53
+ }
54
+ //# sourceMappingURL=OutboundRateLimiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OutboundRateLimiter.d.ts","sourceRoot":"","sources":["../../../src/messaging/imessage/OutboundRateLimiter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,iBAAiB;IAChC,+DAA+D;IAC/D,UAAU,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,qDAAqD;IACrD,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAGnC,OAAO,CAAC,iBAAiB,CAA+B;IAExD,OAAO,CAAC,gBAAgB,CAAgB;gBAE5B,MAAM,GAAE,OAAO,CAAC,iBAAiB,CAAM;IAKnD;;;OAGG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IA2B/D;;OAEG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAgB/B;;OAEG;IACH,MAAM,IAAI,eAAe;IAiBzB;;OAEG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE;IAYxE,OAAO,CAAC,QAAQ;CAgBjB"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * OutboundRateLimiter — In-memory sliding-window rate limiter for outbound messages.
3
+ *
4
+ * Two independent limits:
5
+ * 1. Per-contact: max messages per hour (sliding window)
6
+ * 2. Global: max messages per day (rolling 24h window)
7
+ *
8
+ * Resets on server restart (in-memory only). This is acceptable because
9
+ * limits are a safety net, not a billing mechanism.
10
+ */
11
+ export class OutboundRateLimiter {
12
+ maxPerHour;
13
+ maxPerDay;
14
+ // Timestamps of sent messages per contact (for sliding window)
15
+ contactTimestamps = new Map();
16
+ // Global timestamps
17
+ globalTimestamps = [];
18
+ constructor(config = {}) {
19
+ this.maxPerHour = config.maxPerHour ?? 20;
20
+ this.maxPerDay = config.maxPerDay ?? 100;
21
+ }
22
+ /**
23
+ * Check if a send to this recipient is allowed under rate limits.
24
+ * Does NOT record the send — call record() after successful delivery.
25
+ */
26
+ check(recipient) {
27
+ const now = Date.now();
28
+ const hourAgo = now - 3_600_000;
29
+ const dayAgo = now - 86_400_000;
30
+ // Per-contact hourly check
31
+ const contactTs = this.contactTimestamps.get(recipient) || [];
32
+ const recentContact = contactTs.filter(t => t > hourAgo);
33
+ if (recentContact.length >= this.maxPerHour) {
34
+ return {
35
+ allowed: false,
36
+ reason: `per-contact hourly limit reached (${this.maxPerHour}/hr)`,
37
+ };
38
+ }
39
+ // Global daily check
40
+ const recentGlobal = this.globalTimestamps.filter(t => t > dayAgo);
41
+ if (recentGlobal.length >= this.maxPerDay) {
42
+ return {
43
+ allowed: false,
44
+ reason: `global daily limit reached (${this.maxPerDay}/day)`,
45
+ };
46
+ }
47
+ return { allowed: true };
48
+ }
49
+ /**
50
+ * Record a sent message for rate tracking.
51
+ */
52
+ record(recipient) {
53
+ const now = Date.now();
54
+ // Contact timestamps
55
+ if (!this.contactTimestamps.has(recipient)) {
56
+ this.contactTimestamps.set(recipient, []);
57
+ }
58
+ this.contactTimestamps.get(recipient).push(now);
59
+ // Global timestamps
60
+ this.globalTimestamps.push(now);
61
+ // Periodic cleanup — evict old entries to prevent memory growth
62
+ this._cleanup();
63
+ }
64
+ /**
65
+ * Get current rate limit status.
66
+ */
67
+ status() {
68
+ const now = Date.now();
69
+ const hourAgo = now - 3_600_000;
70
+ const dayAgo = now - 86_400_000;
71
+ const perContact = new Map();
72
+ for (const [contact, timestamps] of this.contactTimestamps) {
73
+ const count = timestamps.filter(t => t > hourAgo).length;
74
+ if (count > 0)
75
+ perContact.set(contact, count);
76
+ }
77
+ return {
78
+ perContact,
79
+ globalToday: this.globalTimestamps.filter(t => t > dayAgo).length,
80
+ };
81
+ }
82
+ /**
83
+ * Get counts for a specific contact (for audit log inclusion).
84
+ */
85
+ countsFor(recipient) {
86
+ const now = Date.now();
87
+ const hourAgo = now - 3_600_000;
88
+ const dayAgo = now - 86_400_000;
89
+ const contactTs = this.contactTimestamps.get(recipient) || [];
90
+ return {
91
+ contactHour: contactTs.filter(t => t > hourAgo).length,
92
+ globalDay: this.globalTimestamps.filter(t => t > dayAgo).length,
93
+ };
94
+ }
95
+ _cleanup() {
96
+ const dayAgo = Date.now() - 86_400_000;
97
+ // Clean global
98
+ this.globalTimestamps = this.globalTimestamps.filter(t => t > dayAgo);
99
+ // Clean per-contact
100
+ for (const [contact, timestamps] of this.contactTimestamps) {
101
+ const filtered = timestamps.filter(t => t > dayAgo);
102
+ if (filtered.length === 0) {
103
+ this.contactTimestamps.delete(contact);
104
+ }
105
+ else {
106
+ this.contactTimestamps.set(contact, filtered);
107
+ }
108
+ }
109
+ }
110
+ }
111
+ //# sourceMappingURL=OutboundRateLimiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OutboundRateLimiter.js","sourceRoot":"","sources":["../../../src/messaging/imessage/OutboundRateLimiter.ts"],"names":[],"mappings":"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"}