@diegoaltoworks/chatter 0.33.0 → 0.35.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/dist/channels/whatsapp/images.d.ts +85 -0
- package/dist/channels/whatsapp/images.d.ts.map +1 -0
- package/dist/channels/whatsapp/images.test.d.ts +2 -0
- package/dist/channels/whatsapp/images.test.d.ts.map +1 -0
- package/dist/channels/whatsapp/inbound.d.ts +9 -0
- package/dist/channels/whatsapp/inbound.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.d.ts +1 -0
- package/dist/channels/whatsapp/index.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.js +82 -0
- package/dist/channels/whatsapp/index.mjs +79 -0
- package/dist/scheduler/claimStore.d.ts +27 -0
- package/dist/scheduler/claimStore.d.ts.map +1 -0
- package/dist/scheduler/claimStore.test.d.ts +2 -0
- package/dist/scheduler/claimStore.test.d.ts.map +1 -0
- package/dist/scheduler/compose.d.ts +23 -0
- package/dist/scheduler/compose.d.ts.map +1 -0
- package/dist/scheduler/compose.test.d.ts +2 -0
- package/dist/scheduler/compose.test.d.ts.map +1 -0
- package/dist/scheduler/deliver.d.ts +10 -0
- package/dist/scheduler/deliver.d.ts.map +1 -0
- package/dist/scheduler/deliver.test.d.ts +2 -0
- package/dist/scheduler/deliver.test.d.ts.map +1 -0
- package/dist/scheduler/dueDrops.d.ts +13 -0
- package/dist/scheduler/dueDrops.d.ts.map +1 -0
- package/dist/scheduler/dueDrops.test.d.ts +2 -0
- package/dist/scheduler/dueDrops.test.d.ts.map +1 -0
- package/dist/scheduler/index.d.ts +31 -0
- package/dist/scheduler/index.d.ts.map +1 -0
- package/dist/scheduler/index.js +332 -0
- package/dist/scheduler/index.mjs +290 -0
- package/dist/scheduler/scheduler.d.ts +44 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -0
- package/dist/scheduler/scheduler.test.d.ts +2 -0
- package/dist/scheduler/scheduler.test.d.ts.map +1 -0
- package/dist/scheduler/tick.d.ts +19 -0
- package/dist/scheduler/tick.d.ts.map +1 -0
- package/dist/scheduler/tick.test.d.ts +2 -0
- package/dist/scheduler/tick.test.d.ts.map +1 -0
- package/dist/scheduler/types.d.ts +36 -0
- package/dist/scheduler/types.d.ts.map +1 -0
- package/package.json +8 -2
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional image requests in the WhatsApp channel: detect a drawing/picture
|
|
3
|
+
* request (with or without an attached photo), route it through `../../images`,
|
|
4
|
+
* and reply with the generated picture. Inert unless `./images` is
|
|
5
|
+
* configured — `createWhatsAppImageHandler` returns `false` for every
|
|
6
|
+
* message when it isn't, so callers fall through to normal chat.
|
|
7
|
+
*
|
|
8
|
+
* This module ships no bot personality: the detector and subject-cleaning
|
|
9
|
+
* patterns are generic drawing verbs (no hardcoded bot names), and every
|
|
10
|
+
* reply string (ack/limit/moderation/error) is caller-configured with no
|
|
11
|
+
* default text.
|
|
12
|
+
*
|
|
13
|
+
* Downloading the attached photo needs Baileys' `downloadMediaMessage`,
|
|
14
|
+
* which — per this subpath's rule that only `./baileys` touches the
|
|
15
|
+
* optional peer dependency at runtime — is injected as `downloadPhoto`
|
|
16
|
+
* rather than imported here. The host wires it via `./whatsapp`'s
|
|
17
|
+
* `loadBaileys` (see docs/channels.md), not this module.
|
|
18
|
+
*
|
|
19
|
+
* Everything pulled in from `../../images` is type-only: each subpath is
|
|
20
|
+
* built as its own bundle, so a value import here (even just for an
|
|
21
|
+
* `instanceof` check) would inline the whole `./images` module — OpenAI SDK
|
|
22
|
+
* included — into `./whatsapp`, and importing from the `../../images`
|
|
23
|
+
* barrel rather than its leaf files pulls in every sibling module's value
|
|
24
|
+
* imports too. Error mapping below checks `code`/`name` structurally
|
|
25
|
+
* instead, which also means it works when the error crossed a bundle
|
|
26
|
+
* boundary (the two subpaths' `ImageGenerationError` classes are not
|
|
27
|
+
* `instanceof`-compatible with each other).
|
|
28
|
+
*/
|
|
29
|
+
import type { WAMessage, WASocket } from "@whiskeysockets/baileys";
|
|
30
|
+
import type { CaptionComposer } from "../../images/captionComposer";
|
|
31
|
+
import type { ImageUploader } from "../../images/uploader";
|
|
32
|
+
export interface DrawRequestDetectorConfig {
|
|
33
|
+
/** Tested against the raw message text to decide whether it's an image request. Defaults to a generic set of drawing verbs — no bot-specific names. */
|
|
34
|
+
detectPattern?: RegExp;
|
|
35
|
+
/** Applied in order to derive the subject from the raw text (mention tokens, greetings/politeness, the draw verb itself). */
|
|
36
|
+
stripPatterns?: readonly RegExp[];
|
|
37
|
+
}
|
|
38
|
+
export declare function isDrawRequest(text: string, config?: DrawRequestDetectorConfig): boolean;
|
|
39
|
+
/** Cleans a draw request into a scene subject: "@1234 please draw me a cat" -> "cat". Falls back to the trimmed original when the patterns strip everything (or nothing). */
|
|
40
|
+
export declare function drawSubject(text: string, config?: DrawRequestDetectorConfig): string;
|
|
41
|
+
/** Neutral, overridable reply strings — this module ships no bot personality. Unset = silent for that outcome. */
|
|
42
|
+
export interface WhatsAppImageStrings {
|
|
43
|
+
/** Sent immediately on accepting the request, before generation starts. */
|
|
44
|
+
ack?: string;
|
|
45
|
+
/** Sent when the per-sender/day cap (`checkAndReserve`) blocks the request. */
|
|
46
|
+
limitReached?: string;
|
|
47
|
+
/** Sent when the image API blocks the request on moderation grounds. */
|
|
48
|
+
moderationBlocked?: string;
|
|
49
|
+
/** Sent for any other generation/upload/download failure. */
|
|
50
|
+
error?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface WhatsAppImageHandlerDeps {
|
|
53
|
+
/** The slice of `./images`' `ImageUploader` this module needs. */
|
|
54
|
+
images: Pick<ImageUploader, "isConfigured" | "peekCached" | "getOrCreateImage">;
|
|
55
|
+
/**
|
|
56
|
+
* Per-sender/day reservation (e.g. `./usage`'s `DailyLimiter.checkAndReserve`).
|
|
57
|
+
* Omit to skip the cap entirely. Checked AFTER the cache, so a cache hit
|
|
58
|
+
* never burns a unit.
|
|
59
|
+
*/
|
|
60
|
+
checkAndReserve?: (senderId: string) => Promise<{
|
|
61
|
+
allowed: boolean;
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* Downloads the attached photo as bytes. Omit to ignore attached photos —
|
|
65
|
+
* text-only requests still work. See the module docstring for why this is
|
|
66
|
+
* injected rather than imported.
|
|
67
|
+
*/
|
|
68
|
+
downloadPhoto?: (message: WAMessage) => Promise<Uint8Array>;
|
|
69
|
+
/** Best-effort tailored caption for the reply. Omit to send the image with no caption. */
|
|
70
|
+
captionComposer?: Pick<CaptionComposer, "composeCaption">;
|
|
71
|
+
detector?: DrawRequestDetectorConfig;
|
|
72
|
+
strings?: WhatsAppImageStrings;
|
|
73
|
+
}
|
|
74
|
+
export interface WhatsAppImageRequestContext {
|
|
75
|
+
sock: WASocket;
|
|
76
|
+
message: WAMessage;
|
|
77
|
+
chatId: string;
|
|
78
|
+
senderId: string;
|
|
79
|
+
text: string;
|
|
80
|
+
hasPhoto: boolean;
|
|
81
|
+
}
|
|
82
|
+
/** `true` = the message was an image request and this module replied to it (successfully or with a mapped error) — the caller must not also route it through normal chat. `false` = not an image request, or `./images` isn't configured. */
|
|
83
|
+
export type WhatsAppImageHandler = (ctx: WhatsAppImageRequestContext) => Promise<boolean>;
|
|
84
|
+
export declare function createWhatsAppImageHandler(deps: WhatsAppImageHandlerDeps): WhatsAppImageHandler;
|
|
85
|
+
//# sourceMappingURL=images.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/images.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAEpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAkB3D,MAAM,WAAW,yBAAyB;IACxC,uJAAuJ;IACvJ,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6HAA6H;IAC7H,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAOD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,yBAA8B,GAAG,OAAO,CAE3F;AAED,6KAA6K;AAC7K,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,yBAA8B,GAAG,MAAM,CAUxF;AAED,kHAAkH;AAClH,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,wBAAwB;IACvC,kEAAkE;IAClE,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,cAAc,GAAG,YAAY,GAAG,kBAAkB,CAAC,CAAC;IAChF;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACtE;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5D,0FAA0F;IAC1F,eAAe,CAAC,EAAE,IAAI,CAAC,eAAe,EAAE,gBAAgB,CAAC,CAAC;IAC1D,QAAQ,CAAC,EAAE,yBAAyB,CAAC;IACrC,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,SAAS,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,6OAA6O;AAC7O,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,EAAE,2BAA2B,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAsB1F,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,wBAAwB,GAAG,oBAAoB,CA4C/F"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"images.test.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/images.test.ts"],"names":[],"mappings":""}
|
|
@@ -50,6 +50,7 @@ import type { PromptLoader } from "../../core/prompts";
|
|
|
50
50
|
import type { VectorStore } from "../../core/retrieval";
|
|
51
51
|
import { type SessionIdentityRegistry } from "../gates";
|
|
52
52
|
import type { WhatsAppMessageEvent } from "./channel";
|
|
53
|
+
import type { WhatsAppImageHandler } from "./images";
|
|
53
54
|
export declare function isGroupJid(jid: string): boolean;
|
|
54
55
|
/** "447700900123:17@s.whatsapp.net" (device-suffixed) and "447700900123@s.whatsapp.net" match. */
|
|
55
56
|
export declare function jidsMatch(a: string | undefined, b: string | undefined): boolean;
|
|
@@ -124,6 +125,14 @@ export interface WhatsAppInboundConfig {
|
|
|
124
125
|
senderPhone: string;
|
|
125
126
|
text: string;
|
|
126
127
|
}) => string | undefined | Promise<string | undefined>;
|
|
128
|
+
/**
|
|
129
|
+
* Optional image-request routing (see `./images`). Consulted right before
|
|
130
|
+
* the normal chat pipeline; a `true` result means the message was handled
|
|
131
|
+
* as an image request (successfully or with a mapped error string) and
|
|
132
|
+
* this handler returns without answering through chat. Inert unless
|
|
133
|
+
* `./images` is configured.
|
|
134
|
+
*/
|
|
135
|
+
images?: WhatsAppImageHandler;
|
|
127
136
|
now?: () => number;
|
|
128
137
|
}
|
|
129
138
|
/** Builds the `onMessage` handler for `createWhatsAppChannel` (see `./channel`) — see the module docstring for how to wire `ServerDependencies` into it. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/inbound.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,KAAK,QAAQ,EAAc,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAE,KAAK,UAAU,EAAkB,MAAM,oBAAoB,CAAC;AAErE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAKL,KAAK,uBAAuB,EAE7B,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/inbound.ts"],"names":[],"mappings":"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"}
|
|
@@ -29,6 +29,7 @@ export type { Baileys } from "./baileys";
|
|
|
29
29
|
export { loadBaileys } from "./baileys";
|
|
30
30
|
export { acquireSessionLease, createWhatsAppChannel, type LeaseGatedConnectDeps, reconnectDelayMs, senderNameFor, shutdownWaSessions, type WaSessionHandle, type WhatsAppChannelConfig, type WhatsAppMessageEvent, } from "./channel";
|
|
31
31
|
export { decrypt, encrypt } from "./crypto";
|
|
32
|
+
export { createWhatsAppImageHandler, type DrawRequestDetectorConfig, drawSubject, isDrawRequest, type WhatsAppImageHandler, type WhatsAppImageHandlerDeps, type WhatsAppImageRequestContext, type WhatsAppImageStrings, } from "./images";
|
|
32
33
|
export { createWhatsAppInboundHandler, extractText, isGroupJid, jidsMatch, jidToPhoneNumber, messageContext, senderPhoneFor, type WaMessageContext, type WhatsAppInboundConfig, } from "./inbound";
|
|
33
34
|
export { canAcquireLease, createTursoWaLeaseStore, LEASE_HEARTBEAT_MS, LEASE_STALE_MS, LEASE_WAIT_MS, type WaLeaseRow, type WaLeaseStore, } from "./lease";
|
|
34
35
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EACL,4BAA4B,EAC5B,WAAW,EACX,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/whatsapp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC1E,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EACL,0BAA0B,EAC1B,KAAK,yBAAyB,EAC9B,WAAW,EACX,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC7B,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,GAC1B,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,4BAA4B,EAC5B,WAAW,EACX,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC"}
|
|
@@ -37,10 +37,13 @@ __export(index_exports, {
|
|
|
37
37
|
canAcquireLease: () => canAcquireLease,
|
|
38
38
|
createTursoWaLeaseStore: () => createTursoWaLeaseStore,
|
|
39
39
|
createWhatsAppChannel: () => createWhatsAppChannel,
|
|
40
|
+
createWhatsAppImageHandler: () => createWhatsAppImageHandler,
|
|
40
41
|
createWhatsAppInboundHandler: () => createWhatsAppInboundHandler,
|
|
41
42
|
decrypt: () => decrypt,
|
|
43
|
+
drawSubject: () => drawSubject,
|
|
42
44
|
encrypt: () => encrypt,
|
|
43
45
|
extractText: () => extractText,
|
|
46
|
+
isDrawRequest: () => isDrawRequest,
|
|
44
47
|
isGroupJid: () => isGroupJid,
|
|
45
48
|
jidToPhoneNumber: () => jidToPhoneNumber,
|
|
46
49
|
jidsMatch: () => jidsMatch,
|
|
@@ -467,6 +470,71 @@ function createWhatsAppChannel(config) {
|
|
|
467
470
|
};
|
|
468
471
|
}
|
|
469
472
|
|
|
473
|
+
// src/channels/whatsapp/images.ts
|
|
474
|
+
var DEFAULT_DETECT_PATTERN = /\b(draw|paint|sketch|illustrate)\b|\b(make|generate|create)\b[^.!?]{0,20}\b(picture|image|drawing|photo|cartoon)\b/i;
|
|
475
|
+
var DEFAULT_STRIP_PATTERNS = [
|
|
476
|
+
/@\d+/g,
|
|
477
|
+
/^(hey|hi|please|ok)[,!\s]+/i,
|
|
478
|
+
/^(can|could|will)\s+you\s+/i,
|
|
479
|
+
// Longest-alternative-first ((an|a), not (a|an)): a first-match-wins
|
|
480
|
+
// alternation would otherwise take "a" out of "an" and leave a stray "n".
|
|
481
|
+
/^(please\s+)?(draw|paint|sketch|illustrate|make|generate|create)\s+(me|us)?\s*(an|a)?\s*(picture|image|drawing|sketch|photo|cartoon)?\s*(of\s+)?/i
|
|
482
|
+
];
|
|
483
|
+
function testPattern(pattern, text) {
|
|
484
|
+
return new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, "")).test(text);
|
|
485
|
+
}
|
|
486
|
+
function isDrawRequest(text, config = {}) {
|
|
487
|
+
return testPattern(config.detectPattern ?? DEFAULT_DETECT_PATTERN, text);
|
|
488
|
+
}
|
|
489
|
+
function drawSubject(text, config = {}) {
|
|
490
|
+
let subject = text;
|
|
491
|
+
for (const pattern of config.stripPatterns ?? DEFAULT_STRIP_PATTERNS) {
|
|
492
|
+
subject = subject.replace(pattern, " ").replace(/\s+/g, " ").trim();
|
|
493
|
+
}
|
|
494
|
+
return subject.length > 0 ? subject : text.trim();
|
|
495
|
+
}
|
|
496
|
+
function isModerationError(error) {
|
|
497
|
+
return typeof error === "object" && error !== null && error.name === "ImageGenerationError" && error.code === "moderation";
|
|
498
|
+
}
|
|
499
|
+
async function replyText(sock, chatId, message, text) {
|
|
500
|
+
if (!text) return;
|
|
501
|
+
await sock.sendMessage(chatId, { text }, { quoted: message }).catch(() => void 0);
|
|
502
|
+
}
|
|
503
|
+
function createWhatsAppImageHandler(deps) {
|
|
504
|
+
return async function handleWhatsAppImageRequest(ctx) {
|
|
505
|
+
if (!deps.images.isConfigured()) return false;
|
|
506
|
+
if (!isDrawRequest(ctx.text, deps.detector)) return false;
|
|
507
|
+
const subject = drawSubject(ctx.text, deps.detector);
|
|
508
|
+
try {
|
|
509
|
+
const photoBytes = ctx.hasPhoto ? await deps.downloadPhoto?.(ctx.message) : void 0;
|
|
510
|
+
const inputImages = photoBytes ? [{ bytes: photoBytes }] : void 0;
|
|
511
|
+
const request = { prompt: subject, inputImages };
|
|
512
|
+
if (deps.checkAndReserve) {
|
|
513
|
+
const cached2 = await deps.images.peekCached(request);
|
|
514
|
+
if (!cached2) {
|
|
515
|
+
const reserved = await deps.checkAndReserve(ctx.senderId);
|
|
516
|
+
if (!reserved.allowed) {
|
|
517
|
+
await replyText(ctx.sock, ctx.chatId, ctx.message, deps.strings?.limitReached);
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
await replyText(ctx.sock, ctx.chatId, ctx.message, deps.strings?.ack);
|
|
523
|
+
const image = await deps.images.getOrCreateImage(request);
|
|
524
|
+
const caption = deps.captionComposer ? await deps.captionComposer.composeCaption({ subject }) : void 0;
|
|
525
|
+
await ctx.sock.sendMessage(
|
|
526
|
+
ctx.chatId,
|
|
527
|
+
caption ? { image: { url: image.url }, caption } : { image: { url: image.url } },
|
|
528
|
+
{ quoted: ctx.message }
|
|
529
|
+
);
|
|
530
|
+
} catch (error) {
|
|
531
|
+
const text = isModerationError(error) ? deps.strings?.moderationBlocked : deps.strings?.error;
|
|
532
|
+
await replyText(ctx.sock, ctx.chatId, ctx.message, text);
|
|
533
|
+
}
|
|
534
|
+
return true;
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
470
538
|
// src/core/guardrails.ts
|
|
471
539
|
function detectLeakage(text) {
|
|
472
540
|
const flags = [
|
|
@@ -751,6 +819,17 @@ function createWhatsAppInboundHandler(config) {
|
|
|
751
819
|
return;
|
|
752
820
|
}
|
|
753
821
|
const senderPhone = await senderPhoneFor(sock, message, chatId);
|
|
822
|
+
if (config.images) {
|
|
823
|
+
const handled = await config.images({
|
|
824
|
+
sock,
|
|
825
|
+
message,
|
|
826
|
+
chatId,
|
|
827
|
+
senderId: senderPhone,
|
|
828
|
+
text,
|
|
829
|
+
hasPhoto: Boolean(message.message?.imageMessage)
|
|
830
|
+
});
|
|
831
|
+
if (handled) return;
|
|
832
|
+
}
|
|
754
833
|
const personaLayer = await resolvePersonaLayer(config, { senderPhone, text });
|
|
755
834
|
const buckets = await resolveBuckets({
|
|
756
835
|
mode: "public",
|
|
@@ -793,10 +872,13 @@ function createWhatsAppInboundHandler(config) {
|
|
|
793
872
|
canAcquireLease,
|
|
794
873
|
createTursoWaLeaseStore,
|
|
795
874
|
createWhatsAppChannel,
|
|
875
|
+
createWhatsAppImageHandler,
|
|
796
876
|
createWhatsAppInboundHandler,
|
|
797
877
|
decrypt,
|
|
878
|
+
drawSubject,
|
|
798
879
|
encrypt,
|
|
799
880
|
extractText,
|
|
881
|
+
isDrawRequest,
|
|
800
882
|
isGroupJid,
|
|
801
883
|
jidToPhoneNumber,
|
|
802
884
|
jidsMatch,
|
|
@@ -411,6 +411,71 @@ function createWhatsAppChannel(config) {
|
|
|
411
411
|
};
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
+
// src/channels/whatsapp/images.ts
|
|
415
|
+
var DEFAULT_DETECT_PATTERN = /\b(draw|paint|sketch|illustrate)\b|\b(make|generate|create)\b[^.!?]{0,20}\b(picture|image|drawing|photo|cartoon)\b/i;
|
|
416
|
+
var DEFAULT_STRIP_PATTERNS = [
|
|
417
|
+
/@\d+/g,
|
|
418
|
+
/^(hey|hi|please|ok)[,!\s]+/i,
|
|
419
|
+
/^(can|could|will)\s+you\s+/i,
|
|
420
|
+
// Longest-alternative-first ((an|a), not (a|an)): a first-match-wins
|
|
421
|
+
// alternation would otherwise take "a" out of "an" and leave a stray "n".
|
|
422
|
+
/^(please\s+)?(draw|paint|sketch|illustrate|make|generate|create)\s+(me|us)?\s*(an|a)?\s*(picture|image|drawing|sketch|photo|cartoon)?\s*(of\s+)?/i
|
|
423
|
+
];
|
|
424
|
+
function testPattern(pattern, text) {
|
|
425
|
+
return new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, "")).test(text);
|
|
426
|
+
}
|
|
427
|
+
function isDrawRequest(text, config = {}) {
|
|
428
|
+
return testPattern(config.detectPattern ?? DEFAULT_DETECT_PATTERN, text);
|
|
429
|
+
}
|
|
430
|
+
function drawSubject(text, config = {}) {
|
|
431
|
+
let subject = text;
|
|
432
|
+
for (const pattern of config.stripPatterns ?? DEFAULT_STRIP_PATTERNS) {
|
|
433
|
+
subject = subject.replace(pattern, " ").replace(/\s+/g, " ").trim();
|
|
434
|
+
}
|
|
435
|
+
return subject.length > 0 ? subject : text.trim();
|
|
436
|
+
}
|
|
437
|
+
function isModerationError(error) {
|
|
438
|
+
return typeof error === "object" && error !== null && error.name === "ImageGenerationError" && error.code === "moderation";
|
|
439
|
+
}
|
|
440
|
+
async function replyText(sock, chatId, message, text) {
|
|
441
|
+
if (!text) return;
|
|
442
|
+
await sock.sendMessage(chatId, { text }, { quoted: message }).catch(() => void 0);
|
|
443
|
+
}
|
|
444
|
+
function createWhatsAppImageHandler(deps) {
|
|
445
|
+
return async function handleWhatsAppImageRequest(ctx) {
|
|
446
|
+
if (!deps.images.isConfigured()) return false;
|
|
447
|
+
if (!isDrawRequest(ctx.text, deps.detector)) return false;
|
|
448
|
+
const subject = drawSubject(ctx.text, deps.detector);
|
|
449
|
+
try {
|
|
450
|
+
const photoBytes = ctx.hasPhoto ? await deps.downloadPhoto?.(ctx.message) : void 0;
|
|
451
|
+
const inputImages = photoBytes ? [{ bytes: photoBytes }] : void 0;
|
|
452
|
+
const request = { prompt: subject, inputImages };
|
|
453
|
+
if (deps.checkAndReserve) {
|
|
454
|
+
const cached2 = await deps.images.peekCached(request);
|
|
455
|
+
if (!cached2) {
|
|
456
|
+
const reserved = await deps.checkAndReserve(ctx.senderId);
|
|
457
|
+
if (!reserved.allowed) {
|
|
458
|
+
await replyText(ctx.sock, ctx.chatId, ctx.message, deps.strings?.limitReached);
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
await replyText(ctx.sock, ctx.chatId, ctx.message, deps.strings?.ack);
|
|
464
|
+
const image = await deps.images.getOrCreateImage(request);
|
|
465
|
+
const caption = deps.captionComposer ? await deps.captionComposer.composeCaption({ subject }) : void 0;
|
|
466
|
+
await ctx.sock.sendMessage(
|
|
467
|
+
ctx.chatId,
|
|
468
|
+
caption ? { image: { url: image.url }, caption } : { image: { url: image.url } },
|
|
469
|
+
{ quoted: ctx.message }
|
|
470
|
+
);
|
|
471
|
+
} catch (error) {
|
|
472
|
+
const text = isModerationError(error) ? deps.strings?.moderationBlocked : deps.strings?.error;
|
|
473
|
+
await replyText(ctx.sock, ctx.chatId, ctx.message, text);
|
|
474
|
+
}
|
|
475
|
+
return true;
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
414
479
|
// src/core/guardrails.ts
|
|
415
480
|
function detectLeakage(text) {
|
|
416
481
|
const flags = [
|
|
@@ -695,6 +760,17 @@ function createWhatsAppInboundHandler(config) {
|
|
|
695
760
|
return;
|
|
696
761
|
}
|
|
697
762
|
const senderPhone = await senderPhoneFor(sock, message, chatId);
|
|
763
|
+
if (config.images) {
|
|
764
|
+
const handled = await config.images({
|
|
765
|
+
sock,
|
|
766
|
+
message,
|
|
767
|
+
chatId,
|
|
768
|
+
senderId: senderPhone,
|
|
769
|
+
text,
|
|
770
|
+
hasPhoto: Boolean(message.message?.imageMessage)
|
|
771
|
+
});
|
|
772
|
+
if (handled) return;
|
|
773
|
+
}
|
|
698
774
|
const personaLayer = await resolvePersonaLayer(config, { senderPhone, text });
|
|
699
775
|
const buckets = await resolveBuckets({
|
|
700
776
|
mode: "public",
|
|
@@ -736,10 +812,13 @@ export {
|
|
|
736
812
|
canAcquireLease,
|
|
737
813
|
createTursoWaLeaseStore,
|
|
738
814
|
createWhatsAppChannel,
|
|
815
|
+
createWhatsAppImageHandler,
|
|
739
816
|
createWhatsAppInboundHandler,
|
|
740
817
|
decrypt,
|
|
818
|
+
drawSubject,
|
|
741
819
|
encrypt,
|
|
742
820
|
extractText,
|
|
821
|
+
isDrawRequest,
|
|
743
822
|
isGroupJid,
|
|
744
823
|
jidToPhoneNumber,
|
|
745
824
|
jidsMatch,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A Turso-backed exactly-once claim: an atomic upsert means two instances
|
|
3
|
+
* racing the same entry can never both believe they claimed it — see
|
|
4
|
+
* `canClaim`, which mirrors `canAcquireLease` in
|
|
5
|
+
* `../channels/whatsapp/lease.ts`. A failed send calls `release` to delete
|
|
6
|
+
* the claim row, letting the entry be claimed again by whichever instance
|
|
7
|
+
* ticks next within the grace window. An instance that dies mid-delivery
|
|
8
|
+
* without releasing leaves its claim to go stale instead: after
|
|
9
|
+
* `claimTimeoutMs` with no renewal, another instance may take it over.
|
|
10
|
+
*/
|
|
11
|
+
import type { Client } from "@libsql/client";
|
|
12
|
+
import type { ScheduleClaimStore } from "./types";
|
|
13
|
+
export declare const DEFAULT_CLAIM_TABLE = "chatter_schedule_claims";
|
|
14
|
+
/** How long an unreleased claim blocks reclaiming — short relative to a typical grace window, so a crashed claimant frees up in time for a retry to still land within grace. */
|
|
15
|
+
export declare const DEFAULT_CLAIM_TIMEOUT_MS: number;
|
|
16
|
+
export interface ScheduleClaimRow {
|
|
17
|
+
instanceId: string;
|
|
18
|
+
claimedAt: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Pure: may `instanceId` take (or keep) the claim on an entry right now,
|
|
22
|
+
* given the current row (null = unclaimed)? Mirrors the SQL WHERE clause in
|
|
23
|
+
* `createTursoScheduleClaimStore` — keep the two in sync.
|
|
24
|
+
*/
|
|
25
|
+
export declare function canClaim(existing: ScheduleClaimRow | null, instanceId: string, now: number, claimTimeoutMs: number): boolean;
|
|
26
|
+
export declare function createTursoScheduleClaimStore(client: Client, instanceId: string, tableName?: string, claimTimeoutMs?: number): ScheduleClaimStore;
|
|
27
|
+
//# sourceMappingURL=claimStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claimStore.d.ts","sourceRoot":"","sources":["../../src/scheduler/claimStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD,eAAO,MAAM,mBAAmB,4BAA4B,CAAC;AAC7D,gLAAgL;AAChL,eAAO,MAAM,wBAAwB,QAAa,CAAC;AAKnD,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CACtB,QAAQ,EAAE,gBAAgB,GAAG,IAAI,EACjC,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,EACX,cAAc,EAAE,MAAM,GACrB,OAAO,CAIT;AA8BD,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,SAAS,GAAE,MAA4B,EACvC,cAAc,GAAE,MAAiC,GAChD,kBAAkB,CA4BpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claimStore.test.d.ts","sourceRoot":"","sources":["../../src/scheduler/claimStore.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type OpenAI from "openai";
|
|
2
|
+
import type { AnswerFn } from "../core/answer";
|
|
3
|
+
import type { PipelineMessage, PipelineMode } from "../core/pipeline";
|
|
4
|
+
import type { ComposeFn, ScheduleEntry } from "./types";
|
|
5
|
+
/**
|
|
6
|
+
* Runs the pluggable compose step and guarantees a message comes back: a
|
|
7
|
+
* missing compose function, a blank result, or a throwing/rejecting compose
|
|
8
|
+
* all fall back to `fallbackMessage` rather than blocking delivery.
|
|
9
|
+
*/
|
|
10
|
+
export declare function composeMessage(entry: ScheduleEntry, compose: ComposeFn | undefined, fallbackMessage: string): Promise<string>;
|
|
11
|
+
export interface AnswerComposeOptions {
|
|
12
|
+
client: OpenAI;
|
|
13
|
+
system: string;
|
|
14
|
+
/** Turns a schedule entry's opaque payload into the messages sent to the brain. */
|
|
15
|
+
buildMessages: (entry: ScheduleEntry) => PipelineMessage[];
|
|
16
|
+
answerFn?: AnswerFn;
|
|
17
|
+
mode?: PipelineMode;
|
|
18
|
+
temperature?: number;
|
|
19
|
+
model?: string;
|
|
20
|
+
}
|
|
21
|
+
/** Builds a `ComposeFn` that composes via `answerFn`/`completeOnce`, for callers who want an LLM-written message instead of static text. */
|
|
22
|
+
export declare function createAnswerCompose(options: AnswerComposeOptions): ComposeFn;
|
|
23
|
+
//# sourceMappingURL=compose.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.d.ts","sourceRoot":"","sources":["../../src/scheduler/compose.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExD;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,SAAS,GAAG,SAAS,EAC9B,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,MAAM,CAAC,CASjB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,aAAa,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,eAAe,EAAE,CAAC;IAC3D,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,4IAA4I;AAC5I,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAa5E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.test.d.ts","sourceRoot":"","sources":["../../src/scheduler/compose.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ChannelSenderRegistry } from "../channels/senders";
|
|
2
|
+
import type { ScheduleEntry } from "./types";
|
|
3
|
+
export interface DeliverOptions {
|
|
4
|
+
senders: ChannelSenderRegistry;
|
|
5
|
+
/** Try voice first when the channel supports it; text is always the guaranteed fallback. */
|
|
6
|
+
voiceAttempted?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/** Delivers a composed message. Text is guaranteed; voice is only ever a best-effort first attempt. */
|
|
9
|
+
export declare function deliver(entry: ScheduleEntry, message: string, options: DeliverOptions): Promise<boolean>;
|
|
10
|
+
//# sourceMappingURL=deliver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deliver.d.ts","sourceRoot":"","sources":["../../src/scheduler/deliver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,qBAAqB,CAAC;IAC/B,4FAA4F;IAC5F,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,uGAAuG;AACvG,wBAAsB,OAAO,CAC3B,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,OAAO,CAAC,CAMlB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deliver.test.d.ts","sourceRoot":"","sources":["../../src/scheduler/deliver.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure fire-time filtering. A schedule fires once its time has passed, but
|
|
3
|
+
* only within a grace window — a stale entry (the process was down, or the
|
|
4
|
+
* tick was delayed) past grace is skipped rather than delivered late.
|
|
5
|
+
*/
|
|
6
|
+
import type { ScheduleEntry } from "./types";
|
|
7
|
+
export declare function isDue(entry: ScheduleEntry, now: number): boolean;
|
|
8
|
+
export declare function isWithinGrace(entry: ScheduleEntry, now: number, graceMs: number): boolean;
|
|
9
|
+
/** Due entries still inside their grace window, in caller-supplied order. */
|
|
10
|
+
export declare function dueDrops(entries: ScheduleEntry[], now: number, graceMs: number): ScheduleEntry[];
|
|
11
|
+
/** Due entries whose grace window has already passed — reported so callers can log them, never delivered. */
|
|
12
|
+
export declare function staleDrops(entries: ScheduleEntry[], now: number, graceMs: number): ScheduleEntry[];
|
|
13
|
+
//# sourceMappingURL=dueDrops.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dueDrops.d.ts","sourceRoot":"","sources":["../../src/scheduler/dueDrops.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,wBAAgB,KAAK,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAEhE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzF;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CAEhG;AAED,6GAA6G;AAC7G,wBAAgB,UAAU,CACxB,OAAO,EAAE,aAAa,EAAE,EACxB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,GACd,aAAa,EAAE,CAEjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dueDrops.test.d.ts","sourceRoot":"","sources":["../../src/scheduler/dueDrops.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduler — exactly-once outbound delivery on a caller-supplied fire time.
|
|
3
|
+
* Published as the `@diegoaltoworks/chatter/scheduler` subpath so the core
|
|
4
|
+
* install stays untouched by it.
|
|
5
|
+
*
|
|
6
|
+
* Content-free: chatter stores no schedule data of its own. A caller
|
|
7
|
+
* supplies candidate {@link ScheduleEntry} objects each tick (its own store,
|
|
8
|
+
* cron, queue, anything); this module adds the parts that are easy to get
|
|
9
|
+
* wrong in a multi-instance deployment — an exactly-once claim in Turso so
|
|
10
|
+
* two instances never both deliver the same entry, a fire-time grace window
|
|
11
|
+
* so a delayed tick doesn't send stale messages, a pluggable compose step
|
|
12
|
+
* (static text or an LLM call via `answerFn`/`completeOnce`) that always
|
|
13
|
+
* falls back rather than blocking delivery, and delivery through the
|
|
14
|
+
* channel sender registry with an optional voice-attempted/text-guaranteed
|
|
15
|
+
* mode.
|
|
16
|
+
*
|
|
17
|
+
* @packageDocumentation
|
|
18
|
+
*/
|
|
19
|
+
export type { ScheduleClaimRow } from "./claimStore";
|
|
20
|
+
export { canClaim, createTursoScheduleClaimStore, DEFAULT_CLAIM_TABLE, DEFAULT_CLAIM_TIMEOUT_MS, } from "./claimStore";
|
|
21
|
+
export type { AnswerComposeOptions } from "./compose";
|
|
22
|
+
export { composeMessage, createAnswerCompose } from "./compose";
|
|
23
|
+
export type { DeliverOptions } from "./deliver";
|
|
24
|
+
export { deliver } from "./deliver";
|
|
25
|
+
export { dueDrops, isDue, isWithinGrace, staleDrops } from "./dueDrops";
|
|
26
|
+
export type { Scheduler, SchedulerConfig } from "./scheduler";
|
|
27
|
+
export { createScheduler, DEFAULT_FALLBACK_MESSAGE, DEFAULT_GRACE_MS, DEFAULT_INTERVAL_MS, } from "./scheduler";
|
|
28
|
+
export type { TickOptions } from "./tick";
|
|
29
|
+
export { runTick } from "./tick";
|
|
30
|
+
export type { ComposeFn, ScheduleClaimStore, ScheduleEntry, TickResult } from "./types";
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/scheduler/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EACL,QAAQ,EACR,6BAA6B,EAC7B,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAChE,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxE,YAAY,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EACL,eAAe,EACf,wBAAwB,EACxB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACjC,YAAY,EAAE,SAAS,EAAE,kBAAkB,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC"}
|