@diegoaltoworks/chatter 0.26.0 → 0.27.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.
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Channel-agnostic reply gates — pure and testable, zero imports from the
3
+ * rest of chatter. A transport (WhatsApp, or any future channel) resolves
4
+ * its own wire format into a {@link ChannelMessage} and calls
5
+ * {@link decideChannelAction} to learn whether to answer, stay quiet, or
6
+ * flip a mute switch. Nothing here knows about jids, Baileys, or any
7
+ * specific bot's name — mention/reply-to detection is the transport's job;
8
+ * this module only combines the already-resolved booleans with allowlist,
9
+ * mute, and rate-limit policy.
10
+ */
11
+ /** The slice of an inbound message every channel can resolve into this shape. */
12
+ export interface ChannelMessage {
13
+ /** The conversation this message belongs to — a DM thread or a group. */
14
+ chatId: string;
15
+ senderId: string;
16
+ text: string;
17
+ /** True for a 1:1 conversation; false for a group/channel with other members. */
18
+ isDirectMessage: boolean;
19
+ /** Resolved by the caller: does this message @-mention the bot? */
20
+ mentionsBot: boolean;
21
+ /** Resolved by the caller: is this message a reply to one of the bot's own messages? */
22
+ isReplyToBot: boolean;
23
+ /** True when the bot itself sent this message (including via another session — see {@link isEffectivelyFromSelf}). */
24
+ fromBot: boolean;
25
+ }
26
+ export type ChannelAction = "reply" | "ignore" | "mute" | "unmute";
27
+ export interface ChannelGateConfig {
28
+ /** Chats eligible for a reply. Empty = every chat is eligible (the master allowlist switch). */
29
+ allowedChats: string[];
30
+ /** Group chats currently muted — addressed messages there are ignored until unmuted. Has no effect on DMs, which always reply. */
31
+ mutedChats: Set<string>;
32
+ /**
33
+ * Caller-supplied trigger for "go quiet in this group chat". No default:
34
+ * this module ships no bot name or in-character phrasing, so mute/unmute
35
+ * are inert unless a host configures a pattern. Never applied to DMs.
36
+ */
37
+ muteRegex?: RegExp;
38
+ unmuteRegex?: RegExp;
39
+ }
40
+ /**
41
+ * DMs always reply. In groups, only an allowlisted, unmuted chat gets a
42
+ * reply, and only when addressed (mentioned or replying to the bot) —
43
+ * merely mentioning the bot's name in third person is not an invitation.
44
+ * The unmute check runs before the muted-chat gate so an unmute phrase can
45
+ * un-silence the bot in the same message, and both run after the allowlist
46
+ * so a chat the bot isn't eligible for can't toggle its mute state.
47
+ */
48
+ export declare function decideChannelAction(msg: ChannelMessage, config: ChannelGateConfig): ChannelAction;
49
+ /**
50
+ * Sliding-window rate limiter (pure, injectable clock): true = allowed and
51
+ * counted. One instance guards one budget — callers wanting separate DM and
52
+ * group budgets create two (see {@link underReplyRateLimit}).
53
+ */
54
+ export declare function createSlidingWindowRateLimiter(maxPerWindow: number, windowMs: number, now?: () => number): (key: string) => boolean;
55
+ /**
56
+ * Routes a chat to its DM or group rate limiter so a flood in one can't
57
+ * spend the other's budget — a DM flood used to be entirely unthrottled
58
+ * before this split existed. Each limiter is expected to be its own
59
+ * {@link createSlidingWindowRateLimiter} instance.
60
+ */
61
+ export declare function underReplyRateLimit(msg: Pick<ChannelMessage, "chatId" | "isDirectMessage">, limiters: {
62
+ dm: (chatId: string) => boolean;
63
+ group: (chatId: string) => boolean;
64
+ }): boolean;
65
+ /**
66
+ * Every session's own identities ever seen, keyed by session id. A
67
+ * multi-session deployment (e.g. two linked numbers in one process) gives
68
+ * each session only its own identity from its transport — a message that
69
+ * actually came from a *different* session's identity looks like a
70
+ * stranger, not "us", and the two sessions can ping-pong replies at each
71
+ * other indefinitely. Callers populate this directly (`registry.set(sessionId, ids)`)
72
+ * as each session's identities become known, typically on connect and kept
73
+ * in sync as they change.
74
+ *
75
+ * Entries are NOT expected to be removed when a session disconnects: a
76
+ * session's own identity is still "us" while it reconnects, and losing that
77
+ * during exactly the disconnect/reconnect window this guard exists for
78
+ * would defeat it.
79
+ */
80
+ export type SessionIdentityRegistry = Map<string, string[]>;
81
+ /** True when `id` matches an identity of ANY session ever registered, not just the one that received the message. */
82
+ export declare function isFromAnySession(id: string | undefined, registry: SessionIdentityRegistry): boolean;
83
+ /**
84
+ * The one predicate transports need before deciding fromBot: a message is
85
+ * effectively from the bot itself if the transport already flagged it so,
86
+ * or if its sender id matches an identity registered by any session (the
87
+ * loop-guard case a single session's own flag can't catch).
88
+ */
89
+ export declare function isEffectivelyFromSelf(msg: Pick<ChannelMessage, "fromBot" | "senderId">, registry: SessionIdentityRegistry): boolean;
90
+ //# sourceMappingURL=gates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gates.d.ts","sourceRoot":"","sources":["../../src/channels/gates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,eAAe,EAAE,OAAO,CAAC;IACzB,mEAAmE;IACnE,WAAW,EAAE,OAAO,CAAC;IACrB,wFAAwF;IACxF,YAAY,EAAE,OAAO,CAAC;IACtB,sHAAsH;IACtH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEnE,MAAM,WAAW,iBAAiB;IAChC,gGAAgG;IAChG,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,kIAAkI;IAClI,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAOD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,iBAAiB,GAAG,aAAa,CAYjG;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,GAAG,GAAE,MAAM,MAAiB,SAGD,MAAM,KAAG,OAAO,CAY5C;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,iBAAiB,CAAC,EACvD,QAAQ,EAAE;IAAE,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAA;CAAE,GAChF,OAAO,CAET;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,uBAAuB,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;AAE5D,qHAAqH;AACrH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,GAAG,SAAS,EACtB,QAAQ,EAAE,uBAAuB,GAChC,OAAO,CAMT;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,GAAG,UAAU,CAAC,EACjD,QAAQ,EAAE,uBAAuB,GAChC,OAAO,CAET"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=gates.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gates.test.d.ts","sourceRoot":"","sources":["../../src/channels/gates.test.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegoaltoworks/chatter",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "description": "Embeddable AI chatbot framework with RAG, authentication, and customizable widgets",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",