crewly 1.20.121 → 1.20.123

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 (78) hide show
  1. package/config/skills/agent/core/whatsapp-draft/SKILL.md +80 -0
  2. package/config/skills/agent/core/whatsapp-draft/execute.sh +84 -0
  3. package/config/skills/agent/core/whatsapp-draft/execute.test.sh +70 -0
  4. package/config/skills/agent/core/whatsapp-inbox/SKILL.md +75 -0
  5. package/config/skills/agent/core/whatsapp-inbox/execute.sh +87 -0
  6. package/config/skills/agent/core/whatsapp-inbox/execute.test.sh +68 -0
  7. package/config/skills/agent/core/whatsapp-read/SKILL.md +80 -0
  8. package/config/skills/agent/core/whatsapp-read/execute.sh +116 -0
  9. package/config/skills/agent/core/whatsapp-read/execute.test.sh +84 -0
  10. package/config/skills/agent/core/whatsapp-send/SKILL.md +70 -0
  11. package/config/skills/agent/core/whatsapp-send/execute.sh +84 -0
  12. package/config/skills/agent/core/whatsapp-send/execute.test.sh +79 -0
  13. package/dist/backend/backend/src/constants.d.ts +107 -0
  14. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  15. package/dist/backend/backend/src/constants.js +105 -0
  16. package/dist/backend/backend/src/constants.js.map +1 -1
  17. package/dist/backend/backend/src/controllers/whatsapp/whatsapp-inbox.controller.d.ts +73 -0
  18. package/dist/backend/backend/src/controllers/whatsapp/whatsapp-inbox.controller.d.ts.map +1 -0
  19. package/dist/backend/backend/src/controllers/whatsapp/whatsapp-inbox.controller.js +310 -0
  20. package/dist/backend/backend/src/controllers/whatsapp/whatsapp-inbox.controller.js.map +1 -0
  21. package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.d.ts.map +1 -1
  22. package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.js +53 -4
  23. package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.js.map +1 -1
  24. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  25. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +5 -0
  26. package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  27. package/dist/backend/backend/src/services/browser/browser-session.service.d.ts.map +1 -1
  28. package/dist/backend/backend/src/services/browser/browser-session.service.js +13 -0
  29. package/dist/backend/backend/src/services/browser/browser-session.service.js.map +1 -1
  30. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
  31. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +29 -1
  32. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
  33. package/dist/backend/backend/src/services/whatsapp/index.d.ts +5 -1
  34. package/dist/backend/backend/src/services/whatsapp/index.d.ts.map +1 -1
  35. package/dist/backend/backend/src/services/whatsapp/index.js +5 -1
  36. package/dist/backend/backend/src/services/whatsapp/index.js.map +1 -1
  37. package/dist/backend/backend/src/services/whatsapp/whatsapp-connection-config.d.ts +59 -0
  38. package/dist/backend/backend/src/services/whatsapp/whatsapp-connection-config.d.ts.map +1 -0
  39. package/dist/backend/backend/src/services/whatsapp/whatsapp-connection-config.js +108 -0
  40. package/dist/backend/backend/src/services/whatsapp/whatsapp-connection-config.js.map +1 -0
  41. package/dist/backend/backend/src/services/whatsapp/whatsapp-draft-gate.d.ts +74 -0
  42. package/dist/backend/backend/src/services/whatsapp/whatsapp-draft-gate.d.ts.map +1 -0
  43. package/dist/backend/backend/src/services/whatsapp/whatsapp-draft-gate.js +82 -0
  44. package/dist/backend/backend/src/services/whatsapp/whatsapp-draft-gate.js.map +1 -0
  45. package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox-capture.d.ts +184 -0
  46. package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox-capture.d.ts.map +1 -0
  47. package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox-capture.js +420 -0
  48. package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox-capture.js.map +1 -0
  49. package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox.store.d.ts +243 -0
  50. package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox.store.d.ts.map +1 -0
  51. package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox.store.js +538 -0
  52. package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox.store.js.map +1 -0
  53. package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.d.ts +32 -2
  54. package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.d.ts.map +1 -1
  55. package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.js +65 -9
  56. package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.js.map +1 -1
  57. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.d.ts.map +1 -1
  58. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.js +11 -0
  59. package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.js.map +1 -1
  60. package/dist/backend/backend/src/services/whatsapp/whatsapp.service.d.ts +59 -1
  61. package/dist/backend/backend/src/services/whatsapp/whatsapp.service.d.ts.map +1 -1
  62. package/dist/backend/backend/src/services/whatsapp/whatsapp.service.js +159 -9
  63. package/dist/backend/backend/src/services/whatsapp/whatsapp.service.js.map +1 -1
  64. package/dist/backend/backend/src/types/whatsapp.types.d.ts +108 -0
  65. package/dist/backend/backend/src/types/whatsapp.types.d.ts.map +1 -1
  66. package/dist/backend/backend/src/types/whatsapp.types.js +9 -0
  67. package/dist/backend/backend/src/types/whatsapp.types.js.map +1 -1
  68. package/dist/backend/build-info.json +2 -2
  69. package/dist/cli/backend/src/constants.d.ts +107 -0
  70. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  71. package/dist/cli/backend/src/constants.js +105 -0
  72. package/dist/cli/backend/src/constants.js.map +1 -1
  73. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
  74. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +5 -0
  75. package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
  76. package/frontend/dist/assets/{index-07e338a9.js → index-daf43f1a.js} +268 -268
  77. package/frontend/dist/index.html +1 -1
  78. package/package.json +1 -1
@@ -0,0 +1,538 @@
1
+ /**
2
+ * WhatsApp Inbox Store
3
+ *
4
+ * Local SQLite store behind the WhatsApp inbox connector: the chats and
5
+ * messages captured from the owner's personal account (read-only), and the
6
+ * reply drafts agents propose. Lives at `~/.crewly/whatsapp/inbox.db`
7
+ * (resolved through `getCrewlyHomePath()`, so `CREWLY_HOME` isolates it).
8
+ *
9
+ * Nothing in here talks to WhatsApp. Sending a draft is the controller's
10
+ * job, gated on the owner's confirmation; this store only records the
11
+ * draft's lifecycle (`pending` → `sending` → `sent` / back to `pending`,
12
+ * or `discarded`) with an atomic claim so a draft can never be sent twice.
13
+ *
14
+ * @module services/whatsapp/whatsapp-inbox.store
15
+ */
16
+ import { randomUUID } from 'crypto';
17
+ import { chmodSync, existsSync, mkdirSync } from 'fs';
18
+ import path from 'path';
19
+ import { WHATSAPP_CONSTANTS } from '../../constants.js';
20
+ import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
21
+ import { createBareModuleRequire } from '../../utils/node-require.utils.js';
22
+ import { loadNativeAddonOrFatal } from '../../utils/native-binding.utils.js';
23
+ /** CJS `require` for the native addon (this module compiles to ESM). */
24
+ const nodeRequire = createBareModuleRequire(typeof require === 'function' ? require : null);
25
+ /** Owner-only permissions: the inbox holds private conversations. */
26
+ const PRIVATE_DIR_MODE = 0o700;
27
+ const PRIVATE_FILE_MODE = 0o600;
28
+ /** In-memory database name accepted by better-sqlite3 (tests). */
29
+ export const IN_MEMORY_DB = ':memory:';
30
+ /** Idempotent schema; safe to run on every open. */
31
+ const INBOX_SCHEMA_SQL = `
32
+ CREATE TABLE IF NOT EXISTS chats (
33
+ id TEXT PRIMARY KEY,
34
+ name TEXT,
35
+ name_rank INTEGER NOT NULL DEFAULT 0,
36
+ is_group INTEGER NOT NULL DEFAULT 0,
37
+ last_message_at INTEGER
38
+ );
39
+ CREATE TABLE IF NOT EXISTS messages (
40
+ id TEXT PRIMARY KEY,
41
+ chat_id TEXT NOT NULL,
42
+ from_me INTEGER NOT NULL,
43
+ sender_jid TEXT,
44
+ sender_name TEXT,
45
+ text TEXT NOT NULL DEFAULT '',
46
+ ts INTEGER NOT NULL,
47
+ kind TEXT NOT NULL
48
+ );
49
+ CREATE INDEX IF NOT EXISTS idx_wa_messages_chat_ts ON messages(chat_id, ts);
50
+ CREATE INDEX IF NOT EXISTS idx_wa_messages_chat_fromme_ts ON messages(chat_id, from_me, ts);
51
+ CREATE INDEX IF NOT EXISTS idx_wa_chats_last ON chats(last_message_at);
52
+ CREATE TABLE IF NOT EXISTS drafts (
53
+ id TEXT PRIMARY KEY,
54
+ seq INTEGER NOT NULL UNIQUE,
55
+ code TEXT NOT NULL UNIQUE,
56
+ chat_id TEXT NOT NULL,
57
+ text TEXT NOT NULL,
58
+ status TEXT NOT NULL,
59
+ created_at INTEGER NOT NULL,
60
+ created_by TEXT,
61
+ sent_at INTEGER,
62
+ discarded_at INTEGER,
63
+ last_error TEXT
64
+ );
65
+ CREATE INDEX IF NOT EXISTS idx_wa_drafts_status ON drafts(status, created_at);
66
+ `;
67
+ /**
68
+ * Resolve the default inbox database path under the Crewly home.
69
+ *
70
+ * @returns Absolute path to `<crewly home>/whatsapp/inbox.db`
71
+ */
72
+ export function getDefaultInboxDbPath() {
73
+ return path.join(getCrewlyHomePath(), WHATSAPP_CONSTANTS.DATA_DIR, WHATSAPP_CONSTANTS.INBOX_DB_FILE);
74
+ }
75
+ /**
76
+ * Format a draft sequence number as its human code.
77
+ *
78
+ * @param seq - Monotonic sequence number
79
+ * @returns Code such as `W12`
80
+ */
81
+ export function formatDraftCode(seq) {
82
+ return `${WHATSAPP_CONSTANTS.DRAFT_CODE_PREFIX}${seq}`;
83
+ }
84
+ /**
85
+ * Parse a user-typed draft code (`W12`, `w12`, `#W12`, `12`) into its sequence number.
86
+ *
87
+ * @param raw - The code as typed
88
+ * @returns The sequence number, or null when the text is not a draft code
89
+ */
90
+ export function parseDraftCode(raw) {
91
+ const m = /^#?W?(\d+)$/i.exec(raw.trim());
92
+ if (!m)
93
+ return null;
94
+ const n = Number.parseInt(m[1], 10);
95
+ return Number.isSafeInteger(n) && n > 0 ? n : null;
96
+ }
97
+ /**
98
+ * Escape `%`, `_` and the escape char itself for a SQL `LIKE ... ESCAPE '\\'`.
99
+ *
100
+ * @param q - Raw search text
101
+ * @returns Pattern-safe text (wrap in `%` yourself)
102
+ */
103
+ function escapeLike(q) {
104
+ return q.replace(/[\\%_]/g, (c) => `\\${c}`);
105
+ }
106
+ /**
107
+ * Whether a JID names a group chat.
108
+ *
109
+ * @param jid - Chat JID
110
+ * @returns True for `...@g.us`
111
+ */
112
+ function isGroupJid(jid) {
113
+ return jid.endsWith(WHATSAPP_CONSTANTS.GROUP_JID_SUFFIX);
114
+ }
115
+ /**
116
+ * Map a chats row to the public shape.
117
+ *
118
+ * @param r - Raw row
119
+ * @returns Public chat
120
+ */
121
+ function toChat(r) {
122
+ return { id: r.id, name: r.name, isGroup: r.is_group === 1, lastMessageAt: r.last_message_at };
123
+ }
124
+ /**
125
+ * Map a messages row to the public shape.
126
+ *
127
+ * @param r - Raw row
128
+ * @returns Public message
129
+ */
130
+ function toMessage(r) {
131
+ return {
132
+ id: r.id,
133
+ chatId: r.chat_id,
134
+ fromMe: r.from_me === 1,
135
+ senderJid: r.sender_jid,
136
+ senderName: r.sender_name,
137
+ text: r.text,
138
+ ts: r.ts,
139
+ kind: r.kind,
140
+ };
141
+ }
142
+ /**
143
+ * Map a drafts row to the public shape.
144
+ *
145
+ * @param r - Raw row
146
+ * @returns Public draft
147
+ */
148
+ function toDraft(r) {
149
+ return {
150
+ id: r.id,
151
+ code: r.code,
152
+ seq: r.seq,
153
+ chatId: r.chat_id,
154
+ text: r.text,
155
+ status: r.status,
156
+ createdAt: r.created_at,
157
+ createdBy: r.created_by,
158
+ sentAt: r.sent_at,
159
+ discardedAt: r.discarded_at,
160
+ lastError: r.last_error,
161
+ };
162
+ }
163
+ /**
164
+ * SQLite-backed store for the WhatsApp inbox and reply drafts.
165
+ *
166
+ * @example
167
+ * ```typescript
168
+ * const store = new WhatsAppInboxStore(':memory:');
169
+ * store.upsertMessage({ id: 'm1', chatId: '1@s.whatsapp.net', fromMe: false, senderJid: null,
170
+ * senderName: 'Ann', text: 'hi', ts: Date.now(), kind: 'text' });
171
+ * store.listInbox({ limit: 20 }); // → [{ chat, unansweredCount: 1, ... }]
172
+ * ```
173
+ */
174
+ export class WhatsAppInboxStore {
175
+ db;
176
+ /**
177
+ * Open (and migrate) the inbox database.
178
+ *
179
+ * @param dbPath - File path, or `:memory:` for tests. The parent directory
180
+ * is created owner-only (0700) and the file is chmod 0600.
181
+ * @throws NativeBindingFatalError when better-sqlite3 cannot load
182
+ */
183
+ constructor(dbPath = getDefaultInboxDbPath()) {
184
+ const Database = loadNativeAddonOrFatal('better-sqlite3', nodeRequire);
185
+ const inMemory = dbPath === IN_MEMORY_DB;
186
+ if (!inMemory) {
187
+ const dir = path.dirname(dbPath);
188
+ if (!existsSync(dir))
189
+ mkdirSync(dir, { recursive: true, mode: PRIVATE_DIR_MODE });
190
+ }
191
+ this.db = new Database(dbPath);
192
+ if (!inMemory) {
193
+ this.db.pragma('journal_mode = WAL');
194
+ try {
195
+ chmodSync(dbPath, PRIVATE_FILE_MODE);
196
+ }
197
+ catch {
198
+ // Best effort — a filesystem without POSIX modes still works.
199
+ }
200
+ }
201
+ this.db.exec(INBOX_SCHEMA_SQL);
202
+ }
203
+ /**
204
+ * Close the database handle.
205
+ */
206
+ close() {
207
+ this.db.close();
208
+ }
209
+ // ---------------------------------------------------------------------------
210
+ // Chats & messages (capture side)
211
+ // ---------------------------------------------------------------------------
212
+ /**
213
+ * Insert or update a chat.
214
+ *
215
+ * The name only changes when the new source ranks at least as high as the
216
+ * one that set the current name (an address-book name is never replaced by
217
+ * a pushName). `lastMessageAt` only moves forward.
218
+ *
219
+ * @param input - Chat fields known by this source
220
+ */
221
+ upsertChat(input) {
222
+ const name = input.name && input.name.trim().length > 0 ? input.name.trim() : null;
223
+ const rank = name ? (input.nameRank ?? WHATSAPP_CONSTANTS.NAME_RANKS.CHAT) : WHATSAPP_CONSTANTS.NAME_RANKS.NONE;
224
+ const isGroup = (input.isGroup ?? isGroupJid(input.id)) ? 1 : 0;
225
+ this.db
226
+ .prepare(`INSERT INTO chats (id, name, name_rank, is_group, last_message_at)
227
+ VALUES (@id, @name, @rank, @isGroup, @last)
228
+ ON CONFLICT(id) DO UPDATE SET
229
+ name = CASE WHEN excluded.name IS NOT NULL AND excluded.name_rank >= chats.name_rank
230
+ THEN excluded.name ELSE chats.name END,
231
+ name_rank = CASE WHEN excluded.name IS NOT NULL AND excluded.name_rank >= chats.name_rank
232
+ THEN excluded.name_rank ELSE chats.name_rank END,
233
+ is_group = MAX(chats.is_group, excluded.is_group),
234
+ last_message_at = CASE
235
+ WHEN excluded.last_message_at IS NULL THEN chats.last_message_at
236
+ WHEN chats.last_message_at IS NULL THEN excluded.last_message_at
237
+ ELSE MAX(chats.last_message_at, excluded.last_message_at) END`)
238
+ .run({ id: input.id, name, rank, isGroup, last: input.lastMessageAt ?? null });
239
+ }
240
+ /**
241
+ * Insert or update one message (idempotent on the message id) and advance
242
+ * its chat's `lastMessageAt`, creating the chat row if needed.
243
+ *
244
+ * A re-delivered message keeps its original `fromMe`, chat and timestamp;
245
+ * text, kind and a newly learned sender name are refreshed (edits, late
246
+ * pushNames).
247
+ *
248
+ * @param msg - The message
249
+ */
250
+ upsertMessage(msg) {
251
+ this.upsertChat({ id: msg.chatId, lastMessageAt: msg.ts });
252
+ this.db
253
+ .prepare(`INSERT INTO messages (id, chat_id, from_me, sender_jid, sender_name, text, ts, kind)
254
+ VALUES (@id, @chatId, @fromMe, @senderJid, @senderName, @text, @ts, @kind)
255
+ ON CONFLICT(id) DO UPDATE SET
256
+ text = CASE WHEN excluded.text <> '' THEN excluded.text ELSE messages.text END,
257
+ kind = excluded.kind,
258
+ sender_name = COALESCE(excluded.sender_name, messages.sender_name),
259
+ sender_jid = COALESCE(messages.sender_jid, excluded.sender_jid)`)
260
+ .run({
261
+ id: msg.id,
262
+ chatId: msg.chatId,
263
+ fromMe: msg.fromMe ? 1 : 0,
264
+ senderJid: msg.senderJid,
265
+ senderName: msg.senderName,
266
+ text: msg.text,
267
+ ts: msg.ts,
268
+ kind: msg.kind,
269
+ });
270
+ }
271
+ /**
272
+ * Upsert many messages in one transaction (history sync).
273
+ *
274
+ * @param msgs - Messages
275
+ * @returns How many were written
276
+ */
277
+ upsertMessages(msgs) {
278
+ const tx = this.db.transaction((batch) => {
279
+ for (const m of batch)
280
+ this.upsertMessage(m);
281
+ });
282
+ tx(msgs);
283
+ return msgs.length;
284
+ }
285
+ /**
286
+ * Upsert many chats in one transaction.
287
+ *
288
+ * @param chats - Chats
289
+ */
290
+ upsertChats(chats) {
291
+ const tx = this.db.transaction((batch) => {
292
+ for (const c of batch)
293
+ this.upsertChat(c);
294
+ });
295
+ tx(chats);
296
+ }
297
+ // ---------------------------------------------------------------------------
298
+ // Read API
299
+ // ---------------------------------------------------------------------------
300
+ /**
301
+ * Look up one chat.
302
+ *
303
+ * @param id - Chat JID
304
+ * @returns The chat, or null
305
+ */
306
+ getChat(id) {
307
+ const row = this.db.prepare('SELECT * FROM chats WHERE id = ?').get(id);
308
+ return row ? toChat(row) : null;
309
+ }
310
+ /**
311
+ * Chats that need a reply: the newest message is not the owner's.
312
+ *
313
+ * `unansweredCount` counts inbound messages after the owner's last message
314
+ * in that chat (all of them when the owner never wrote there).
315
+ *
316
+ * @param opts - Limit and whether to include groups
317
+ * @returns Entries, newest first
318
+ */
319
+ listInbox(opts) {
320
+ const rows = this.db
321
+ .prepare(`WITH ranked AS (
322
+ SELECT m.*, ROW_NUMBER() OVER (PARTITION BY m.chat_id ORDER BY m.ts DESC, m.rowid DESC) AS rn
323
+ FROM messages m
324
+ ),
325
+ last_out AS (
326
+ SELECT chat_id, MAX(ts) AS t FROM messages WHERE from_me = 1 GROUP BY chat_id
327
+ )
328
+ SELECT c.id, c.name, c.name_rank, c.is_group, c.last_message_at,
329
+ r.text AS last_text, r.kind AS last_kind, r.sender_name AS last_sender_name, r.ts AS last_ts,
330
+ (SELECT COUNT(*) FROM messages i
331
+ WHERE i.chat_id = c.id AND i.from_me = 0
332
+ AND i.ts > COALESCE((SELECT t FROM last_out WHERE last_out.chat_id = c.id), -1)
333
+ ) AS unanswered
334
+ FROM chats c
335
+ JOIN ranked r ON r.chat_id = c.id AND r.rn = 1
336
+ WHERE r.from_me = 0 AND (@includeGroups = 1 OR c.is_group = 0)
337
+ ORDER BY r.ts DESC
338
+ LIMIT @limit`)
339
+ .all({ includeGroups: opts.includeGroups ? 1 : 0, limit: opts.limit });
340
+ return rows.map((r) => ({
341
+ chat: toChat(r),
342
+ unansweredCount: r.unanswered,
343
+ lastText: r.last_text,
344
+ lastKind: r.last_kind,
345
+ lastSenderName: r.last_sender_name,
346
+ lastMessageAt: r.last_ts,
347
+ }));
348
+ }
349
+ /**
350
+ * Recent chats, optionally filtered by name or JID substring.
351
+ *
352
+ * @param opts - Limit and optional query
353
+ * @returns Chats, most recently active first (chats with no messages last)
354
+ */
355
+ listChats(opts) {
356
+ const q = opts.q?.trim();
357
+ const rows = (q
358
+ ? this.db
359
+ .prepare(`SELECT * FROM chats
360
+ WHERE name LIKE @p ESCAPE '\\' OR id LIKE @p ESCAPE '\\'
361
+ ORDER BY last_message_at IS NULL, last_message_at DESC LIMIT @limit`)
362
+ .all({ p: `%${escapeLike(q)}%`, limit: opts.limit })
363
+ : this.db
364
+ .prepare('SELECT * FROM chats ORDER BY last_message_at IS NULL, last_message_at DESC LIMIT ?')
365
+ .all(opts.limit));
366
+ return rows.map(toChat);
367
+ }
368
+ /**
369
+ * One page of a chat's messages, in chronological order.
370
+ *
371
+ * @param chatId - Chat JID
372
+ * @param opts - Page size and an exclusive upper bound (epoch ms) for paging back
373
+ * @returns The newest `limit` messages older than `before`, oldest first
374
+ */
375
+ listMessages(chatId, opts) {
376
+ const rows = (opts.before !== undefined
377
+ ? this.db
378
+ .prepare('SELECT * FROM messages WHERE chat_id = ? AND ts < ? ORDER BY ts DESC, rowid DESC LIMIT ?')
379
+ .all(chatId, opts.before, opts.limit)
380
+ : this.db
381
+ .prepare('SELECT * FROM messages WHERE chat_id = ? ORDER BY ts DESC, rowid DESC LIMIT ?')
382
+ .all(chatId, opts.limit));
383
+ return rows.map(toMessage).reverse();
384
+ }
385
+ /**
386
+ * Substring search over message text (case-insensitive for ASCII).
387
+ *
388
+ * @param q - Search text (must be non-empty)
389
+ * @param limit - Row cap
390
+ * @returns Hits, newest first
391
+ */
392
+ search(q, limit) {
393
+ const rows = this.db
394
+ .prepare(`SELECT m.*, c.name AS chat_name FROM messages m
395
+ LEFT JOIN chats c ON c.id = m.chat_id
396
+ WHERE m.text LIKE ? ESCAPE '\\'
397
+ ORDER BY m.ts DESC LIMIT ?`)
398
+ .all(`%${escapeLike(q.trim())}%`, limit);
399
+ return rows.map((r) => ({ ...toMessage(r), chatName: r.chat_name }));
400
+ }
401
+ // ---------------------------------------------------------------------------
402
+ // Drafts
403
+ // ---------------------------------------------------------------------------
404
+ /**
405
+ * Record a reply draft with the next monotonic code.
406
+ *
407
+ * @param input - Recipient, text, author session (null = owner) and clock
408
+ * @returns The stored draft (status `pending`)
409
+ */
410
+ createDraft(input) {
411
+ const create = this.db.transaction(() => {
412
+ const row = this.db.prepare('SELECT COALESCE(MAX(seq), 0) + 1 AS next FROM drafts').get();
413
+ const seq = row.next;
414
+ const id = randomUUID();
415
+ this.db
416
+ .prepare(`INSERT INTO drafts (id, seq, code, chat_id, text, status, created_at, created_by)
417
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
418
+ .run(id, seq, formatDraftCode(seq), input.chatId, input.text, WHATSAPP_CONSTANTS.DRAFT_STATUSES.PENDING, input.now, input.createdBy);
419
+ return this.getDraftById(id);
420
+ });
421
+ return create();
422
+ }
423
+ /**
424
+ * Look up a draft by its opaque id.
425
+ *
426
+ * @param id - Draft id
427
+ * @returns The draft, or null
428
+ */
429
+ getDraftById(id) {
430
+ const row = this.db.prepare('SELECT * FROM drafts WHERE id = ?').get(id);
431
+ return row ? toDraft(row) : null;
432
+ }
433
+ /**
434
+ * Look up a draft by id or by human code (`W12`, `12`, `#W12`).
435
+ *
436
+ * @param ref - Id or code
437
+ * @returns The draft, or null
438
+ */
439
+ findDraft(ref) {
440
+ const byId = this.getDraftById(ref);
441
+ if (byId)
442
+ return byId;
443
+ const seq = parseDraftCode(ref);
444
+ if (seq === null)
445
+ return null;
446
+ const row = this.db.prepare('SELECT * FROM drafts WHERE seq = ?').get(seq);
447
+ return row ? toDraft(row) : null;
448
+ }
449
+ /**
450
+ * List drafts, newest first.
451
+ *
452
+ * @param opts - Optional status filter and row cap
453
+ * @returns Drafts
454
+ */
455
+ listDrafts(opts) {
456
+ const rows = (opts.status
457
+ ? this.db.prepare('SELECT * FROM drafts WHERE status = ? ORDER BY seq DESC LIMIT ?').all(opts.status, opts.limit)
458
+ : this.db.prepare('SELECT * FROM drafts ORDER BY seq DESC LIMIT ?').all(opts.limit));
459
+ return rows.map(toDraft);
460
+ }
461
+ /**
462
+ * Atomically move a draft from `pending` to `sending`.
463
+ *
464
+ * Only one caller can win: this is what makes "a draft can't be sent twice"
465
+ * hold under concurrent requests.
466
+ *
467
+ * @param id - Draft id
468
+ * @returns True when this caller now holds the send claim
469
+ */
470
+ claimDraftForSend(id) {
471
+ const res = this.db
472
+ .prepare('UPDATE drafts SET status = ? WHERE id = ? AND status = ?')
473
+ .run(WHATSAPP_CONSTANTS.DRAFT_STATUSES.SENDING, id, WHATSAPP_CONSTANTS.DRAFT_STATUSES.PENDING);
474
+ return res.changes === 1;
475
+ }
476
+ /**
477
+ * Mark a claimed draft as sent.
478
+ *
479
+ * @param id - Draft id
480
+ * @param now - Epoch ms of the send
481
+ */
482
+ markDraftSent(id, now) {
483
+ this.db
484
+ .prepare('UPDATE drafts SET status = ?, sent_at = ?, last_error = NULL WHERE id = ? AND status = ?')
485
+ .run(WHATSAPP_CONSTANTS.DRAFT_STATUSES.SENT, now, id, WHATSAPP_CONSTANTS.DRAFT_STATUSES.SENDING);
486
+ }
487
+ /**
488
+ * Return a claimed draft to `pending` after a failed send, keeping the error.
489
+ *
490
+ * @param id - Draft id
491
+ * @param error - Why the send failed
492
+ */
493
+ releaseDraftClaim(id, error) {
494
+ this.db
495
+ .prepare('UPDATE drafts SET status = ?, last_error = ? WHERE id = ? AND status = ?')
496
+ .run(WHATSAPP_CONSTANTS.DRAFT_STATUSES.PENDING, error, id, WHATSAPP_CONSTANTS.DRAFT_STATUSES.SENDING);
497
+ }
498
+ /**
499
+ * Discard a pending draft.
500
+ *
501
+ * @param id - Draft id
502
+ * @param now - Epoch ms
503
+ * @returns True when the draft was pending and is now discarded
504
+ */
505
+ discardDraft(id, now) {
506
+ const res = this.db
507
+ .prepare('UPDATE drafts SET status = ?, discarded_at = ? WHERE id = ? AND status = ?')
508
+ .run(WHATSAPP_CONSTANTS.DRAFT_STATUSES.DISCARDED, now, id, WHATSAPP_CONSTANTS.DRAFT_STATUSES.PENDING);
509
+ return res.changes === 1;
510
+ }
511
+ }
512
+ /** Process-wide store instance (opened lazily). */
513
+ let storeInstance = null;
514
+ /**
515
+ * Get the process-wide inbox store, opening it on first use.
516
+ *
517
+ * @returns The store at {@link getDefaultInboxDbPath}
518
+ */
519
+ export function getWhatsAppInboxStore() {
520
+ if (!storeInstance)
521
+ storeInstance = new WhatsAppInboxStore();
522
+ return storeInstance;
523
+ }
524
+ /**
525
+ * Close and forget the process-wide store (tests, shutdown).
526
+ */
527
+ export function resetWhatsAppInboxStore() {
528
+ if (storeInstance) {
529
+ try {
530
+ storeInstance.close();
531
+ }
532
+ catch {
533
+ // Already closed.
534
+ }
535
+ }
536
+ storeInstance = null;
537
+ }
538
+ //# sourceMappingURL=whatsapp-inbox.store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whatsapp-inbox.store.js","sourceRoot":"","sources":["../../../../../../backend/src/services/whatsapp/whatsapp-inbox.store.ts"],"names":[],"mappings":"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"}
@@ -2,11 +2,14 @@
2
2
  * WhatsApp Initializer
3
3
  *
4
4
  * Handles automatic WhatsApp connection on application startup.
5
- * Checks for environment variables and initializes if configured.
5
+ * Connects when `WHATSAPP_ENABLED=true`, or when the owner connected from the
6
+ * dashboard earlier (persisted `~/.crewly/whatsapp/connection.json` with
7
+ * auto-connect on). The orchestrator bridge (auto-replies) is started only in
8
+ * `assistant` mode — never in `inbox` mode.
6
9
  *
7
10
  * @module services/whatsapp/initializer
8
11
  */
9
- import type { WhatsAppConfig } from '../../types/whatsapp.types.js';
12
+ import { type WhatsAppConfig, type WhatsAppMode } from '../../types/whatsapp.types.js';
10
13
  import type { MessageQueueService } from '../messaging/message-queue.service.js';
11
14
  /**
12
15
  * Result of initialization attempt
@@ -38,6 +41,33 @@ export declare function isWhatsAppConfigured(): boolean;
38
41
  * @returns WhatsAppConfig object or null if not configured
39
42
  */
40
43
  export declare function getWhatsAppConfigFromEnv(): WhatsAppConfig | null;
44
+ /**
45
+ * Read `WHATSAPP_MODE` (`assistant` | `inbox`).
46
+ *
47
+ * @returns The mode, or undefined when unset or invalid
48
+ */
49
+ export declare function getWhatsAppModeFromEnv(): WhatsAppMode | undefined;
50
+ /**
51
+ * Work out what to connect with at startup, if anything.
52
+ *
53
+ * - `WHATSAPP_ENABLED=true`: env config; mode from `WHATSAPP_MODE`, else the
54
+ * persisted mode, else `assistant` (what the env path always meant).
55
+ * - Otherwise: the persisted dashboard connection, when auto-connect is on.
56
+ *
57
+ * @returns Config to initialize with, or null to stay disconnected
58
+ */
59
+ export declare function resolveStartupWhatsAppConfig(): Promise<WhatsAppConfig | null>;
60
+ /**
61
+ * Start or stop the orchestrator bridge to match a connection mode.
62
+ *
63
+ * `assistant` starts it (routes messages to the orchestrator and replies).
64
+ * `inbox` tears down any bridge left from an earlier assistant connection
65
+ * so nothing can auto-reply.
66
+ *
67
+ * @param mode - Connection mode
68
+ * @param messageQueueService - Queue for orchestrator delivery (assistant only)
69
+ */
70
+ export declare function applyBridgeForMode(mode: WhatsAppMode, messageQueueService?: MessageQueueService): Promise<void>;
41
71
  /**
42
72
  * Initialize WhatsApp integration if configured via environment variables.
43
73
  *
@@ -1 +1 @@
1
- {"version":3,"file":"whatsapp-initializer.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/whatsapp/whatsapp-initializer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACpE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAKjF;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,SAAS,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC3C;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,cAAc,GAAG,IAAI,CAUhE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,8BAA8B,CAClD,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CA2B7B;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAYtD"}
1
+ {"version":3,"file":"whatsapp-initializer.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/whatsapp/whatsapp-initializer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAkB,KAAK,cAAc,EAAE,KAAK,YAAY,EAAE,MAAM,+BAA+B,CAAC;AACvG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAOjF;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,SAAS,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC3C;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,cAAc,GAAG,IAAI,CAWhE;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,YAAY,GAAG,SAAS,CAGjE;AAED;;;;;;;;GAQG;AACH,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAWnF;AAED;;;;;;;;;GASG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,YAAY,EAClB,mBAAmB,CAAC,EAAE,mBAAmB,GACxC,OAAO,CAAC,IAAI,CAAC,CAUf;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,8BAA8B,CAClD,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,kBAAkB,CAAC,CAqB7B;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAYtD"}