@diegoaltoworks/chatter 0.61.0 → 0.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -17
- package/dist/auth/secretStrength.d.ts +1 -1
- package/dist/auth/secretStrength.d.ts.map +1 -1
- package/dist/bin/create-apikey.js +45 -28
- package/dist/bin/wa-pair.js +113 -46
- package/dist/channels/backoff.d.ts +10 -0
- package/dist/channels/backoff.d.ts.map +1 -0
- package/dist/channels/gates.d.ts +15 -8
- package/dist/channels/gates.d.ts.map +1 -1
- package/dist/channels/index.d.ts +1 -1
- package/dist/channels/index.d.ts.map +1 -1
- package/dist/channels/index.js +38 -5
- package/dist/channels/index.mjs +37 -5
- package/dist/channels/matrix/api.d.ts +25 -6
- package/dist/channels/matrix/api.d.ts.map +1 -1
- package/dist/channels/matrix/channel.d.ts +4 -56
- package/dist/channels/matrix/channel.d.ts.map +1 -1
- package/dist/channels/matrix/handler.d.ts +16 -9
- package/dist/channels/matrix/handler.d.ts.map +1 -1
- package/dist/channels/matrix/index.d.ts +2 -2
- package/dist/channels/matrix/index.d.ts.map +1 -1
- package/dist/channels/matrix/index.js +196 -40
- package/dist/channels/matrix/index.mjs +189 -39
- package/dist/channels/matrix/sync.d.ts +4 -1
- package/dist/channels/matrix/sync.d.ts.map +1 -1
- package/dist/channels/matrix/updates.d.ts +39 -10
- package/dist/channels/matrix/updates.d.ts.map +1 -1
- package/dist/channels/pipeline.d.ts +14 -14
- package/dist/channels/pipeline.d.ts.map +1 -1
- package/dist/channels/polling.d.ts +67 -0
- package/dist/channels/polling.d.ts.map +1 -0
- package/dist/channels/telegram/channel.d.ts +2 -56
- package/dist/channels/telegram/channel.d.ts.map +1 -1
- package/dist/channels/telegram/index.js +53 -23
- package/dist/channels/telegram/index.mjs +53 -23
- package/dist/channels/telegram/poll.d.ts +5 -1
- package/dist/channels/telegram/poll.d.ts.map +1 -1
- package/dist/channels/telegram/webhook.d.ts +2 -10
- package/dist/channels/telegram/webhook.d.ts.map +1 -1
- package/dist/channels/whatsapp/authState.d.ts +40 -9
- package/dist/channels/whatsapp/authState.d.ts.map +1 -1
- package/dist/channels/whatsapp/channel.d.ts +39 -4
- package/dist/channels/whatsapp/channel.d.ts.map +1 -1
- package/dist/channels/whatsapp/inbound.d.ts +23 -25
- package/dist/channels/whatsapp/inbound.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.d.ts +3 -3
- package/dist/channels/whatsapp/index.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.js +188 -66
- package/dist/channels/whatsapp/index.mjs +185 -66
- package/dist/channels/whatsapp/pairing.d.ts.map +1 -1
- package/dist/client/src/Chat.d.ts.map +1 -1
- package/dist/client/src/ChatButton.d.ts.map +1 -1
- package/dist/client/src/browser.d.ts.map +1 -1
- package/dist/core/answer.d.ts +8 -0
- package/dist/core/answer.d.ts.map +1 -1
- package/dist/core/buildLock.d.ts +52 -0
- package/dist/core/buildLock.d.ts.map +1 -0
- package/dist/core/pipeline.d.ts +42 -5
- package/dist/core/pipeline.d.ts.map +1 -1
- package/dist/core/prompts.d.ts +2 -0
- package/dist/core/prompts.d.ts.map +1 -1
- package/dist/core/retrieval.d.ts +100 -25
- package/dist/core/retrieval.d.ts.map +1 -1
- package/dist/core/session.d.ts +8 -0
- package/dist/core/session.d.ts.map +1 -1
- package/dist/flows/index.d.ts +1 -1
- package/dist/flows/index.d.ts.map +1 -1
- package/dist/flows/index.js +8 -6
- package/dist/flows/index.mjs +7 -6
- package/dist/flows/intent.d.ts +1 -1
- package/dist/flows/intent.d.ts.map +1 -1
- package/dist/flows/loader.d.ts +1 -2
- package/dist/flows/loader.d.ts.map +1 -1
- package/dist/flows/params.d.ts +1 -1
- package/dist/flows/params.d.ts.map +1 -1
- package/dist/flows/session.d.ts +4 -2
- package/dist/flows/session.d.ts.map +1 -1
- package/dist/flows/types.d.ts +2 -3
- package/dist/flows/types.d.ts.map +1 -1
- package/dist/history/index.d.ts +1 -1
- package/dist/history/index.d.ts.map +1 -1
- package/dist/history/index.js +6 -3
- package/dist/history/index.mjs +5 -3
- package/dist/history/tursoStore.d.ts +6 -4
- package/dist/history/tursoStore.d.ts.map +1 -1
- package/dist/images/index.js +2 -2
- package/dist/images/index.mjs +2 -2
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +596 -295
- package/dist/index.mjs +595 -295
- package/dist/mcp-server/index.d.ts +5 -5
- package/dist/mcp-server/index.d.ts.map +1 -1
- package/dist/mcp-server/logger.d.ts +12 -2
- package/dist/mcp-server/logger.d.ts.map +1 -1
- package/dist/mcp-server/rate-limiter.d.ts +10 -3
- package/dist/mcp-server/rate-limiter.d.ts.map +1 -1
- package/dist/mcp-server/types.d.ts +20 -5
- package/dist/mcp-server/types.d.ts.map +1 -1
- package/dist/mcp-server.js +479 -244
- package/dist/mcp-server.mjs +485 -244
- package/dist/middleware/apiKey.d.ts +10 -0
- package/dist/middleware/apiKey.d.ts.map +1 -0
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/cors.d.ts.map +1 -1
- package/dist/middleware/ratelimit.d.ts.map +1 -1
- package/dist/middleware/referrer.d.ts.map +1 -1
- package/dist/middleware/session.d.ts +1 -1
- package/dist/middleware/session.d.ts.map +1 -1
- package/dist/routes/demo.d.ts.map +1 -1
- package/dist/routes/openai.d.ts.map +1 -1
- package/dist/routes/private.d.ts.map +1 -1
- package/dist/routes/public.d.ts.map +1 -1
- package/dist/scheduler/index.js +1 -1
- package/dist/scheduler/index.mjs +1 -1
- package/dist/scheduler/scheduler.d.ts +9 -1
- package/dist/scheduler/scheduler.d.ts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +491 -268
- package/dist/server.mjs +497 -268
- package/dist/shortener.d.ts +5 -4
- package/dist/shortener.d.ts.map +1 -1
- package/dist/types.d.ts +129 -75
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -3
|
@@ -67,27 +67,55 @@ function ensureAuthSchema(client) {
|
|
|
67
67
|
}
|
|
68
68
|
return ready;
|
|
69
69
|
}
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
function createTursoWaAuthKV(db) {
|
|
71
|
+
return {
|
|
72
|
+
async read(id) {
|
|
73
|
+
await ensureAuthSchema(db);
|
|
74
|
+
const result = await db.execute({
|
|
75
|
+
sql: "SELECT value FROM wa_auth WHERE id = ?",
|
|
76
|
+
args: [id]
|
|
77
|
+
});
|
|
78
|
+
const row = result.rows[0];
|
|
79
|
+
return row ? row.value : null;
|
|
80
|
+
},
|
|
81
|
+
async write(id, value) {
|
|
82
|
+
await ensureAuthSchema(db);
|
|
83
|
+
await db.execute({
|
|
84
|
+
sql: "INSERT OR REPLACE INTO wa_auth (id, value) VALUES (?, ?)",
|
|
85
|
+
args: [id, value]
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
async remove(id) {
|
|
89
|
+
await ensureAuthSchema(db);
|
|
90
|
+
await db.execute({ sql: "DELETE FROM wa_auth WHERE id = ?", args: [id] });
|
|
91
|
+
},
|
|
92
|
+
// Mirrors isAuthRowForSession - keep the two in sync.
|
|
93
|
+
async clear(sessionId) {
|
|
94
|
+
await ensureAuthSchema(db);
|
|
95
|
+
if (sessionId === "default") {
|
|
96
|
+
await db.execute("DELETE FROM wa_auth WHERE id NOT LIKE 's:%'");
|
|
97
|
+
} else {
|
|
98
|
+
await db.execute({
|
|
99
|
+
sql: "DELETE FROM wa_auth WHERE id LIKE ?",
|
|
100
|
+
args: [`s:${sessionId}/%`]
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
async function useAuthState(store, secret, sessionId, runtime) {
|
|
72
107
|
const rowId = (id) => sessionId === "default" ? id : `s:${sessionId}/${id}`;
|
|
73
108
|
async function read(id) {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
});
|
|
78
|
-
const row = result.rows[0];
|
|
79
|
-
if (!row) return null;
|
|
80
|
-
return JSON.parse(await decrypt(row.value, secret), runtime.bufferJSON.reviver);
|
|
109
|
+
const value = await store.read(rowId(id));
|
|
110
|
+
if (value === null) return null;
|
|
111
|
+
return JSON.parse(await decrypt(value, secret), runtime.bufferJSON.reviver);
|
|
81
112
|
}
|
|
82
113
|
async function write(id, value) {
|
|
83
114
|
const payload = await encrypt(JSON.stringify(value, runtime.bufferJSON.replacer), secret);
|
|
84
|
-
await
|
|
85
|
-
sql: "INSERT OR REPLACE INTO wa_auth (id, value) VALUES (?, ?)",
|
|
86
|
-
args: [rowId(id), payload]
|
|
87
|
-
});
|
|
115
|
+
await store.write(rowId(id), payload);
|
|
88
116
|
}
|
|
89
117
|
async function remove(id) {
|
|
90
|
-
await
|
|
118
|
+
await store.remove(rowId(id));
|
|
91
119
|
}
|
|
92
120
|
const creds = await read("creds") ?? runtime.initAuthCreds();
|
|
93
121
|
return {
|
|
@@ -124,18 +152,13 @@ async function useTursoAuthState(db, secret, sessionId, runtime) {
|
|
|
124
152
|
}
|
|
125
153
|
},
|
|
126
154
|
saveCreds: () => write("creds", creds),
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
await db.execute("DELETE FROM wa_auth WHERE id NOT LIKE 's:%'");
|
|
130
|
-
} else {
|
|
131
|
-
await db.execute({
|
|
132
|
-
sql: "DELETE FROM wa_auth WHERE id LIKE ?",
|
|
133
|
-
args: [`s:${sessionId}/%`]
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
155
|
+
// Scoped to THIS session - never nuke a sibling number's pairing.
|
|
156
|
+
clear: () => store.clear(sessionId)
|
|
137
157
|
};
|
|
138
158
|
}
|
|
159
|
+
async function useTursoAuthState(db, secret, sessionId, runtime) {
|
|
160
|
+
return useAuthState(createTursoWaAuthKV(db), secret, sessionId, runtime);
|
|
161
|
+
}
|
|
139
162
|
|
|
140
163
|
// src/channels/whatsapp/baileys.ts
|
|
141
164
|
function wrapMissingBaileysError(cause) {
|
|
@@ -198,6 +221,11 @@ function createConsoleLogger(level = "info") {
|
|
|
198
221
|
};
|
|
199
222
|
}
|
|
200
223
|
|
|
224
|
+
// src/channels/backoff.ts
|
|
225
|
+
function exponentialBackoffMs(baseMs, maxMs, n) {
|
|
226
|
+
return Math.min(baseMs * 2 ** Math.max(0, Math.min(n, 20)), maxMs);
|
|
227
|
+
}
|
|
228
|
+
|
|
201
229
|
// src/channels/whatsapp/lease.ts
|
|
202
230
|
var LEASE_STALE_MS = 9e4;
|
|
203
231
|
var LEASE_HEARTBEAT_MS = 3e4;
|
|
@@ -256,7 +284,7 @@ function createTursoWaLeaseStore(client) {
|
|
|
256
284
|
var RECONNECT_DELAY_MS = 5e3;
|
|
257
285
|
var RECONNECT_MAX_DELAY_MS = 10 * 60 * 1e3;
|
|
258
286
|
function reconnectDelayMs(failures) {
|
|
259
|
-
return
|
|
287
|
+
return exponentialBackoffMs(RECONNECT_DELAY_MS, RECONNECT_MAX_DELAY_MS, failures);
|
|
260
288
|
}
|
|
261
289
|
async function shutdownWaSessions(sessions) {
|
|
262
290
|
await Promise.all(
|
|
@@ -267,11 +295,17 @@ async function shutdownWaSessions(sessions) {
|
|
|
267
295
|
})
|
|
268
296
|
);
|
|
269
297
|
}
|
|
270
|
-
|
|
298
|
+
function claimSession(sessionId, sessions) {
|
|
271
299
|
const handle = sessions.get(sessionId);
|
|
272
|
-
if (!handle) return;
|
|
300
|
+
if (!handle) return void 0;
|
|
273
301
|
sessions.delete(sessionId);
|
|
302
|
+
return handle;
|
|
303
|
+
}
|
|
304
|
+
async function loseSession(sessionId, sessions) {
|
|
305
|
+
const handle = claimSession(sessionId, sessions);
|
|
306
|
+
if (!handle) return false;
|
|
274
307
|
await shutdownWaSessions(/* @__PURE__ */ new Map([[sessionId, handle]]));
|
|
308
|
+
return true;
|
|
275
309
|
}
|
|
276
310
|
async function acquireSessionLease(sessionId, deps) {
|
|
277
311
|
const { leaseStore, instanceId, sessions, isStopped, connect } = deps;
|
|
@@ -307,7 +341,9 @@ async function acquireSessionLease(sessionId, deps) {
|
|
|
307
341
|
void leaseStore.tryAcquire(sessionId, instanceId, now(), staleMs).then((stillHeld) => {
|
|
308
342
|
if (stillHeld || isStopped()) return;
|
|
309
343
|
logger.error(`WhatsApp[${sessionId}]: lost the lease to another instance \u2014 disconnecting`);
|
|
310
|
-
void loseSession(sessionId, sessions).then(
|
|
344
|
+
void loseSession(sessionId, sessions).then((lost) => {
|
|
345
|
+
if (lost) retry();
|
|
346
|
+
});
|
|
311
347
|
}).catch((error) => logger.warn(`WhatsApp[${sessionId}]: lease heartbeat failed:`, error));
|
|
312
348
|
}, heartbeatMs);
|
|
313
349
|
if (typeof heartbeat === "object" && "unref" in heartbeat) heartbeat.unref();
|
|
@@ -319,6 +355,10 @@ async function acquireSessionLease(sessionId, deps) {
|
|
|
319
355
|
try {
|
|
320
356
|
await connect(sessionId);
|
|
321
357
|
} catch (error) {
|
|
358
|
+
if (deps.onConnectFailed) {
|
|
359
|
+
deps.onConnectFailed(sessionId, error);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
322
362
|
logger.error(`WhatsApp[${sessionId}]: connect failed right after acquiring the lease:`, error);
|
|
323
363
|
await loseSession(sessionId, sessions);
|
|
324
364
|
retry();
|
|
@@ -327,9 +367,18 @@ async function acquireSessionLease(sessionId, deps) {
|
|
|
327
367
|
function senderNameFor(channelName, sessionId) {
|
|
328
368
|
return sessionId === "default" ? channelName : `${channelName}:${sessionId}`;
|
|
329
369
|
}
|
|
370
|
+
function normalizeWaMediaPayload(payload) {
|
|
371
|
+
const media = typeof payload === "string" ? { url: payload } : payload;
|
|
372
|
+
if (!media || typeof media !== "object" || typeof media.url !== "string" || !media.url) {
|
|
373
|
+
throw new TypeError(
|
|
374
|
+
`WhatsApp sendMedia payload must be a URL string or { url, caption? }, got: ${typeof payload}`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
return media;
|
|
378
|
+
}
|
|
330
379
|
function createWhatsAppChannel(config) {
|
|
331
380
|
assertStrongSecret(config.sessionSecret, "WhatsApp sessionSecret (e.g. WA_SESSION_SECRET)");
|
|
332
|
-
const channelName = "whatsapp";
|
|
381
|
+
const channelName = config.name ?? "whatsapp";
|
|
333
382
|
const sessionIds = config.sessionIds && config.sessionIds.length > 0 ? config.sessionIds : ["default"];
|
|
334
383
|
const now = config.now ?? Date.now;
|
|
335
384
|
const schedule = config.schedule ?? ((fn, ms) => void setTimeout(fn, ms));
|
|
@@ -347,10 +396,59 @@ function createWhatsAppChannel(config) {
|
|
|
347
396
|
name: channelName,
|
|
348
397
|
async start(deps) {
|
|
349
398
|
const log = config.logger ?? deps.logger ?? createConsoleLogger();
|
|
350
|
-
|
|
351
|
-
|
|
399
|
+
if ((!config.leaseStore || !config.authStore) && !deps.db) {
|
|
400
|
+
throw new Error(
|
|
401
|
+
"The WhatsApp channel needs a libsql client for auth state and the session lease: set config.database (config.retriever alone does not open one), or supply both config.leaseStore and config.authStore."
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
const leaseStore = config.leaseStore ?? createTursoWaLeaseStore(deps.db);
|
|
405
|
+
const authStore = config.authStore ?? createTursoWaAuthKV(deps.db);
|
|
352
406
|
senders = deps.senders;
|
|
353
407
|
await loadBaileys2();
|
|
408
|
+
const leaseDeps = {
|
|
409
|
+
leaseStore,
|
|
410
|
+
instanceId,
|
|
411
|
+
sessions,
|
|
412
|
+
isStopped: () => stopped,
|
|
413
|
+
connect,
|
|
414
|
+
now,
|
|
415
|
+
staleMs,
|
|
416
|
+
heartbeatMs,
|
|
417
|
+
waitMs,
|
|
418
|
+
schedule,
|
|
419
|
+
logger: log,
|
|
420
|
+
onConnectFailed: recoverFromFailedConnect
|
|
421
|
+
};
|
|
422
|
+
function nextRetryDelayMs(sessionId) {
|
|
423
|
+
const failures = failureCounts.get(sessionId) ?? 0;
|
|
424
|
+
failureCounts.set(sessionId, failures + 1);
|
|
425
|
+
return reconnectDelayMs(failures);
|
|
426
|
+
}
|
|
427
|
+
function attemptConnect(sessionId) {
|
|
428
|
+
void connect(sessionId).catch((error) => recoverFromFailedConnect(sessionId, error));
|
|
429
|
+
}
|
|
430
|
+
function recoverFromFailedConnect(sessionId, error) {
|
|
431
|
+
if (stopped) return;
|
|
432
|
+
const handle = claimSession(sessionId, sessions);
|
|
433
|
+
if (!handle) return;
|
|
434
|
+
const delay = nextRetryDelayMs(sessionId);
|
|
435
|
+
log.error(
|
|
436
|
+
`WhatsApp[${sessionId}]: connect attempt failed, releasing the lease and retrying in ${Math.round(delay / 1e3)}s:`,
|
|
437
|
+
error
|
|
438
|
+
);
|
|
439
|
+
schedule(
|
|
440
|
+
() => void acquireSessionLease(sessionId, leaseDeps).catch(
|
|
441
|
+
(retryError) => log.error(`WhatsApp[${sessionId}]: lease re-check failed:`, retryError)
|
|
442
|
+
),
|
|
443
|
+
delay
|
|
444
|
+
);
|
|
445
|
+
void shutdownWaSessions(/* @__PURE__ */ new Map([[sessionId, handle]])).catch(
|
|
446
|
+
(teardownError) => log.warn(
|
|
447
|
+
`WhatsApp[${sessionId}]: teardown after a failed connect failed:`,
|
|
448
|
+
teardownError
|
|
449
|
+
)
|
|
450
|
+
);
|
|
451
|
+
}
|
|
354
452
|
async function connect(sessionId) {
|
|
355
453
|
if (stopped || !sessions.has(sessionId)) return;
|
|
356
454
|
const baileys = await loadBaileys2();
|
|
@@ -359,8 +457,8 @@ function createWhatsAppChannel(config) {
|
|
|
359
457
|
initAuthCreds: baileys.initAuthCreds,
|
|
360
458
|
appStateSyncKeyFromObject: (value) => baileys.proto.Message.AppStateSyncKeyData.fromObject(value)
|
|
361
459
|
};
|
|
362
|
-
const { state, saveCreds } = await
|
|
363
|
-
|
|
460
|
+
const { state, saveCreds } = await useAuthState(
|
|
461
|
+
authStore,
|
|
364
462
|
config.sessionSecret,
|
|
365
463
|
sessionId,
|
|
366
464
|
runtime
|
|
@@ -369,7 +467,7 @@ function createWhatsAppChannel(config) {
|
|
|
369
467
|
log.warn(
|
|
370
468
|
`WhatsApp[${sessionId}]: no paired session found \u2014 pair it first (see docs/channels.md). Re-checking in 60s.`
|
|
371
469
|
);
|
|
372
|
-
schedule(() =>
|
|
470
|
+
schedule(() => attemptConnect(sessionId), 6e4);
|
|
373
471
|
return;
|
|
374
472
|
}
|
|
375
473
|
const { version } = await baileys.fetchLatestBaileysVersion();
|
|
@@ -404,6 +502,13 @@ function createWhatsAppChannel(config) {
|
|
|
404
502
|
sendText: async (chatId, text) => {
|
|
405
503
|
await sock.sendMessage(chatId, { text });
|
|
406
504
|
},
|
|
505
|
+
sendMedia: async (chatId, payload) => {
|
|
506
|
+
const { url, caption } = normalizeWaMediaPayload(payload);
|
|
507
|
+
await sock.sendMessage(
|
|
508
|
+
chatId,
|
|
509
|
+
caption !== void 0 ? { image: { url }, caption } : { image: { url } }
|
|
510
|
+
);
|
|
511
|
+
},
|
|
407
512
|
sendReaction: async (chatId, messageRef, emoji) => {
|
|
408
513
|
await sock.sendMessage(chatId, {
|
|
409
514
|
react: { text: emoji, key: messageRef }
|
|
@@ -428,17 +533,11 @@ function createWhatsAppChannel(config) {
|
|
|
428
533
|
return;
|
|
429
534
|
}
|
|
430
535
|
if (!stopped && sessions.has(sessionId)) {
|
|
431
|
-
const delay =
|
|
432
|
-
failureCounts.set(sessionId, (failureCounts.get(sessionId) ?? 0) + 1);
|
|
536
|
+
const delay = nextRetryDelayMs(sessionId);
|
|
433
537
|
log.warn(
|
|
434
538
|
`WhatsApp[${sessionId}]: connection closed (${statusCode ?? "?"}), reconnecting in ${Math.round(delay / 1e3)}s...`
|
|
435
539
|
);
|
|
436
|
-
schedule(
|
|
437
|
-
() => void connect(sessionId).catch(
|
|
438
|
-
(error) => log.error(`WhatsApp[${sessionId}]: reconnect failed:`, error)
|
|
439
|
-
),
|
|
440
|
-
delay
|
|
441
|
-
);
|
|
540
|
+
schedule(() => attemptConnect(sessionId), delay);
|
|
442
541
|
}
|
|
443
542
|
}
|
|
444
543
|
});
|
|
@@ -456,19 +555,9 @@ function createWhatsAppChannel(config) {
|
|
|
456
555
|
}
|
|
457
556
|
}
|
|
458
557
|
for (const sessionId of sessionIds) {
|
|
459
|
-
void acquireSessionLease(sessionId,
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
sessions,
|
|
463
|
-
isStopped: () => stopped,
|
|
464
|
-
connect,
|
|
465
|
-
now,
|
|
466
|
-
staleMs,
|
|
467
|
-
heartbeatMs,
|
|
468
|
-
waitMs,
|
|
469
|
-
schedule,
|
|
470
|
-
logger: log
|
|
471
|
-
}).catch((error) => log.error(`WhatsApp[${sessionId}]: failed to start:`, error));
|
|
558
|
+
void acquireSessionLease(sessionId, leaseDeps).catch(
|
|
559
|
+
(error) => log.error(`WhatsApp[${sessionId}]: failed to start:`, error)
|
|
560
|
+
);
|
|
472
561
|
}
|
|
473
562
|
},
|
|
474
563
|
async stop() {
|
|
@@ -679,7 +768,8 @@ async function answerOnce({
|
|
|
679
768
|
async function applyTransformReply(transformReply, input, logger) {
|
|
680
769
|
if (!transformReply) return input.text;
|
|
681
770
|
try {
|
|
682
|
-
|
|
771
|
+
const result = await transformReply(input);
|
|
772
|
+
return typeof result === "string" ? result : null;
|
|
683
773
|
} catch (error) {
|
|
684
774
|
logger?.error(
|
|
685
775
|
`transformReply threw for channel "${input.channel}"; sending the original reply`,
|
|
@@ -741,6 +831,7 @@ async function prepareChat({
|
|
|
741
831
|
sender,
|
|
742
832
|
rewriteQuery,
|
|
743
833
|
rerankContext,
|
|
834
|
+
fallbackFn,
|
|
744
835
|
logger
|
|
745
836
|
}) {
|
|
746
837
|
const lastUserMsg = lastUserMessage(messages);
|
|
@@ -757,21 +848,39 @@ async function prepareChat({
|
|
|
757
848
|
logger?.error("rewriteQuery threw; falling back to the original query", error);
|
|
758
849
|
}
|
|
759
850
|
}
|
|
760
|
-
|
|
851
|
+
const resolvedBuckets = buckets ?? defaultBuckets(mode);
|
|
852
|
+
const retrievedChunks = await store.query(query, topK, resolvedBuckets);
|
|
853
|
+
let ctx = retrievedChunks;
|
|
761
854
|
if (rerankContext) {
|
|
762
855
|
try {
|
|
763
|
-
const reranked = await rerankContext({ query, chunks: ctx });
|
|
856
|
+
const reranked = await rerankContext({ query, chunks: [...ctx] });
|
|
764
857
|
if (Array.isArray(reranked) && reranked.every((c) => typeof c === "string")) ctx = reranked;
|
|
765
858
|
} catch (error) {
|
|
766
859
|
logger?.error("rerankContext threw; falling back to the original chunks", error);
|
|
767
860
|
}
|
|
768
861
|
}
|
|
862
|
+
let fallback;
|
|
863
|
+
if (ctx.length === 0 && fallbackFn) {
|
|
864
|
+
try {
|
|
865
|
+
const guidance = await fallbackFn({
|
|
866
|
+
query,
|
|
867
|
+
mode,
|
|
868
|
+
buckets: resolvedBuckets,
|
|
869
|
+
sender,
|
|
870
|
+
retrievedChunks
|
|
871
|
+
});
|
|
872
|
+
if (typeof guidance === "string" && guidance.trim()) fallback = guidance.trim();
|
|
873
|
+
} catch (error) {
|
|
874
|
+
logger?.error("fallbackFn threw; proceeding without fallback guidance", error);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
769
877
|
const persona = personaLayer?.trim() || (mode === "private" ? prompts.privatePersona : prompts.publicPersona);
|
|
770
878
|
const hint = channelHint?.trim();
|
|
771
879
|
const system = [
|
|
772
880
|
prompts.baseSystemRules,
|
|
773
881
|
persona,
|
|
774
882
|
...hint ? [hint] : [],
|
|
883
|
+
...fallback ? [fallback] : [],
|
|
775
884
|
`${label}:
|
|
776
885
|
${ctx.join("\n\n")}`
|
|
777
886
|
].join("\n\n");
|
|
@@ -779,7 +888,7 @@ ${ctx.join("\n\n")}`
|
|
|
779
888
|
}
|
|
780
889
|
|
|
781
890
|
// src/history/compaction.ts
|
|
782
|
-
var
|
|
891
|
+
var COMPACTION_TIMEOUT_MS = 8e3;
|
|
783
892
|
var LOAD_ALL = Number.MAX_SAFE_INTEGER;
|
|
784
893
|
var SUMMARY_PREFIX = "Summary of earlier conversation: ";
|
|
785
894
|
function defaultSummarize(client, model) {
|
|
@@ -807,7 +916,7 @@ function createHistoryCompactor(deps, options) {
|
|
|
807
916
|
throw new Error(`Invalid history compaction keep: ${keep}`);
|
|
808
917
|
}
|
|
809
918
|
const summarize = options.summarize ?? defaultSummarize(deps.client, options.model);
|
|
810
|
-
const timeoutMs = options.timeoutMs ??
|
|
919
|
+
const timeoutMs = options.timeoutMs ?? COMPACTION_TIMEOUT_MS;
|
|
811
920
|
return {
|
|
812
921
|
async maybeCompact(store, conversationId) {
|
|
813
922
|
const turns = await store.load(conversationId, LOAD_ALL);
|
|
@@ -848,6 +957,16 @@ function createHistoryCompactor(deps, options) {
|
|
|
848
957
|
}
|
|
849
958
|
|
|
850
959
|
// src/channels/pipeline.ts
|
|
960
|
+
function resolveBrainHooks(config, fallback) {
|
|
961
|
+
return {
|
|
962
|
+
answerFn: config.answerFn ?? fallback?.answerFn,
|
|
963
|
+
bucketsFor: config.bucketsFor ?? fallback?.bucketsFor,
|
|
964
|
+
rewriteQuery: config.rewriteQuery ?? fallback?.rewriteQuery,
|
|
965
|
+
rerankContext: config.rerankContext ?? fallback?.rerankContext,
|
|
966
|
+
fallbackFn: config.fallbackFn ?? fallback?.fallbackFn,
|
|
967
|
+
transformReply: config.transformReply ?? fallback?.transformReply
|
|
968
|
+
};
|
|
969
|
+
}
|
|
851
970
|
var DEFAULT_HISTORY_LIMIT = 20;
|
|
852
971
|
var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
|
|
853
972
|
var DEFAULT_DM_RATE_LIMIT = { max: 20, windowMs: 60 * 60 * 1e3 };
|
|
@@ -932,6 +1051,7 @@ function createInboundPipeline(deps, config) {
|
|
|
932
1051
|
sender,
|
|
933
1052
|
rewriteQuery: config.rewriteQuery,
|
|
934
1053
|
rerankContext: config.rerankContext,
|
|
1054
|
+
fallbackFn: config.fallbackFn,
|
|
935
1055
|
logger: config.logger
|
|
936
1056
|
});
|
|
937
1057
|
const { content: produced } = await answerOnce({
|
|
@@ -1067,13 +1187,9 @@ function createWhatsAppInboundHandler(config) {
|
|
|
1067
1187
|
{ client: config.client, store: config.store, prompts: config.prompts },
|
|
1068
1188
|
{
|
|
1069
1189
|
channel: "whatsapp",
|
|
1070
|
-
|
|
1071
|
-
bucketsFor: config.bucketsFor,
|
|
1072
|
-
rewriteQuery: config.rewriteQuery,
|
|
1073
|
-
rerankContext: config.rerankContext,
|
|
1074
|
-
transformReply: config.transformReply,
|
|
1190
|
+
...resolveBrainHooks(config, config.serverConfig),
|
|
1075
1191
|
model: config.model,
|
|
1076
|
-
channelHint: config.channelHint,
|
|
1192
|
+
channelHint: config.channelHint ?? "Channel: WhatsApp.",
|
|
1077
1193
|
personaResolver: personaResolver ? ({ sender, text }) => personaResolver({ senderPhone: sender, text }) : void 0,
|
|
1078
1194
|
history: config.history,
|
|
1079
1195
|
allowedChats,
|
|
@@ -1233,6 +1349,7 @@ export {
|
|
|
1233
1349
|
LEASE_WAIT_MS,
|
|
1234
1350
|
acquireSessionLease,
|
|
1235
1351
|
canAcquireLease,
|
|
1352
|
+
createTursoWaAuthKV,
|
|
1236
1353
|
createTursoWaLeaseStore,
|
|
1237
1354
|
createWhatsAppChannel,
|
|
1238
1355
|
createWhatsAppImageHandler,
|
|
@@ -1248,11 +1365,13 @@ export {
|
|
|
1248
1365
|
jidsMatch,
|
|
1249
1366
|
loadBaileys,
|
|
1250
1367
|
messageContext,
|
|
1368
|
+
normalizeWaMediaPayload,
|
|
1251
1369
|
reconnectDelayMs,
|
|
1252
1370
|
resolveWaMessage,
|
|
1253
1371
|
senderNameFor,
|
|
1254
1372
|
senderPhoneFor,
|
|
1255
1373
|
shutdownWaSessions,
|
|
1256
1374
|
stripOwnMentions,
|
|
1375
|
+
useAuthState,
|
|
1257
1376
|
useTursoAuthState
|
|
1258
1377
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/pairing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;
|
|
1
|
+
{"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/pairing.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../../src/client/src/Chat.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEvD,qBAAa,IAAI;IACf,OAAO,CAAC,GAAG,CAAU;IACrB,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAC,CAAa;IAE7B,OAAO,CAAC,iBAAiB,CAAe;IACxC,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,UAAU,CAAqB;IAEvC,YAAY,MAAM,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../../src/client/src/Chat.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAEvD,qBAAa,IAAI;IACf,OAAO,CAAC,GAAG,CAAU;IACrB,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAC,CAAa;IAE7B,OAAO,CAAC,iBAAiB,CAAe;IACxC,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,UAAU,CAAqB;IAEvC,YAAY,MAAM,EAAE,UAAU,EA0B7B;IAED,OAAO,CAAC,MAAM;IAoBd,OAAO,CAAC,oBAAoB;YA8Cd,UAAU;IA4DxB,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,cAAc;IAItB;;OAEG;IACH,KAAK,IAAI,IAAI,CAGZ;IAED;;OAEG;IACH,WAAW,IAAI,WAAW,EAAE,CAE3B;IAED;;OAEG;IACH,OAAO,IAAI,IAAI,CAEd;IAED;;OAEG;IACH,OAAO,CAAC,QAAQ;CAGjB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatButton.d.ts","sourceRoot":"","sources":["../../../src/client/src/ChatButton.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,MAAM,CAAS;IAEvB,YAAY,MAAM,EAAE,gBAAgB,EASnC;IAED,OAAO,CAAC,MAAM;
|
|
1
|
+
{"version":3,"file":"ChatButton.d.ts","sourceRoot":"","sources":["../../../src/client/src/ChatButton.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,qBAAa,UAAU;IACrB,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,aAAa,CAAe;IACpC,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,MAAM,CAAS;IAEvB,YAAY,MAAM,EAAE,gBAAgB,EASnC;IAED,OAAO,CAAC,MAAM;IAkBd,OAAO,CAAC,oBAAoB;IAoB5B,OAAO,CAAC,MAAM;IAQd,IAAI,IAAI,IAAI,CA+CX;IAED,KAAK,IAAI,IAAI,CAcZ;IAED;;OAEG;IACH,OAAO,CAAC,QAAQ;IAIhB;;OAEG;IACH,OAAO,IAAI,IAAI,CAId;IAED;;OAEG;IACH,QAAQ,IAAI,OAAO,CAElB;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../../src/client/src/browser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../../src/client/src/browser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,OAAO,EAAE;YACP,OAAO,EAAE,OAAO,OAAO,CAAC;YACxB,IAAI,EAAE,OAAO,IAAI,CAAC;YAClB,UAAU,EAAE,OAAO,UAAU,CAAC;SAC/B,CAAC;KACH;CACF;AAYD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
package/dist/core/answer.d.ts
CHANGED
|
@@ -95,6 +95,14 @@ export type TransformReply = (input: TransformReplyInput) => string | null | Pro
|
|
|
95
95
|
* Runs `transformReply` if configured. A throw/rejection is logged and the
|
|
96
96
|
* ORIGINAL text is returned — the delivery invariant: a bug in a plugin hook
|
|
97
97
|
* must never silently swallow an answer the model already produced.
|
|
98
|
+
*
|
|
99
|
+
* The result crosses the same plugin boundary `runAnswerFn` does, so it gets
|
|
100
|
+
* the same treatment: anything that isn't a string is coerced to `null`
|
|
101
|
+
* (treated as a veto) rather than passed through as whatever a
|
|
102
|
+
* non-conforming hook happened to return (`undefined` from a missing
|
|
103
|
+
* `return`, say). Every call site already treats a falsy result as "nothing
|
|
104
|
+
* delivered", but only a normalized `string | null` matches this function's
|
|
105
|
+
* own declared return type.
|
|
98
106
|
*/
|
|
99
107
|
export declare function applyTransformReply(transformReply: TransformReply | undefined, input: TransformReplyInput, logger?: Pick<Logger, "error">): Promise<string | null>;
|
|
100
108
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../src/core/answer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAGjC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEhE,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAKD,qEAAqE;AACrE,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,uCAAuC;IACvC,IAAI,EAAE,YAAY,CAAC;IACnB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,WAAW,CAAA;CAAE,CAAC;AAE/E;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;AAE1F,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqBD;;GAEG;AACH,wBAAsB,UAAU,CAAC,EAC/B,QAAQ,EACR,MAAM,EACN,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,cAAc,EACd,WAAW,EACX,KAAK,GACN,EAAE,aAAa,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAAC,CAKlE;AAED,4FAA4F;AAC5F,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,mBAAmB,KAAK,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAEpG
|
|
1
|
+
{"version":3,"file":"answer.d.ts","sourceRoot":"","sources":["../../src/core/answer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AAGjC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEhE,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;CACtB;AAKD,qEAAqE;AACrE,MAAM,WAAW,aAAa;IAC5B,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,uCAAuC;IACvC,IAAI,EAAE,YAAY,CAAC;IACnB,mEAAmE;IACnE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,WAAW,CAAA;CAAE,CAAC;AAE/E;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAAC;AAE1F,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqBD;;GAEG;AACH,wBAAsB,UAAU,CAAC,EAC/B,QAAQ,EACR,MAAM,EACN,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,cAAc,EACd,WAAW,EACX,KAAK,GACN,EAAE,aAAa,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,CAAA;CAAE,CAAC,CAKlE;AAED,4FAA4F;AAC5F,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;GAUG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,mBAAmB,KAAK,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAEpG;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CACvC,cAAc,EAAE,cAAc,GAAG,SAAS,EAC1C,KAAK,EAAE,mBAAmB,EAC1B,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAYxB;AAED;;;;;;;;GAQG;AACH,wBAAuB,YAAY,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,MAAM,EACN,QAAQ,EACR,IAAI,EACJ,MAAM,EACN,cAAc,EACd,WAAW,EACX,KAAK,GACN,EAAE,aAAa,GAAG,cAAc,CAAC,MAAM,CAAC,CAaxC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-writer lock around the destructive half of a knowledge-base build.
|
|
3
|
+
*
|
|
4
|
+
* `VectorStore.build()` is a read-diff-delete-insert sequence: it reads the
|
|
5
|
+
* chunk ids already in the database, computes the ids its own `knowledgeDir`
|
|
6
|
+
* produces, and deletes everything not in that set. Two instances booting
|
|
7
|
+
* against the same database at the same time (a rolling deploy, a manual
|
|
8
|
+
* deploy racing an automated one) each run that sequence against a view of
|
|
9
|
+
* the data the other is still mutating, so one boot's cleanup can delete
|
|
10
|
+
* chunks the other has just written or has not finished writing.
|
|
11
|
+
*
|
|
12
|
+
* The lock lives in the same database (`chatter_build_lock`) so it is visible
|
|
13
|
+
* to every instance, and acquiring or renewing it is a single atomic upsert
|
|
14
|
+
* (see `createTursoBuildLock`) - two instances racing the same key can never
|
|
15
|
+
* both believe they hold it. A holder that dies without releasing leaves a
|
|
16
|
+
* stale row instead of wedging every future boot: after `staleMs` with no
|
|
17
|
+
* heartbeat, the next instance may take it over.
|
|
18
|
+
*/
|
|
19
|
+
import type { Client } from "@libsql/client";
|
|
20
|
+
/** Lock key for the knowledge-base build. One build runs per database at a time. */
|
|
21
|
+
export declare const BUILD_LOCK_KEY = "knowledge_build";
|
|
22
|
+
/**
|
|
23
|
+
* A held lock this old with no heartbeat is treated as abandoned. Generous
|
|
24
|
+
* because a first build embeds every chunk it loads, which can take minutes;
|
|
25
|
+
* a holder renews the lock as it goes (see `VectorStore.build`), so only a
|
|
26
|
+
* process that actually died lets the lock go stale.
|
|
27
|
+
*/
|
|
28
|
+
export declare const BUILD_LOCK_STALE_MS: number;
|
|
29
|
+
export interface BuildLockRow {
|
|
30
|
+
holderId: string;
|
|
31
|
+
heartbeatAt: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Pure: may `holderId` take (or keep) the build lock right now, given the
|
|
35
|
+
* current row (null = never held)? Mirrors the SQL WHERE clause in
|
|
36
|
+
* `createTursoBuildLock` - keep the two in sync.
|
|
37
|
+
*/
|
|
38
|
+
export declare function canAcquireBuildLock(existing: BuildLockRow | null, holderId: string, now: number, staleMs?: number): boolean;
|
|
39
|
+
export interface BuildLock {
|
|
40
|
+
/** Atomically acquires or renews the lock; resolves true iff `holderId` now holds it. */
|
|
41
|
+
tryAcquire(key: string, holderId: string, now: number, staleMs: number): Promise<boolean>;
|
|
42
|
+
/** Releases the lock iff still held by `holderId` (no-op otherwise, so a late release never undoes a takeover). */
|
|
43
|
+
release(key: string, holderId: string): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A libsql-backed {@link BuildLock}. The WHERE clause on DO UPDATE encodes
|
|
47
|
+
* `canAcquireBuildLock`: SQLite skips the update (and RETURNING yields no
|
|
48
|
+
* row) when it is false, so a losing racer sees zero rows back - no separate
|
|
49
|
+
* read-then-write, no race window.
|
|
50
|
+
*/
|
|
51
|
+
export declare function createTursoBuildLock(client: Client): BuildLock;
|
|
52
|
+
//# sourceMappingURL=buildLock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildLock.d.ts","sourceRoot":"","sources":["../../src/core/buildLock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C,oFAAoF;AACpF,eAAO,MAAM,cAAc,oBAAoB,CAAC;AAEhD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,QAAc,CAAC;AAE/C,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,GAAG,IAAI,EAC7B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,MAA4B,GACpC,OAAO,CAIT;AAED,MAAM,WAAW,SAAS;IACxB,yFAAyF;IACzF,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1F,mHAAmH;IACnH,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD;AAwBD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAyB9D"}
|
package/dist/core/pipeline.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { Logger } from "./logger";
|
|
12
12
|
import type { PromptLoader } from "./prompts";
|
|
13
|
-
import type {
|
|
13
|
+
import type { Retriever } from "./retrieval";
|
|
14
14
|
export type PipelineMode = "public" | "private";
|
|
15
15
|
export interface PipelineMessage {
|
|
16
16
|
role: "user" | "assistant";
|
|
@@ -60,11 +60,43 @@ export interface RerankContextArgs {
|
|
|
60
60
|
* fails closed by design; use this hook for relevance/ordering only.
|
|
61
61
|
*/
|
|
62
62
|
export type RerankContext = (ctx: RerankContextArgs) => string[] | Promise<string[]>;
|
|
63
|
+
/** What `fallbackFn` sees for a turn where retrieval came back empty. */
|
|
64
|
+
export interface FallbackContext {
|
|
65
|
+
/** The query retrieval actually ran with, post-`rewriteQuery` if configured. */
|
|
66
|
+
query: string;
|
|
67
|
+
mode: PipelineMode;
|
|
68
|
+
/** Buckets retrieval ran against for this turn. */
|
|
69
|
+
buckets: string[];
|
|
70
|
+
/** Caller identity, where the surface has one. Absent on anonymous surfaces. */
|
|
71
|
+
sender?: string;
|
|
72
|
+
/**
|
|
73
|
+
* What `store.query` returned, before `rerankContext` ran: always
|
|
74
|
+
* non-empty when this is empty because `rerankContext` filtered
|
|
75
|
+
* everything out, and always empty when the store itself found nothing.
|
|
76
|
+
* A hook can use this to tell "some low-confidence signal existed" apart
|
|
77
|
+
* from "nothing matched at all".
|
|
78
|
+
*/
|
|
79
|
+
retrievedChunks: string[];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Runs only when retrieval produced no chunks for a turn (after
|
|
83
|
+
* `rerankContext`, if configured), the deterministic, testable alternative
|
|
84
|
+
* to baking "what if there's nothing to retrieve" judgment calls into
|
|
85
|
+
* freeform system-prompt text. Return a string to inject as an extra system
|
|
86
|
+
* prompt layer scoped to this turn only (e.g. "no matching context was
|
|
87
|
+
* found; offer a clearly-labelled guess or decline"); return `undefined` to
|
|
88
|
+
* leave the prompt exactly as it would be otherwise.
|
|
89
|
+
*
|
|
90
|
+
* A throw/rejection is caught by `prepareChat`, which proceeds without
|
|
91
|
+
* fallback guidance: a broken hook must never break the chat path.
|
|
92
|
+
*/
|
|
93
|
+
export type FallbackFn = (ctx: FallbackContext) => string | undefined | Promise<string | undefined>;
|
|
63
94
|
/**
|
|
64
95
|
* Run retrieval for the latest user message and assemble the system prompt.
|
|
65
96
|
*
|
|
66
97
|
* The assembled system prompt is layered, in order:
|
|
67
|
-
* base rules → persona → channel hint (optional) →
|
|
98
|
+
* base rules → persona → channel hint (optional) → fallback guidance
|
|
99
|
+
* (optional, only when retrieval came back empty) → retrieved context.
|
|
68
100
|
*
|
|
69
101
|
* `personaLayer` and `channelHint` let a caller shape those middle layers
|
|
70
102
|
* without hand-rolling its own sandwich around `store`/`prompts`. Both are
|
|
@@ -83,10 +115,13 @@ export type RerankContext = (ctx: RerankContextArgs) => string[] | Promise<strin
|
|
|
83
115
|
* value falls back to what retrieval would have done unmodified, so a buggy
|
|
84
116
|
* hook degrades relevance rather than breaking the chat path.
|
|
85
117
|
*
|
|
118
|
+
* `fallbackFn` runs after both, only when the chunks folded into the prompt
|
|
119
|
+
* would otherwise be empty, see {@link FallbackFn}.
|
|
120
|
+
*
|
|
86
121
|
* @throws if the conversation contains no user message
|
|
87
122
|
*/
|
|
88
|
-
export declare function prepareChat({ store, prompts, mode, messages, personaLayer, channelHint, buckets, sender, rewriteQuery, rerankContext, logger, }: {
|
|
89
|
-
store:
|
|
123
|
+
export declare function prepareChat({ store, prompts, mode, messages, personaLayer, channelHint, buckets, sender, rewriteQuery, rerankContext, fallbackFn, logger, }: {
|
|
124
|
+
store: Retriever;
|
|
90
125
|
prompts: PromptLoader;
|
|
91
126
|
mode: PipelineMode;
|
|
92
127
|
messages: PipelineMessage[];
|
|
@@ -105,7 +140,9 @@ export declare function prepareChat({ store, prompts, mode, messages, personaLay
|
|
|
105
140
|
rewriteQuery?: RewriteQuery;
|
|
106
141
|
/** Post-processes retrieved chunks before they're folded into the prompt — see {@link RerankContext}. */
|
|
107
142
|
rerankContext?: RerankContext;
|
|
108
|
-
/**
|
|
143
|
+
/** Injects fallback guidance when retrieval comes back empty, see {@link FallbackFn}. */
|
|
144
|
+
fallbackFn?: FallbackFn;
|
|
145
|
+
/** Logs a `rewriteQuery`/`rerankContext`/`fallbackFn` throw before falling back. Unset: the failure is silent. */
|
|
109
146
|
logger?: Pick<Logger, "error">;
|
|
110
147
|
}): Promise<PreparedChat>;
|
|
111
148
|
//# sourceMappingURL=pipeline.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/core/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/core/pipeline.ts"],"names":[],"mappings":"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"}
|