@diegoaltoworks/chatter 0.47.0 → 0.48.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 +3 -4
- package/dist/auth/apikeys.d.ts.map +1 -1
- package/dist/auth/secretStrength.d.ts +8 -0
- package/dist/auth/secretStrength.d.ts.map +1 -0
- package/dist/bin/create-apikey.js +22 -3
- package/dist/bin/wa-pair.js +59 -11
- package/dist/channels/gates.d.ts +9 -0
- package/dist/channels/gates.d.ts.map +1 -1
- package/dist/channels/index.d.ts +1 -0
- package/dist/channels/index.d.ts.map +1 -1
- package/dist/channels/index.js +252 -8
- package/dist/channels/index.mjs +250 -8
- package/dist/channels/pipeline.d.ts +106 -0
- package/dist/channels/pipeline.d.ts.map +1 -0
- package/dist/channels/senders.d.ts +2 -1
- package/dist/channels/senders.d.ts.map +1 -1
- package/dist/channels/whatsapp/channel.d.ts +4 -0
- package/dist/channels/whatsapp/channel.d.ts.map +1 -1
- package/dist/channels/whatsapp/crypto.d.ts +17 -3
- package/dist/channels/whatsapp/crypto.d.ts.map +1 -1
- package/dist/channels/whatsapp/inbound.d.ts +34 -4
- package/dist/channels/whatsapp/inbound.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.d.ts +2 -1
- package/dist/channels/whatsapp/index.d.ts.map +1 -1
- package/dist/channels/whatsapp/index.js +395 -159
- package/dist/channels/whatsapp/index.mjs +394 -160
- package/dist/channels/whatsapp/router.d.ts +107 -0
- package/dist/channels/whatsapp/router.d.ts.map +1 -0
- package/dist/core/answer.d.ts +5 -2
- package/dist/core/answer.d.ts.map +1 -1
- package/dist/core/logger.d.ts +23 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/messages.d.ts +8 -0
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/pipeline.d.ts +2 -0
- package/dist/core/pipeline.d.ts.map +1 -1
- package/dist/core/retrieval.d.ts +4 -0
- package/dist/core/retrieval.d.ts.map +1 -1
- package/dist/flows/engine.d.ts +3 -0
- package/dist/flows/engine.d.ts.map +1 -1
- package/dist/flows/index.d.ts +1 -0
- package/dist/flows/index.d.ts.map +1 -1
- package/dist/flows/index.js +91 -25
- package/dist/flows/index.mjs +91 -26
- package/dist/flows/intent.d.ts +2 -1
- package/dist/flows/intent.d.ts.map +1 -1
- package/dist/flows/loader.d.ts +5 -3
- package/dist/flows/loader.d.ts.map +1 -1
- package/dist/flows/types.d.ts +9 -0
- package/dist/flows/types.d.ts.map +1 -1
- package/dist/history/index.d.ts +17 -0
- package/dist/history/index.d.ts.map +1 -0
- package/dist/history/index.js +100 -0
- package/dist/history/index.mjs +73 -0
- package/dist/history/tursoStore.d.ts +26 -0
- package/dist/history/tursoStore.d.ts.map +1 -0
- package/dist/history/types.d.ts +18 -0
- package/dist/history/types.d.ts.map +1 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1146 -32444
- package/dist/index.mjs +1082 -32410
- package/dist/mcp-server/cost-tracker.d.ts +11 -6
- package/dist/mcp-server/cost-tracker.d.ts.map +1 -1
- package/dist/mcp-server/logger.d.ts +5 -2
- package/dist/mcp-server/logger.d.ts.map +1 -1
- package/dist/mcp-server/sdk.d.ts +23 -0
- package/dist/mcp-server/sdk.d.ts.map +1 -0
- package/dist/mcp-server/tool-input.d.ts +12 -0
- package/dist/mcp-server/tool-input.d.ts.map +1 -0
- package/dist/mcp-server/types.d.ts +10 -3
- package/dist/mcp-server/types.d.ts.map +1 -1
- package/dist/mcp-server.d.ts +1 -2
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +192 -111
- package/dist/mcp-server.mjs +179 -98
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/bodyLimit.d.ts +11 -0
- package/dist/middleware/bodyLimit.d.ts.map +1 -0
- package/dist/middleware/clientKey.d.ts +15 -0
- package/dist/middleware/clientKey.d.ts.map +1 -0
- package/dist/middleware/cors.d.ts.map +1 -1
- package/dist/middleware/jwt.d.ts +0 -3
- package/dist/middleware/jwt.d.ts.map +1 -1
- package/dist/middleware/originMatch.d.ts +27 -0
- package/dist/middleware/originMatch.d.ts.map +1 -0
- package/dist/middleware/ratelimit.d.ts +0 -6
- package/dist/middleware/ratelimit.d.ts.map +1 -1
- package/dist/middleware/referrer.d.ts +5 -1
- package/dist/middleware/referrer.d.ts.map +1 -1
- package/dist/middleware/securityHeaders.d.ts +20 -0
- package/dist/middleware/securityHeaders.d.ts.map +1 -0
- package/dist/middleware/session.d.ts +2 -1
- package/dist/middleware/session.d.ts.map +1 -1
- package/dist/middleware/windowBucket.d.ts +19 -0
- package/dist/middleware/windowBucket.d.ts.map +1 -0
- package/dist/personas/index.js +20 -1
- package/dist/personas/index.mjs +20 -1
- package/dist/personas/resolver.d.ts +3 -0
- package/dist/personas/resolver.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/compose.d.ts +2 -1
- package/dist/scheduler/compose.d.ts.map +1 -1
- package/dist/scheduler/index.js +29 -9
- package/dist/scheduler/index.mjs +29 -9
- package/dist/scheduler/scheduler.d.ts +3 -0
- package/dist/scheduler/scheduler.d.ts.map +1 -1
- package/dist/scheduler/tick.d.ts +2 -0
- package/dist/scheduler/tick.d.ts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +678 -528
- package/dist/server.mjs +678 -528
- package/dist/types.d.ts +87 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +22 -7
package/README.md
CHANGED
|
@@ -90,7 +90,6 @@ Complete guides for setup, deployment, and integration:
|
|
|
90
90
|
- **[Usage Metering](./docs/usage.md)** - Daily spend caps for paid features
|
|
91
91
|
- **[Deployment](./docs/deployment.md)** - Google Cloud Run, Fly.io, Railway, VPS
|
|
92
92
|
- **[Testing](./docs/testing.md)** - Comprehensive testing guide
|
|
93
|
-
- **[Code Quality](./docs/CODE_QUALITY.md)** - Security assessment and architecture review
|
|
94
93
|
- **[Sprint Review](./docs/sprint-review.md)** - Cross-module & security-invariant audit
|
|
95
94
|
- **[FAQs](./docs/faqs.md)** - Troubleshooting and common questions
|
|
96
95
|
|
|
@@ -137,7 +136,7 @@ See [Client Setup Guide](./docs/client.md) for detailed integration examples, th
|
|
|
137
136
|
|
|
138
137
|
## MCP Server Integration
|
|
139
138
|
|
|
140
|
-
Use Chatter as a Model Context Protocol (MCP) server to expose your chatbot to Claude Desktop, VS Code extensions, and other MCP-compatible tools:
|
|
139
|
+
Use Chatter as a Model Context Protocol (MCP) server to expose your chatbot to Claude Desktop, VS Code extensions, and other MCP-compatible tools. `@modelcontextprotocol/sdk` and `zod` are optional peer dependencies scoped to this subpath — install them to use it: `bun add @modelcontextprotocol/sdk zod`.
|
|
141
140
|
|
|
142
141
|
**Basic Setup:**
|
|
143
142
|
```typescript
|
|
@@ -237,7 +236,7 @@ Add to your `claude_desktop_config.json`:
|
|
|
237
236
|
Both tools:
|
|
238
237
|
- Support single messages or full conversation history
|
|
239
238
|
- Track conversation IDs across sessions for continuity
|
|
240
|
-
- Return token usage
|
|
239
|
+
- Return token usage in response metadata, plus an estimated USD cost when `openai.pricing` is configured
|
|
241
240
|
- Use RAG-powered context retrieval from your knowledge base
|
|
242
241
|
- Can be customized with different names and descriptions
|
|
243
242
|
- Can be individually enabled/disabled
|
|
@@ -245,7 +244,7 @@ Both tools:
|
|
|
245
244
|
|
|
246
245
|
**Features:**
|
|
247
246
|
- **Conversation ID Tracking**: Pass `conversationId` parameter to maintain session continuity across tool calls
|
|
248
|
-
- **Cost Tracking**: Every response includes token usage (prompt/completion/total)
|
|
247
|
+
- **Cost Tracking**: Every response includes token usage (prompt/completion/total). Add `openai: { pricing: { promptPer1M, completionPer1M } }` to also get an estimated USD cost — without it, `estimatedCost` is `null` rather than guessing at a price for the wrong model
|
|
249
248
|
- **Rate Limiting**: Optional per-tool rate limiting (requests per minute) to control API usage
|
|
250
249
|
- **Observability**: Comprehensive logging with conversation tracking and cost data
|
|
251
250
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apikeys.d.ts","sourceRoot":"","sources":["../../src/auth/apikeys.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"apikeys.d.ts","sourceRoot":"","sources":["../../src/auth/apikeys.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,aAAa;IAC5B,iCAAiC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,aAAa;IAC5B,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,IAAI,EAAE,SAAS,CAAC;IAChB,0BAA0B;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,2BAA2B;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,+BAA+B;IAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,iCAAiC;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,+BAA+B;IAC/B,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,WAAW,CAAa;IAEhC,YAAY,MAAM,EAAE,MAAM,EAGzB;IAED;;;;;OAKG;IACG,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBzD;IAED;;;;;OAKG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAsBjD;IAED;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IA6BvB;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAW1C;CACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared strength check for caller-supplied secrets (JWT signing key, WhatsApp
|
|
3
|
+
* session-encryption passphrase). Both guard high-value material — API key
|
|
4
|
+
* forgery or WhatsApp account takeover — so a short or empty value fails
|
|
5
|
+
* fast with an actionable error instead of quietly producing a weak key.
|
|
6
|
+
*/
|
|
7
|
+
export declare function assertStrongSecret(secret: string | undefined, label: string): void;
|
|
8
|
+
//# sourceMappingURL=secretStrength.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secretStrength.d.ts","sourceRoot":"","sources":["../../src/auth/secretStrength.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAUlF"}
|
|
@@ -4,12 +4,25 @@
|
|
|
4
4
|
// src/auth/apikeys.ts
|
|
5
5
|
var import_node_crypto = require("node:crypto");
|
|
6
6
|
var import_jose = require("jose");
|
|
7
|
+
|
|
8
|
+
// src/auth/secretStrength.ts
|
|
9
|
+
var MIN_SECRET_LENGTH = 16;
|
|
10
|
+
function assertStrongSecret(secret, label) {
|
|
11
|
+
if (!secret) {
|
|
12
|
+
throw new Error(`${label} is required and cannot be empty.`);
|
|
13
|
+
}
|
|
14
|
+
if (secret.length < MIN_SECRET_LENGTH) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
`${label} is too weak: must be at least ${MIN_SECRET_LENGTH} characters (got ${secret.length}). Generate one with \`openssl rand -base64 32\`.`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/auth/apikeys.ts
|
|
7
22
|
var ApiKeyManager = class {
|
|
8
23
|
secretBytes;
|
|
9
24
|
constructor(secret) {
|
|
10
|
-
|
|
11
|
-
throw new Error("API key secret is required");
|
|
12
|
-
}
|
|
25
|
+
assertStrongSecret(secret, "API key secret (CHATTER_SECRET / config.auth.secret)");
|
|
13
26
|
this.secretBytes = new TextEncoder().encode(secret);
|
|
14
27
|
}
|
|
15
28
|
/**
|
|
@@ -155,6 +168,12 @@ Examples:
|
|
|
155
168
|
console.error(" CHATTER_SECRET=your-secret npx chatter create-apikey\n");
|
|
156
169
|
process.exit(1);
|
|
157
170
|
}
|
|
171
|
+
try {
|
|
172
|
+
assertStrongSecret(secret, "CHATTER_SECRET");
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error(`\u274C ${error.message}`);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
158
177
|
const manager = new ApiKeyManager(secret);
|
|
159
178
|
const keyName = options.name || "api-key";
|
|
160
179
|
const expiresIn = options.expiresIn || "365d";
|
package/dist/bin/wa-pair.js
CHANGED
|
@@ -26,28 +26,74 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
// bin/wa-pair.ts
|
|
27
27
|
var import_client = require("@libsql/client");
|
|
28
28
|
|
|
29
|
+
// src/auth/secretStrength.ts
|
|
30
|
+
var MIN_SECRET_LENGTH = 16;
|
|
31
|
+
function assertStrongSecret(secret, label) {
|
|
32
|
+
if (!secret) {
|
|
33
|
+
throw new Error(`${label} is required and cannot be empty.`);
|
|
34
|
+
}
|
|
35
|
+
if (secret.length < MIN_SECRET_LENGTH) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`${label} is too weak: must be at least ${MIN_SECRET_LENGTH} characters (got ${secret.length}). Generate one with \`openssl rand -base64 32\`.`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
29
42
|
// src/channels/whatsapp/crypto.ts
|
|
30
43
|
var import_node_crypto = require("node:crypto");
|
|
44
|
+
var import_node_util = require("node:util");
|
|
45
|
+
var scryptAsync = (0, import_node_util.promisify)(import_node_crypto.scrypt);
|
|
31
46
|
var IV_LENGTH = 12;
|
|
32
47
|
var TAG_LENGTH = 16;
|
|
33
|
-
|
|
48
|
+
var SALT_LENGTH = 16;
|
|
49
|
+
var KEY_LENGTH = 32;
|
|
50
|
+
var VERSION = 1;
|
|
51
|
+
function deriveLegacyKey(secret) {
|
|
34
52
|
return (0, import_node_crypto.createHash)("sha256").update(secret, "utf-8").digest();
|
|
35
53
|
}
|
|
36
|
-
function
|
|
54
|
+
async function deriveKey(secret, salt) {
|
|
55
|
+
return await scryptAsync(secret, salt, KEY_LENGTH);
|
|
56
|
+
}
|
|
57
|
+
async function encrypt(plaintext, secret) {
|
|
58
|
+
const salt = (0, import_node_crypto.randomBytes)(SALT_LENGTH);
|
|
37
59
|
const iv = (0, import_node_crypto.randomBytes)(IV_LENGTH);
|
|
38
|
-
const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", deriveKey(secret), iv);
|
|
60
|
+
const cipher = (0, import_node_crypto.createCipheriv)("aes-256-gcm", await deriveKey(secret, salt), iv);
|
|
39
61
|
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
40
|
-
return Buffer.concat([
|
|
62
|
+
return Buffer.concat([
|
|
63
|
+
Buffer.from([VERSION]),
|
|
64
|
+
salt,
|
|
65
|
+
iv,
|
|
66
|
+
cipher.getAuthTag(),
|
|
67
|
+
ciphertext
|
|
68
|
+
]).toString("base64");
|
|
41
69
|
}
|
|
42
|
-
function
|
|
43
|
-
const
|
|
70
|
+
async function decryptVersioned(raw, secret) {
|
|
71
|
+
const salt = raw.subarray(1, 1 + SALT_LENGTH);
|
|
72
|
+
const iv = raw.subarray(1 + SALT_LENGTH, 1 + SALT_LENGTH + IV_LENGTH);
|
|
73
|
+
const tag = raw.subarray(1 + SALT_LENGTH + IV_LENGTH, 1 + SALT_LENGTH + IV_LENGTH + TAG_LENGTH);
|
|
74
|
+
const ciphertext = raw.subarray(1 + SALT_LENGTH + IV_LENGTH + TAG_LENGTH);
|
|
75
|
+
const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", await deriveKey(secret, salt), iv);
|
|
76
|
+
decipher.setAuthTag(tag);
|
|
77
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf-8");
|
|
78
|
+
}
|
|
79
|
+
function decryptLegacy(raw, secret) {
|
|
44
80
|
const iv = raw.subarray(0, IV_LENGTH);
|
|
45
81
|
const tag = raw.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
|
|
46
82
|
const ciphertext = raw.subarray(IV_LENGTH + TAG_LENGTH);
|
|
47
|
-
const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm",
|
|
83
|
+
const decipher = (0, import_node_crypto.createDecipheriv)("aes-256-gcm", deriveLegacyKey(secret), iv);
|
|
48
84
|
decipher.setAuthTag(tag);
|
|
49
85
|
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf-8");
|
|
50
86
|
}
|
|
87
|
+
async function decrypt(payload, secret) {
|
|
88
|
+
const raw = Buffer.from(payload, "base64");
|
|
89
|
+
if (raw[0] === VERSION) {
|
|
90
|
+
try {
|
|
91
|
+
return await decryptVersioned(raw, secret);
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return decryptLegacy(raw, secret);
|
|
96
|
+
}
|
|
51
97
|
|
|
52
98
|
// src/channels/whatsapp/authState.ts
|
|
53
99
|
var schemaReady = /* @__PURE__ */ new WeakMap();
|
|
@@ -72,10 +118,10 @@ async function useTursoAuthState(db2, secret, sessionId2, runtime) {
|
|
|
72
118
|
});
|
|
73
119
|
const row = result.rows[0];
|
|
74
120
|
if (!row) return null;
|
|
75
|
-
return JSON.parse(decrypt(row.value, secret), runtime.bufferJSON.reviver);
|
|
121
|
+
return JSON.parse(await decrypt(row.value, secret), runtime.bufferJSON.reviver);
|
|
76
122
|
}
|
|
77
123
|
async function write(id, value) {
|
|
78
|
-
const payload = encrypt(JSON.stringify(value, runtime.bufferJSON.replacer), secret);
|
|
124
|
+
const payload = await encrypt(JSON.stringify(value, runtime.bufferJSON.replacer), secret);
|
|
79
125
|
await db2.execute({
|
|
80
126
|
sql: "INSERT OR REPLACE INTO wa_auth (id, value) VALUES (?, ?)",
|
|
81
127
|
args: [rowId(id), payload]
|
|
@@ -386,8 +432,10 @@ if (args.includes("--code") && !flagValue("--code")) {
|
|
|
386
432
|
var phoneNumber = flagValue("--code")?.replace(/[^0-9]/g, "");
|
|
387
433
|
var reset = args.includes("--reset");
|
|
388
434
|
var sessionSecret = process.env.WA_SESSION_SECRET;
|
|
389
|
-
|
|
390
|
-
|
|
435
|
+
try {
|
|
436
|
+
assertStrongSecret(sessionSecret, "WA_SESSION_SECRET");
|
|
437
|
+
} catch (error) {
|
|
438
|
+
console.error(`\u274C ${error.message}`);
|
|
391
439
|
process.exit(1);
|
|
392
440
|
}
|
|
393
441
|
if (!process.env.TURSO_URL) {
|
package/dist/channels/gates.d.ts
CHANGED
|
@@ -37,6 +37,15 @@ export interface ChannelGateConfig {
|
|
|
37
37
|
muteRegex?: RegExp;
|
|
38
38
|
unmuteRegex?: RegExp;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* True when `msg` is dropped because its chat isn't on `allowedChats`. The
|
|
42
|
+
* allowlist is the first group-only check {@link decideChannelAction} makes,
|
|
43
|
+
* before mute/unmute and addressing, so whenever this is true the allowlist
|
|
44
|
+
* really is the attributable reason for the "ignore" — a transport can log
|
|
45
|
+
* just this case without misattributing a drop that happened for a different
|
|
46
|
+
* cause (from the bot itself, blank text, muted, unaddressed).
|
|
47
|
+
*/
|
|
48
|
+
export declare function isBlockedByAllowlist(msg: ChannelMessage, config: Pick<ChannelGateConfig, "allowedChats">): boolean;
|
|
40
49
|
/**
|
|
41
50
|
* DMs always reply. In groups, only an allowlisted, unmuted chat gets a
|
|
42
51
|
* reply, and only when addressed (mentioned or replying to the bot) —
|
|
@@ -1 +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,
|
|
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,oBAAoB,CAClC,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,cAAc,CAAC,GAC9C,OAAO,CAQT;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,iBAAiB,GAAG,aAAa,CAUjG;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"}
|
package/dist/channels/index.d.ts
CHANGED
|
@@ -42,6 +42,7 @@ export interface Channel {
|
|
|
42
42
|
stop?(): void | Promise<void>;
|
|
43
43
|
}
|
|
44
44
|
export * from "./gates";
|
|
45
|
+
export { createInboundPipeline, type InboundPipelineConfig, type InboundPipelineDeps, type InboundPipelineOutcome, type InboundReplySender, type InboundTurn, } from "./pipeline";
|
|
45
46
|
export type { ChannelSender, ChannelSenderRegistry } from "./senders";
|
|
46
47
|
export { createSenderRegistry } from "./senders";
|
|
47
48
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/channels/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD,MAAM,WAAW,OAAO;IACtB,2FAA2F;IAC3F,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD;;;;;;OAMG;IACH,IAAI,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,cAAc,SAAS,CAAC;AACxB,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/channels/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD,MAAM,WAAW,OAAO;IACtB,2FAA2F;IAC3F,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,IAAI,EAAE,kBAAkB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD;;;;;;OAMG;IACH,IAAI,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/B;AAED,cAAc,SAAS,CAAC;AACxB,OAAO,EACL,qBAAqB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,kBAAkB,EACvB,KAAK,WAAW,GACjB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/channels/index.js
CHANGED
|
@@ -20,9 +20,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/channels/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
createInboundPipeline: () => createInboundPipeline,
|
|
23
24
|
createSenderRegistry: () => createSenderRegistry,
|
|
24
25
|
createSlidingWindowRateLimiter: () => createSlidingWindowRateLimiter,
|
|
25
26
|
decideChannelAction: () => decideChannelAction,
|
|
27
|
+
isBlockedByAllowlist: () => isBlockedByAllowlist,
|
|
26
28
|
isEffectivelyFromSelf: () => isEffectivelyFromSelf,
|
|
27
29
|
isFromAnySession: () => isFromAnySession,
|
|
28
30
|
underReplyRateLimit: () => underReplyRateLimit
|
|
@@ -33,12 +35,13 @@ module.exports = __toCommonJS(index_exports);
|
|
|
33
35
|
function matches(re, text) {
|
|
34
36
|
return new RegExp(re.source, re.flags.replace(/[gy]/g, "")).test(text);
|
|
35
37
|
}
|
|
38
|
+
function isBlockedByAllowlist(msg, config) {
|
|
39
|
+
return !msg.fromBot && msg.text.trim().length > 0 && !msg.isDirectMessage && config.allowedChats.length > 0 && !config.allowedChats.includes(msg.chatId);
|
|
40
|
+
}
|
|
36
41
|
function decideChannelAction(msg, config) {
|
|
37
42
|
if (msg.fromBot || !msg.text.trim()) return "ignore";
|
|
38
43
|
if (!msg.isDirectMessage) {
|
|
39
|
-
if (
|
|
40
|
-
return "ignore";
|
|
41
|
-
}
|
|
44
|
+
if (isBlockedByAllowlist(msg, config)) return "ignore";
|
|
42
45
|
if (config.muteRegex && matches(config.muteRegex, msg.text)) return "mute";
|
|
43
46
|
if (config.unmuteRegex && matches(config.unmuteRegex, msg.text)) return "unmute";
|
|
44
47
|
if (config.mutedChats.has(msg.chatId)) return "ignore";
|
|
@@ -75,13 +78,252 @@ function isEffectivelyFromSelf(msg, registry) {
|
|
|
75
78
|
return msg.fromBot || isFromAnySession(msg.senderId, registry);
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
// src/core/guardrails.ts
|
|
82
|
+
function detectLeakage(text) {
|
|
83
|
+
const flags = [
|
|
84
|
+
/system prompt/i,
|
|
85
|
+
/hidden instruction/i,
|
|
86
|
+
/here (are|is) (my|the) rules/i,
|
|
87
|
+
/BEGIN SYSTEM PROMPT/i,
|
|
88
|
+
/tool instructions/i
|
|
89
|
+
];
|
|
90
|
+
return flags.some((rx) => rx.test(text));
|
|
91
|
+
}
|
|
92
|
+
function scrubOutput(text) {
|
|
93
|
+
return text.replace(/sk-[A-Za-z0-9]{15,}/g, "[REDACTED]").replace(/AIza[0-9A-Za-z_-]{30,}/g, "[REDACTED]");
|
|
94
|
+
}
|
|
95
|
+
function guardOutput(text) {
|
|
96
|
+
if (detectLeakage(text)) {
|
|
97
|
+
return "Sorry, I can't share internal instructions. How else can I help?";
|
|
98
|
+
}
|
|
99
|
+
return scrubOutput(text);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/core/llm.ts
|
|
103
|
+
var DEFAULT_MODEL = "gpt-4o";
|
|
104
|
+
async function completeOnce({
|
|
105
|
+
client,
|
|
106
|
+
system,
|
|
107
|
+
messages,
|
|
108
|
+
temperature = 0.2,
|
|
109
|
+
model = DEFAULT_MODEL
|
|
110
|
+
}) {
|
|
111
|
+
const res = await client.chat.completions.create({
|
|
112
|
+
model,
|
|
113
|
+
temperature,
|
|
114
|
+
messages: [{ role: "system", content: system }, ...messages]
|
|
115
|
+
});
|
|
116
|
+
const text = res.choices[0]?.message?.content ?? "";
|
|
117
|
+
return {
|
|
118
|
+
content: guardOutput(text),
|
|
119
|
+
usage: res.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/core/answer.ts
|
|
124
|
+
var noUsage = () => ({ prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 });
|
|
125
|
+
async function runAnswerFn(answerFn, { system, messages, mode, sender, conversationId }) {
|
|
126
|
+
const result = await answerFn({ system, messages, mode, sender, conversationId });
|
|
127
|
+
const raw = typeof result === "string" ? result : result?.content;
|
|
128
|
+
const usage = typeof result === "string" ? void 0 : result?.usage;
|
|
129
|
+
return {
|
|
130
|
+
content: guardOutput(typeof raw === "string" ? raw : ""),
|
|
131
|
+
usage: usage ?? noUsage()
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async function answerOnce({
|
|
135
|
+
answerFn,
|
|
136
|
+
client,
|
|
137
|
+
system,
|
|
138
|
+
messages,
|
|
139
|
+
mode,
|
|
140
|
+
sender,
|
|
141
|
+
conversationId,
|
|
142
|
+
temperature,
|
|
143
|
+
model
|
|
144
|
+
}) {
|
|
145
|
+
if (answerFn) {
|
|
146
|
+
return runAnswerFn(answerFn, { system, messages, mode, sender, conversationId });
|
|
147
|
+
}
|
|
148
|
+
return completeOnce({ client, system, messages, temperature, model });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/core/buckets.ts
|
|
152
|
+
function defaultBuckets(mode) {
|
|
153
|
+
return ["base", mode];
|
|
154
|
+
}
|
|
155
|
+
function isIdentified(sender) {
|
|
156
|
+
return typeof sender === "string" && sender.trim().length > 0;
|
|
157
|
+
}
|
|
158
|
+
function normalize(buckets) {
|
|
159
|
+
const out = [];
|
|
160
|
+
for (const raw of buckets) {
|
|
161
|
+
if (typeof raw !== "string") continue;
|
|
162
|
+
const bucket = raw.trim();
|
|
163
|
+
if (bucket && !out.includes(bucket)) out.push(bucket);
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
async function resolveBuckets({
|
|
168
|
+
mode,
|
|
169
|
+
sender,
|
|
170
|
+
bucketsFor
|
|
171
|
+
}) {
|
|
172
|
+
if (!bucketsFor) return void 0;
|
|
173
|
+
const identified = isIdentified(sender);
|
|
174
|
+
const requested = await bucketsFor(identified ? { mode, sender } : { mode });
|
|
175
|
+
if (!Array.isArray(requested)) return void 0;
|
|
176
|
+
const cleaned = normalize(requested);
|
|
177
|
+
if (identified) return cleaned;
|
|
178
|
+
const ceiling = new Set(defaultBuckets(mode));
|
|
179
|
+
return cleaned.filter((bucket) => ceiling.has(bucket));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/core/messages.ts
|
|
183
|
+
function lastUserMessage(messages) {
|
|
184
|
+
return [...messages].reverse().find((m) => m.role === "user");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/core/pipeline.ts
|
|
188
|
+
var MODE_SETTINGS = {
|
|
189
|
+
public: { topK: 6, label: "Context" },
|
|
190
|
+
private: { topK: 8, label: "Internal Context" }
|
|
191
|
+
};
|
|
192
|
+
async function prepareChat({
|
|
193
|
+
store,
|
|
194
|
+
prompts,
|
|
195
|
+
mode,
|
|
196
|
+
messages,
|
|
197
|
+
personaLayer,
|
|
198
|
+
channelHint,
|
|
199
|
+
buckets
|
|
200
|
+
}) {
|
|
201
|
+
const lastUserMsg = lastUserMessage(messages);
|
|
202
|
+
if (!lastUserMsg) {
|
|
203
|
+
throw new Error("no user message found in conversation");
|
|
204
|
+
}
|
|
205
|
+
const { topK, label } = MODE_SETTINGS[mode];
|
|
206
|
+
const ctx = await store.query(lastUserMsg.content, topK, buckets ?? defaultBuckets(mode));
|
|
207
|
+
const persona = personaLayer?.trim() || (mode === "private" ? prompts.privatePersona : prompts.publicPersona);
|
|
208
|
+
const hint = channelHint?.trim();
|
|
209
|
+
const system = [
|
|
210
|
+
prompts.baseSystemRules,
|
|
211
|
+
persona,
|
|
212
|
+
...hint ? [hint] : [],
|
|
213
|
+
`${label}:
|
|
214
|
+
${ctx.join("\n\n")}`
|
|
215
|
+
].join("\n\n");
|
|
216
|
+
return { system, messages, context: ctx };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/channels/pipeline.ts
|
|
220
|
+
var DEFAULT_HISTORY_LIMIT = 20;
|
|
221
|
+
var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
|
|
222
|
+
var DEFAULT_DM_RATE_LIMIT = { max: 20, windowMs: 60 * 60 * 1e3 };
|
|
223
|
+
async function resolvePersonaLayer(config, ctx) {
|
|
224
|
+
if (!config.personaResolver) return void 0;
|
|
225
|
+
try {
|
|
226
|
+
return await config.personaResolver(ctx);
|
|
227
|
+
} catch {
|
|
228
|
+
return void 0;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function createInboundPipeline(deps, config) {
|
|
232
|
+
const mutedChats = /* @__PURE__ */ new Set();
|
|
233
|
+
const now = config.now ?? Date.now;
|
|
234
|
+
const group = config.groupRateLimit ?? DEFAULT_GROUP_RATE_LIMIT;
|
|
235
|
+
const dm = config.dmRateLimit ?? DEFAULT_DM_RATE_LIMIT;
|
|
236
|
+
const underGroupRateLimit = createSlidingWindowRateLimiter(group.max, group.windowMs, now);
|
|
237
|
+
const underDmRateLimit = createSlidingWindowRateLimiter(dm.max, dm.windowMs, now);
|
|
238
|
+
const mode = "public";
|
|
239
|
+
return async function handleInboundMessage(msg, turn) {
|
|
240
|
+
const allowedChats = config.allowedChats ?? [];
|
|
241
|
+
const action = decideChannelAction(msg, {
|
|
242
|
+
allowedChats,
|
|
243
|
+
mutedChats,
|
|
244
|
+
muteRegex: config.muteRegex,
|
|
245
|
+
unmuteRegex: config.unmuteRegex
|
|
246
|
+
});
|
|
247
|
+
if (action === "mute") {
|
|
248
|
+
mutedChats.add(msg.chatId);
|
|
249
|
+
if (config.muteReply) await turn.reply.sendGateReply(msg.chatId, config.muteReply);
|
|
250
|
+
return { action: "mute" };
|
|
251
|
+
}
|
|
252
|
+
if (action === "unmute") {
|
|
253
|
+
mutedChats.delete(msg.chatId);
|
|
254
|
+
if (config.unmuteReply) await turn.reply.sendGateReply(msg.chatId, config.unmuteReply);
|
|
255
|
+
return { action: "unmute" };
|
|
256
|
+
}
|
|
257
|
+
if (action === "ignore") {
|
|
258
|
+
return { action: "ignore" };
|
|
259
|
+
}
|
|
260
|
+
if (!underReplyRateLimit(msg, { dm: underDmRateLimit, group: underGroupRateLimit })) {
|
|
261
|
+
return { action: "rate-limited" };
|
|
262
|
+
}
|
|
263
|
+
const sender = typeof turn.sender === "function" ? await turn.sender() : turn.sender ?? msg.senderId;
|
|
264
|
+
const conversationId = turn.conversationId ?? msg.chatId;
|
|
265
|
+
if (turn.intercept && await turn.intercept(sender)) {
|
|
266
|
+
return { action: "intercepted" };
|
|
267
|
+
}
|
|
268
|
+
const personaLayer = await resolvePersonaLayer(config, { sender, text: msg.text });
|
|
269
|
+
const buckets = await resolveBuckets({ mode, sender, bucketsFor: config.bucketsFor });
|
|
270
|
+
const priorTurns = config.history ? await config.history.store.load(
|
|
271
|
+
conversationId,
|
|
272
|
+
config.history.limit ?? DEFAULT_HISTORY_LIMIT
|
|
273
|
+
) : [];
|
|
274
|
+
const messages = [...priorTurns, { role: "user", content: msg.text }];
|
|
275
|
+
await config.history?.store.append(conversationId, { role: "user", content: msg.text });
|
|
276
|
+
const { system } = await prepareChat({
|
|
277
|
+
store: deps.store,
|
|
278
|
+
prompts: deps.prompts,
|
|
279
|
+
mode,
|
|
280
|
+
messages,
|
|
281
|
+
personaLayer,
|
|
282
|
+
channelHint: config.channelHint,
|
|
283
|
+
buckets
|
|
284
|
+
});
|
|
285
|
+
const { content } = await answerOnce({
|
|
286
|
+
answerFn: config.answerFn,
|
|
287
|
+
client: deps.client,
|
|
288
|
+
system,
|
|
289
|
+
messages,
|
|
290
|
+
mode,
|
|
291
|
+
sender,
|
|
292
|
+
conversationId,
|
|
293
|
+
model: config.model
|
|
294
|
+
});
|
|
295
|
+
if (!content) {
|
|
296
|
+
return { action: "ignore" };
|
|
297
|
+
}
|
|
298
|
+
await config.history?.store.append(conversationId, { role: "assistant", content });
|
|
299
|
+
await turn.reply.sendAnswer(msg.chatId, content);
|
|
300
|
+
return { action: "reply", content };
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/core/logger.ts
|
|
305
|
+
var LEVEL_ORDER = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
306
|
+
function createConsoleLogger(level = "info") {
|
|
307
|
+
const threshold = LEVEL_ORDER[level];
|
|
308
|
+
const write = (at) => (...args) => {
|
|
309
|
+
if (LEVEL_ORDER[at] < threshold) return;
|
|
310
|
+
console.error(`[${at}]`, ...args);
|
|
311
|
+
};
|
|
312
|
+
return {
|
|
313
|
+
debug: write("debug"),
|
|
314
|
+
info: write("info"),
|
|
315
|
+
warn: write("warn"),
|
|
316
|
+
error: write("error")
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
78
320
|
// src/channels/senders.ts
|
|
79
|
-
function createSenderRegistry() {
|
|
321
|
+
function createSenderRegistry(logger = createConsoleLogger()) {
|
|
80
322
|
const senders = /* @__PURE__ */ new Map();
|
|
81
323
|
return {
|
|
82
324
|
register(name, sender) {
|
|
83
325
|
if (senders.has(name)) {
|
|
84
|
-
|
|
326
|
+
logger.warn(`Channel sender "${name}" is already registered; overwriting.`);
|
|
85
327
|
}
|
|
86
328
|
senders.set(name, sender);
|
|
87
329
|
},
|
|
@@ -98,7 +340,7 @@ function createSenderRegistry() {
|
|
|
98
340
|
await sender.sendText(chatId, text);
|
|
99
341
|
return true;
|
|
100
342
|
} catch (error) {
|
|
101
|
-
|
|
343
|
+
logger.warn(`Channel sender "${name}" sendText failed:`, error);
|
|
102
344
|
return false;
|
|
103
345
|
}
|
|
104
346
|
},
|
|
@@ -109,7 +351,7 @@ function createSenderRegistry() {
|
|
|
109
351
|
await sender.sendVoice(chatId, payload);
|
|
110
352
|
return true;
|
|
111
353
|
} catch (error) {
|
|
112
|
-
|
|
354
|
+
logger.warn(`Channel sender "${name}" sendVoice failed:`, error);
|
|
113
355
|
return false;
|
|
114
356
|
}
|
|
115
357
|
},
|
|
@@ -120,7 +362,7 @@ function createSenderRegistry() {
|
|
|
120
362
|
await sender.sendMedia(chatId, payload);
|
|
121
363
|
return true;
|
|
122
364
|
} catch (error) {
|
|
123
|
-
|
|
365
|
+
logger.warn(`Channel sender "${name}" sendMedia failed:`, error);
|
|
124
366
|
return false;
|
|
125
367
|
}
|
|
126
368
|
}
|
|
@@ -128,9 +370,11 @@ function createSenderRegistry() {
|
|
|
128
370
|
}
|
|
129
371
|
// Annotate the CommonJS export names for ESM import in node:
|
|
130
372
|
0 && (module.exports = {
|
|
373
|
+
createInboundPipeline,
|
|
131
374
|
createSenderRegistry,
|
|
132
375
|
createSlidingWindowRateLimiter,
|
|
133
376
|
decideChannelAction,
|
|
377
|
+
isBlockedByAllowlist,
|
|
134
378
|
isEffectivelyFromSelf,
|
|
135
379
|
isFromAnySession,
|
|
136
380
|
underReplyRateLimit
|