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.
- package/config/skills/agent/core/whatsapp-draft/SKILL.md +80 -0
- package/config/skills/agent/core/whatsapp-draft/execute.sh +84 -0
- package/config/skills/agent/core/whatsapp-draft/execute.test.sh +70 -0
- package/config/skills/agent/core/whatsapp-inbox/SKILL.md +75 -0
- package/config/skills/agent/core/whatsapp-inbox/execute.sh +87 -0
- package/config/skills/agent/core/whatsapp-inbox/execute.test.sh +68 -0
- package/config/skills/agent/core/whatsapp-read/SKILL.md +80 -0
- package/config/skills/agent/core/whatsapp-read/execute.sh +116 -0
- package/config/skills/agent/core/whatsapp-read/execute.test.sh +84 -0
- package/config/skills/agent/core/whatsapp-send/SKILL.md +70 -0
- package/config/skills/agent/core/whatsapp-send/execute.sh +84 -0
- package/config/skills/agent/core/whatsapp-send/execute.test.sh +79 -0
- package/dist/backend/backend/src/constants.d.ts +107 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +105 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp-inbox.controller.d.ts +73 -0
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp-inbox.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp-inbox.controller.js +310 -0
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp-inbox.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.js +53 -4
- package/dist/backend/backend/src/controllers/whatsapp/whatsapp.controller.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +5 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-session.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-session.service.js +13 -0
- package/dist/backend/backend/src/services/browser/browser-session.service.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +29 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
- package/dist/backend/backend/src/services/whatsapp/index.d.ts +5 -1
- package/dist/backend/backend/src/services/whatsapp/index.d.ts.map +1 -1
- package/dist/backend/backend/src/services/whatsapp/index.js +5 -1
- package/dist/backend/backend/src/services/whatsapp/index.js.map +1 -1
- package/dist/backend/backend/src/services/whatsapp/whatsapp-connection-config.d.ts +59 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-connection-config.d.ts.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-connection-config.js +108 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-connection-config.js.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-draft-gate.d.ts +74 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-draft-gate.d.ts.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-draft-gate.js +82 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-draft-gate.js.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox-capture.d.ts +184 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox-capture.d.ts.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox-capture.js +420 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox-capture.js.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox.store.d.ts +243 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox.store.d.ts.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox.store.js +538 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-inbox.store.js.map +1 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.d.ts +32 -2
- package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.js +65 -9
- package/dist/backend/backend/src/services/whatsapp/whatsapp-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.d.ts.map +1 -1
- package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.js +11 -0
- package/dist/backend/backend/src/services/whatsapp/whatsapp-orchestrator-bridge.js.map +1 -1
- package/dist/backend/backend/src/services/whatsapp/whatsapp.service.d.ts +59 -1
- package/dist/backend/backend/src/services/whatsapp/whatsapp.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/whatsapp/whatsapp.service.js +159 -9
- package/dist/backend/backend/src/services/whatsapp/whatsapp.service.js.map +1 -1
- package/dist/backend/backend/src/types/whatsapp.types.d.ts +108 -0
- package/dist/backend/backend/src/types/whatsapp.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/whatsapp.types.js +9 -0
- package/dist/backend/backend/src/types/whatsapp.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +107 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +105 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +5 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/frontend/dist/assets/{index-07e338a9.js → index-daf43f1a.js} +268 -268
- package/frontend/dist/index.html +1 -1
- 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
|
-
*
|
|
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
|
|
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
|
|
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"}
|