@0xmaxma/claude-gateway 1.8.9 → 1.8.11
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 +46 -6
- package/config.template.json +2 -1
- package/dist/agent/runner.d.ts +86 -1
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +396 -9
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +1028 -6
- package/dist/api/router.js.map +1 -1
- package/dist/api/share-router.d.ts.map +1 -1
- package/dist/api/share-router.js +24 -0
- package/dist/api/share-router.js.map +1 -1
- package/dist/api/webhooks-router.d.ts +2 -0
- package/dist/api/webhooks-router.d.ts.map +1 -1
- package/dist/api/webhooks-router.js +2 -0
- package/dist/api/webhooks-router.js.map +1 -1
- package/dist/api/whatsapp-access.d.ts +123 -0
- package/dist/api/whatsapp-access.d.ts.map +1 -0
- package/dist/api/whatsapp-access.js +135 -0
- package/dist/api/whatsapp-access.js.map +1 -0
- package/dist/api/whatsapp-cloud-access.d.ts +48 -0
- package/dist/api/whatsapp-cloud-access.d.ts.map +1 -0
- package/dist/api/whatsapp-cloud-access.js +58 -0
- package/dist/api/whatsapp-cloud-access.js.map +1 -0
- package/dist/api/whatsapp-cloud-client.d.ts +143 -0
- package/dist/api/whatsapp-cloud-client.d.ts.map +1 -0
- package/dist/api/whatsapp-cloud-client.js +298 -0
- package/dist/api/whatsapp-cloud-client.js.map +1 -0
- package/dist/api/whatsapp-cloud-webhook-router.d.ts +132 -0
- package/dist/api/whatsapp-cloud-webhook-router.d.ts.map +1 -0
- package/dist/api/whatsapp-cloud-webhook-router.js +494 -0
- package/dist/api/whatsapp-cloud-webhook-router.js.map +1 -0
- package/dist/cli/args.d.ts +34 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +55 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/commands/app.d.ts +28 -0
- package/dist/cli/commands/app.d.ts.map +1 -0
- package/dist/cli/commands/app.js +411 -0
- package/dist/cli/commands/app.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +77 -16
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/service.d.ts.map +1 -1
- package/dist/cli/commands/service.js +373 -56
- package/dist/cli/commands/service.js.map +1 -1
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +1 -17
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/http-client.d.ts +8 -0
- package/dist/cli/http-client.d.ts.map +1 -1
- package/dist/cli/http-client.js +5 -2
- package/dist/cli/http-client.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +16 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/prompt.d.ts +17 -2
- package/dist/cli/prompt.d.ts.map +1 -1
- package/dist/cli/prompt.js +33 -2
- package/dist/cli/prompt.js.map +1 -1
- package/dist/config/loader.d.ts +8 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +22 -4
- package/dist/config/loader.js.map +1 -1
- package/dist/config/watcher.d.ts.map +1 -1
- package/dist/config/watcher.js +12 -0
- package/dist/config/watcher.js.map +1 -1
- package/dist/config/whatsapp-accounts.d.ts +77 -0
- package/dist/config/whatsapp-accounts.d.ts.map +1 -0
- package/dist/config/whatsapp-accounts.js +218 -0
- package/dist/config/whatsapp-accounts.js.map +1 -0
- package/dist/history/db.d.ts.map +1 -1
- package/dist/history/db.js +43 -11
- package/dist/history/db.js.map +1 -1
- package/dist/history/types.d.ts +18 -1
- package/dist/history/types.d.ts.map +1 -1
- package/dist/history/types.js +1 -1
- package/dist/history/types.js.map +1 -1
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -1
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +37 -0
- package/dist/session/process.js.map +1 -1
- package/dist/session/store.d.ts +3 -2
- package/dist/session/store.d.ts.map +1 -1
- package/dist/session/store.js +1 -1
- package/dist/session/store.js.map +1 -1
- package/dist/share/session-video-catalog.d.ts +19 -0
- package/dist/share/session-video-catalog.d.ts.map +1 -0
- package/dist/share/session-video-catalog.js +119 -0
- package/dist/share/session-video-catalog.js.map +1 -0
- package/dist/shared/image-optimize.d.ts +32 -0
- package/dist/shared/image-optimize.d.ts.map +1 -0
- package/dist/shared/image-optimize.js +197 -0
- package/dist/shared/image-optimize.js.map +1 -0
- package/dist/shared/image-sniff.d.ts +9 -0
- package/dist/shared/image-sniff.d.ts.map +1 -1
- package/dist/shared/image-sniff.js +13 -0
- package/dist/shared/image-sniff.js.map +1 -1
- package/dist/shared/text-chunk.d.ts +37 -0
- package/dist/shared/text-chunk.d.ts.map +1 -0
- package/dist/shared/text-chunk.js +105 -0
- package/dist/shared/text-chunk.js.map +1 -0
- package/dist/shared/whatsapp-ack.d.ts +13 -0
- package/dist/shared/whatsapp-ack.d.ts.map +1 -0
- package/dist/shared/whatsapp-ack.js +16 -0
- package/dist/shared/whatsapp-ack.js.map +1 -0
- package/dist/types.d.ts +183 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/whatsapp/manager.d.ts +173 -0
- package/dist/whatsapp/manager.d.ts.map +1 -0
- package/dist/whatsapp/manager.js +838 -0
- package/dist/whatsapp/manager.js.map +1 -0
- package/mcp/server.ts +4 -0
- package/mcp/tools/image/module.ts +6 -1
- package/mcp/tools/telegram/receiver-server.ts +15 -8
- package/mcp/tools/telegram/reply-attachment.ts +81 -0
- package/mcp/tools/video/module.ts +6 -1
- package/mcp/tools/whatsapp/module.ts +162 -0
- package/mcp/tools/whatsapp-cloud/module.ts +536 -0
- package/package.json +7 -2
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.optimizeImage = optimizeImage;
|
|
37
|
+
exports.optimizeImageFile = optimizeImageFile;
|
|
38
|
+
/**
|
|
39
|
+
* Best-effort image downscaler for OUTBOUND channel attachments.
|
|
40
|
+
*
|
|
41
|
+
* Both WhatsApp channels used to hard-fail an oversized image: the Baileys
|
|
42
|
+
* manager threw `image exceeds N byte cap` and the Cloud MCP tool let Meta
|
|
43
|
+
* reject the upload. From the agent's point of view that is the worst possible
|
|
44
|
+
* outcome — it generated a perfectly good chart/screenshot and the reply just
|
|
45
|
+
* disappears. Shrinking the picture is almost always what the human wanted, so
|
|
46
|
+
* the send paths now run an over-cap image through `optimizeImage` first.
|
|
47
|
+
*
|
|
48
|
+
* Deliberately NOT applied on a document/`asDocument` send: sending as a
|
|
49
|
+
* document is how the agent asks for the EXACT bytes to arrive (WhatsApp
|
|
50
|
+
* re-compresses anything sent as a photo — see WhatsAppManager.sendMessage's
|
|
51
|
+
* forceDocument comment), so recompressing there would defeat the only reason
|
|
52
|
+
* to pick that mode.
|
|
53
|
+
*
|
|
54
|
+
* `sharp` is loaded lazily via dynamic import for two reasons: it is a native
|
|
55
|
+
* module, so a static top-level import would make EVERY test that transitively
|
|
56
|
+
* reaches the send paths pay its load cost (and fail outright on a platform
|
|
57
|
+
* without a prebuilt binary), and a lazy load keeps this module importable —
|
|
58
|
+
* and therefore mockable — even when sharp itself is unavailable.
|
|
59
|
+
*/
|
|
60
|
+
const fs = __importStar(require("fs"));
|
|
61
|
+
const os = __importStar(require("os"));
|
|
62
|
+
const path = __importStar(require("path"));
|
|
63
|
+
/**
|
|
64
|
+
* Quality sweep, tried at the ORIGINAL dimensions first. Most over-cap images
|
|
65
|
+
* from an agent are large PNG screenshots whose size is encoding, not pixels —
|
|
66
|
+
* a JPEG re-encode alone usually gets them under any sane cap while keeping
|
|
67
|
+
* every pixel of detail, so dimensions are only sacrificed once quality alone
|
|
68
|
+
* has failed.
|
|
69
|
+
*/
|
|
70
|
+
const QUALITY_STEPS = [80, 60, 40];
|
|
71
|
+
/**
|
|
72
|
+
* Dimension sweep, applied only after the quality sweep failed, each at the
|
|
73
|
+
* lowest quality above. Scales (not fixed widths) so the aspect ratio and the
|
|
74
|
+
* relationship to the source resolution are both preserved.
|
|
75
|
+
*/
|
|
76
|
+
const SCALE_STEPS = [0.75, 0.5, 0.35, 0.25];
|
|
77
|
+
/** The most aggressive quality, reused for every scaled attempt. */
|
|
78
|
+
const MIN_QUALITY = QUALITY_STEPS[QUALITY_STEPS.length - 1];
|
|
79
|
+
let cachedSharp = null;
|
|
80
|
+
/**
|
|
81
|
+
* Resolve sharp's callable export across the CJS/ESM interop shapes it can
|
|
82
|
+
* arrive in (a bare function under plain `require`, or under `.default` once
|
|
83
|
+
* TypeScript's esModuleInterop wraps it). Throws if neither is present — every
|
|
84
|
+
* caller here treats a throw as "leave the image alone".
|
|
85
|
+
*/
|
|
86
|
+
async function loadSharp() {
|
|
87
|
+
if (cachedSharp)
|
|
88
|
+
return cachedSharp;
|
|
89
|
+
const mod = await Promise.resolve().then(() => __importStar(require('sharp')));
|
|
90
|
+
const factory = typeof mod === 'function'
|
|
91
|
+
? mod
|
|
92
|
+
: (mod?.default);
|
|
93
|
+
if (typeof factory !== 'function')
|
|
94
|
+
throw new Error('sharp: no callable export');
|
|
95
|
+
cachedSharp = factory;
|
|
96
|
+
return factory;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Shrink `buffer` until it fits `maxBytes`, or as close as this can get.
|
|
100
|
+
*
|
|
101
|
+
* Contract — never throws, always returns something sendable:
|
|
102
|
+
* - already at/under the cap (or an empty/`maxBytes <= 0` input) → the
|
|
103
|
+
* ORIGINAL buffer, byte for byte, with sharp never loaded at all;
|
|
104
|
+
* - a sweep step lands under the cap → that step's output;
|
|
105
|
+
* - every step still exceeds the cap → the SMALLEST attempt seen (which may
|
|
106
|
+
* still be over — the caller decides what to do with a best effort that was
|
|
107
|
+
* not enough), or the original if no attempt managed to beat it;
|
|
108
|
+
* - sharp missing, or the bytes are not a decodable image → the original.
|
|
109
|
+
*
|
|
110
|
+
* Output is JPEG. That is a deliberate narrowing rather than format
|
|
111
|
+
* preservation: JPEG is the only format here with a quality knob that reliably
|
|
112
|
+
* trades detail for bytes, and both WhatsApp channels re-encode a photo
|
|
113
|
+
* on their own anyway. Transparency is flattened onto white first, because
|
|
114
|
+
* JPEG has no alpha channel and sharp's default would composite onto black.
|
|
115
|
+
*/
|
|
116
|
+
async function optimizeImage(buffer, maxBytes) {
|
|
117
|
+
if (!buffer || buffer.length === 0)
|
|
118
|
+
return buffer;
|
|
119
|
+
if (!Number.isFinite(maxBytes) || maxBytes <= 0)
|
|
120
|
+
return buffer;
|
|
121
|
+
if (buffer.length <= maxBytes)
|
|
122
|
+
return buffer;
|
|
123
|
+
let best = buffer;
|
|
124
|
+
try {
|
|
125
|
+
const sharp = await loadSharp();
|
|
126
|
+
// `rotate()` with no argument applies the EXIF orientation and drops the
|
|
127
|
+
// tag — without it a re-encoded phone photo can come out sideways.
|
|
128
|
+
const { width } = await sharp(buffer).metadata();
|
|
129
|
+
for (const quality of QUALITY_STEPS) {
|
|
130
|
+
const out = await sharp(buffer)
|
|
131
|
+
.rotate()
|
|
132
|
+
.flatten({ background: '#ffffff' })
|
|
133
|
+
.jpeg({ quality, mozjpeg: true })
|
|
134
|
+
.toBuffer();
|
|
135
|
+
if (out.length < best.length)
|
|
136
|
+
best = out;
|
|
137
|
+
if (out.length <= maxBytes)
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
// Quality alone was not enough — start giving up pixels too. Skipped
|
|
141
|
+
// entirely when the source has no readable width (sharp could not tell us
|
|
142
|
+
// the dimensions), since there is nothing sane to scale from.
|
|
143
|
+
if (width && width > 0) {
|
|
144
|
+
for (const scale of SCALE_STEPS) {
|
|
145
|
+
const target = Math.max(1, Math.round(width * scale));
|
|
146
|
+
const out = await sharp(buffer)
|
|
147
|
+
.rotate()
|
|
148
|
+
.resize({ width: target, withoutEnlargement: true })
|
|
149
|
+
.flatten({ background: '#ffffff' })
|
|
150
|
+
.jpeg({ quality: MIN_QUALITY, mozjpeg: true })
|
|
151
|
+
.toBuffer();
|
|
152
|
+
if (out.length < best.length)
|
|
153
|
+
best = out;
|
|
154
|
+
if (out.length <= maxBytes)
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// sharp unavailable, or these bytes are not a decodable image. Best-effort
|
|
161
|
+
// by contract: hand back whatever is smallest so the caller's existing
|
|
162
|
+
// over-cap handling still runs, rather than failing the whole reply here.
|
|
163
|
+
}
|
|
164
|
+
return best;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* File-level wrapper for the two send paths: shrink `srcPath` under `maxBytes`
|
|
168
|
+
* and return a path to the result.
|
|
169
|
+
*
|
|
170
|
+
* Returns `srcPath` UNCHANGED whenever nothing was gained — already under the
|
|
171
|
+
* cap, unreadable, or `optimizeImage` handed back the original — so a caller
|
|
172
|
+
* can compare identity to know whether a temp file was created. The temp file
|
|
173
|
+
* lands in the OS temp dir with a `.jpg` extension matching what
|
|
174
|
+
* `optimizeImage` actually produces, alongside the inbound media temp files
|
|
175
|
+
* the channel routers already write there.
|
|
176
|
+
*/
|
|
177
|
+
async function optimizeImageFile(srcPath, maxBytes) {
|
|
178
|
+
let buf;
|
|
179
|
+
try {
|
|
180
|
+
buf = fs.readFileSync(srcPath);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return srcPath;
|
|
184
|
+
}
|
|
185
|
+
const out = await optimizeImage(buf, maxBytes);
|
|
186
|
+
if (out === buf || out.length >= buf.length)
|
|
187
|
+
return srcPath;
|
|
188
|
+
try {
|
|
189
|
+
const dest = path.join(os.tmpdir(), `gw-optimized-${Date.now()}-${path.basename(srcPath, path.extname(srcPath)).slice(0, 64)}.jpg`);
|
|
190
|
+
fs.writeFileSync(dest, out);
|
|
191
|
+
return dest;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return srcPath;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=image-optimize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-optimize.js","sourceRoot":"","sources":["../../src/shared/image-optimize.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,4 +12,13 @@
|
|
|
12
12
|
* not send one at all.
|
|
13
13
|
*/
|
|
14
14
|
export declare function sniffImageExt(buf: Buffer): string;
|
|
15
|
+
/**
|
|
16
|
+
* Reduce a sender-controlled id (WhatsApp message id, etc.) to a safe temp
|
|
17
|
+
* filename component. Callers build paths like
|
|
18
|
+
* `path.join(os.tmpdir(), \`prefix-${id}.ext\`)` from a value the OTHER
|
|
19
|
+
* party's client sets, not us — an id containing `../` segments would
|
|
20
|
+
* otherwise resolve outside the intended directory. Falls back to the
|
|
21
|
+
* current timestamp when the id is empty/missing or sanitizes to nothing.
|
|
22
|
+
*/
|
|
23
|
+
export declare function sanitizeFilenameId(id: string | undefined | null): string;
|
|
15
24
|
//# sourceMappingURL=image-sniff.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-sniff.d.ts","sourceRoot":"","sources":["../../src/shared/image-sniff.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CASjD"}
|
|
1
|
+
{"version":3,"file":"image-sniff.d.ts","sourceRoot":"","sources":["../../src/shared/image-sniff.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CASjD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAGxE"}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.sniffImageExt = sniffImageExt;
|
|
10
|
+
exports.sanitizeFilenameId = sanitizeFilenameId;
|
|
10
11
|
/**
|
|
11
12
|
* Pick a file extension from an image's magic bytes; default jpg.
|
|
12
13
|
*
|
|
@@ -26,4 +27,16 @@ function sniffImageExt(buf) {
|
|
|
26
27
|
return 'webp';
|
|
27
28
|
return 'jpg';
|
|
28
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Reduce a sender-controlled id (WhatsApp message id, etc.) to a safe temp
|
|
32
|
+
* filename component. Callers build paths like
|
|
33
|
+
* `path.join(os.tmpdir(), \`prefix-${id}.ext\`)` from a value the OTHER
|
|
34
|
+
* party's client sets, not us — an id containing `../` segments would
|
|
35
|
+
* otherwise resolve outside the intended directory. Falls back to the
|
|
36
|
+
* current timestamp when the id is empty/missing or sanitizes to nothing.
|
|
37
|
+
*/
|
|
38
|
+
function sanitizeFilenameId(id) {
|
|
39
|
+
const cleaned = (id ?? '').replace(/[^A-Za-z0-9_-]/g, '_').slice(0, 64);
|
|
40
|
+
return cleaned || String(Date.now());
|
|
41
|
+
}
|
|
29
42
|
//# sourceMappingURL=image-sniff.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-sniff.js","sourceRoot":"","sources":["../../src/shared/image-sniff.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AASH,sCASC;
|
|
1
|
+
{"version":3,"file":"image-sniff.js","sourceRoot":"","sources":["../../src/shared/image-sniff.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AASH,sCASC;AAUD,gDAGC;AA7BD;;;;;;GAMG;AACH,SAAgB,aAAa,CAAC,GAAW;IACvC,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC3F,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9G,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC3F,IACE,GAAG,CAAC,MAAM,IAAI,EAAE;QAChB,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC,KAAK,IAAI;QAC1E,OAAO,MAAM,CAAC;IAChB,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,kBAAkB,CAAC,EAA6B;IAC9D,MAAM,OAAO,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACxE,OAAO,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel-agnostic text chunker.
|
|
3
|
+
*
|
|
4
|
+
* Modeled on `src/agent/line-pure.ts`'s `splitForLine` (same paragraph →
|
|
5
|
+
* newline → space cut-point preference) but WITHOUT LINE's two
|
|
6
|
+
* platform-specific behaviours:
|
|
7
|
+
*
|
|
8
|
+
* - No 5-bubble cap. LINE can only carry 5 message objects per reply/push
|
|
9
|
+
* call, so `splitForLine` stops after 5 chunks and truncates. WhatsApp has
|
|
10
|
+
* no such per-request limit (each chunk is its own API call / socket send),
|
|
11
|
+
* so this keeps splitting until the whole text is emitted.
|
|
12
|
+
* - No ellipsis/truncation. Nothing is ever dropped — the concatenation of
|
|
13
|
+
* the returned chunks is the input, modulo whitespace collapsed exactly at
|
|
14
|
+
* the cut points.
|
|
15
|
+
*
|
|
16
|
+
* Used by both WhatsApp channels (Cloud API's 4096-char text limit, Baileys'
|
|
17
|
+
* conservative 4000) and safe for any other channel that needs plain splitting.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Split `text` into chunks of at most `maxChars` characters.
|
|
21
|
+
*
|
|
22
|
+
* `mode`:
|
|
23
|
+
* - `'newline'` (default) — prefer to break on the last paragraph break,
|
|
24
|
+
* then the last newline, then the last space that falls inside the budget,
|
|
25
|
+
* so chunks end at a natural boundary. Whitespace at a cut point is
|
|
26
|
+
* trimmed (it would otherwise show up as a leading blank line on the next
|
|
27
|
+
* message bubble). Falls back to a hard cut when no boundary sits late
|
|
28
|
+
* enough in the budget to be worth using.
|
|
29
|
+
* - `'length'` — hard cut at exactly `maxChars`, no boundary search and no
|
|
30
|
+
* whitespace trimming. For callers whose limit is a strict byte/char
|
|
31
|
+
* budget where losing a space would matter.
|
|
32
|
+
*
|
|
33
|
+
* Returns `[]` for empty input and `[text]` when it already fits. A hard cut
|
|
34
|
+
* (either mode) never splits a surrogate pair — see avoidSurrogateSplit.
|
|
35
|
+
*/
|
|
36
|
+
export declare function chunkText(text: string, maxChars: number, mode?: 'length' | 'newline'): string[];
|
|
37
|
+
//# sourceMappingURL=text-chunk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-chunk.d.ts","sourceRoot":"","sources":["../../src/shared/text-chunk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAqBH;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,QAAQ,GAAG,SAAqB,GACrC,MAAM,EAAE,CA0CV"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Channel-agnostic text chunker.
|
|
4
|
+
*
|
|
5
|
+
* Modeled on `src/agent/line-pure.ts`'s `splitForLine` (same paragraph →
|
|
6
|
+
* newline → space cut-point preference) but WITHOUT LINE's two
|
|
7
|
+
* platform-specific behaviours:
|
|
8
|
+
*
|
|
9
|
+
* - No 5-bubble cap. LINE can only carry 5 message objects per reply/push
|
|
10
|
+
* call, so `splitForLine` stops after 5 chunks and truncates. WhatsApp has
|
|
11
|
+
* no such per-request limit (each chunk is its own API call / socket send),
|
|
12
|
+
* so this keeps splitting until the whole text is emitted.
|
|
13
|
+
* - No ellipsis/truncation. Nothing is ever dropped — the concatenation of
|
|
14
|
+
* the returned chunks is the input, modulo whitespace collapsed exactly at
|
|
15
|
+
* the cut points.
|
|
16
|
+
*
|
|
17
|
+
* Used by both WhatsApp channels (Cloud API's 4096-char text limit, Baileys'
|
|
18
|
+
* conservative 4000) and safe for any other channel that needs plain splitting.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.chunkText = chunkText;
|
|
22
|
+
/**
|
|
23
|
+
* A hard cut at `idx` (a UTF-16 code-unit index) can land between the two
|
|
24
|
+
* code units of a surrogate pair (e.g. most emoji, which sit outside the
|
|
25
|
+
* BMP) — splitting one across two chunks produces an unpaired surrogate in
|
|
26
|
+
* each, which renders as U+FFFD / mojibake on the receiving side. Nudges
|
|
27
|
+
* `idx` back by one so the pair stays together, unless doing so would leave
|
|
28
|
+
* no room in this chunk at all (`idx <= minIdx`), in which case the split is
|
|
29
|
+
* accepted rather than looping forever on an impossibly small budget.
|
|
30
|
+
*/
|
|
31
|
+
function avoidSurrogateSplit(text, idx, minIdx = 0) {
|
|
32
|
+
if (idx <= minIdx || idx >= text.length)
|
|
33
|
+
return idx;
|
|
34
|
+
const before = text.charCodeAt(idx - 1);
|
|
35
|
+
const at = text.charCodeAt(idx);
|
|
36
|
+
const isHighSurrogate = before >= 0xd800 && before <= 0xdbff;
|
|
37
|
+
const isLowSurrogate = at >= 0xdc00 && at <= 0xdfff;
|
|
38
|
+
if (isHighSurrogate && isLowSurrogate && idx - 1 > minIdx)
|
|
39
|
+
return idx - 1;
|
|
40
|
+
return idx;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Split `text` into chunks of at most `maxChars` characters.
|
|
44
|
+
*
|
|
45
|
+
* `mode`:
|
|
46
|
+
* - `'newline'` (default) — prefer to break on the last paragraph break,
|
|
47
|
+
* then the last newline, then the last space that falls inside the budget,
|
|
48
|
+
* so chunks end at a natural boundary. Whitespace at a cut point is
|
|
49
|
+
* trimmed (it would otherwise show up as a leading blank line on the next
|
|
50
|
+
* message bubble). Falls back to a hard cut when no boundary sits late
|
|
51
|
+
* enough in the budget to be worth using.
|
|
52
|
+
* - `'length'` — hard cut at exactly `maxChars`, no boundary search and no
|
|
53
|
+
* whitespace trimming. For callers whose limit is a strict byte/char
|
|
54
|
+
* budget where losing a space would matter.
|
|
55
|
+
*
|
|
56
|
+
* Returns `[]` for empty input and `[text]` when it already fits. A hard cut
|
|
57
|
+
* (either mode) never splits a surrogate pair — see avoidSurrogateSplit.
|
|
58
|
+
*/
|
|
59
|
+
function chunkText(text, maxChars, mode = 'newline') {
|
|
60
|
+
if (!text)
|
|
61
|
+
return [];
|
|
62
|
+
// A non-positive budget can never make progress — treat it as "no split"
|
|
63
|
+
// rather than looping forever.
|
|
64
|
+
if (!Number.isFinite(maxChars) || maxChars < 1)
|
|
65
|
+
return [text];
|
|
66
|
+
if (text.length <= maxChars)
|
|
67
|
+
return [text];
|
|
68
|
+
if (mode === 'length') {
|
|
69
|
+
const chunks = [];
|
|
70
|
+
let i = 0;
|
|
71
|
+
while (i < text.length) {
|
|
72
|
+
const end = avoidSurrogateSplit(text, Math.min(i + maxChars, text.length), i);
|
|
73
|
+
chunks.push(text.slice(i, end));
|
|
74
|
+
i = end;
|
|
75
|
+
}
|
|
76
|
+
return chunks;
|
|
77
|
+
}
|
|
78
|
+
const chunks = [];
|
|
79
|
+
let remaining = text;
|
|
80
|
+
// Only accept a boundary in the LATER half of the budget: an earlier one
|
|
81
|
+
// wastes so much of the chunk that it produces more messages than the text
|
|
82
|
+
// needs (same heuristic splitForLine uses).
|
|
83
|
+
const half = Math.floor(maxChars * 0.5);
|
|
84
|
+
while (remaining) {
|
|
85
|
+
if (remaining.length <= maxChars) {
|
|
86
|
+
chunks.push(remaining);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
let cut = remaining.lastIndexOf('\n\n', maxChars);
|
|
90
|
+
if (cut < half)
|
|
91
|
+
cut = remaining.lastIndexOf('\n', maxChars);
|
|
92
|
+
if (cut < half)
|
|
93
|
+
cut = remaining.lastIndexOf(' ', maxChars);
|
|
94
|
+
if (cut <= 0)
|
|
95
|
+
cut = avoidSurrogateSplit(remaining, maxChars);
|
|
96
|
+
const piece = remaining.slice(0, cut).replace(/\s+$/, '');
|
|
97
|
+
// A cut that trims to nothing (a run of whitespace) would push an empty
|
|
98
|
+
// bubble; skip it, but still advance `remaining` so the loop terminates.
|
|
99
|
+
if (piece)
|
|
100
|
+
chunks.push(piece);
|
|
101
|
+
remaining = remaining.slice(cut).replace(/^\s+/, '');
|
|
102
|
+
}
|
|
103
|
+
return chunks;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=text-chunk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-chunk.js","sourceRoot":"","sources":["../../src/shared/text-chunk.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ack reaction both WhatsApp channels leave on an inbound message while
|
|
3
|
+
* the agent works on it, cleared once the reply lands (Phase 2 of the
|
|
4
|
+
* WhatsApp feature-parity plan, ported from Slack's ack reaction).
|
|
5
|
+
*
|
|
6
|
+
* Lives here rather than in either channel's client so the Baileys manager
|
|
7
|
+
* (src/whatsapp/manager.ts) and the Cloud client (src/api/whatsapp-cloud-client.ts)
|
|
8
|
+
* share ONE value and can never drift into reacting with two different emoji
|
|
9
|
+
* for the same behaviour. ⏳ is the direct equivalent of the
|
|
10
|
+
* `hourglass_flowing_sand` Slack reacts with.
|
|
11
|
+
*/
|
|
12
|
+
export declare const WHATSAPP_ACK_EMOJI = "\u23F3";
|
|
13
|
+
//# sourceMappingURL=whatsapp-ack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whatsapp-ack.d.ts","sourceRoot":"","sources":["../../src/shared/whatsapp-ack.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,WAAM,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WHATSAPP_ACK_EMOJI = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* The ack reaction both WhatsApp channels leave on an inbound message while
|
|
6
|
+
* the agent works on it, cleared once the reply lands (Phase 2 of the
|
|
7
|
+
* WhatsApp feature-parity plan, ported from Slack's ack reaction).
|
|
8
|
+
*
|
|
9
|
+
* Lives here rather than in either channel's client so the Baileys manager
|
|
10
|
+
* (src/whatsapp/manager.ts) and the Cloud client (src/api/whatsapp-cloud-client.ts)
|
|
11
|
+
* share ONE value and can never drift into reacting with two different emoji
|
|
12
|
+
* for the same behaviour. ⏳ is the direct equivalent of the
|
|
13
|
+
* `hourglass_flowing_sand` Slack reacts with.
|
|
14
|
+
*/
|
|
15
|
+
exports.WHATSAPP_ACK_EMOJI = '⏳';
|
|
16
|
+
//# sourceMappingURL=whatsapp-ack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whatsapp-ack.js","sourceRoot":"","sources":["../../src/shared/whatsapp-ack.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;GAUG;AACU,QAAA,kBAAkB,GAAG,GAAG,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -8,6 +8,75 @@ export interface HistoryConfig {
|
|
|
8
8
|
retentionDays?: number;
|
|
9
9
|
maxHistoryMessages?: number;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* One linked WhatsApp number (Baileys device-link) belonging to an agent.
|
|
13
|
+
*
|
|
14
|
+
* Multi-account (Phase 1 of the WhatsApp feature-parity plan): an agent used
|
|
15
|
+
* to have exactly ONE flat `whatsapp` block; it now has an array of these.
|
|
16
|
+
* Every access-control field below is per-account — two numbers on the same
|
|
17
|
+
* agent can run completely different DM/group policies.
|
|
18
|
+
*
|
|
19
|
+
* There is still NO credential field here: the "credential" remains the
|
|
20
|
+
* linked device session on disk. Its location is derived from `id` —
|
|
21
|
+
* `'default'` keeps the historical bare `<workspace>/.whatsapp-state/`
|
|
22
|
+
* directory (so upgrading a live gateway never has to move a linked
|
|
23
|
+
* session), every other account gets `<workspace>/.whatsapp-state/<id>/`.
|
|
24
|
+
*/
|
|
25
|
+
export interface WhatsAppAccountConfig {
|
|
26
|
+
/**
|
|
27
|
+
* Stable slug identifying this account. Doubles as the on-disk state
|
|
28
|
+
* directory suffix and as the `account_id` the `whatsapp_reply` MCP tool
|
|
29
|
+
* passes back, so it must be filesystem-safe and must never be reused for
|
|
30
|
+
* a different number. `'default'` is reserved for the pre-multi-account
|
|
31
|
+
* session (see the class doc above).
|
|
32
|
+
*/
|
|
33
|
+
id: string;
|
|
34
|
+
/** Display name shown in the Settings UI (e.g. "Sales line"). Falls back to `id`. */
|
|
35
|
+
label?: string;
|
|
36
|
+
/**
|
|
37
|
+
* DM access policy (mirrors `slack.dmPolicy`/`line.dmPolicy` exactly).
|
|
38
|
+
* Gates 1:1 conversations (JIDs ending `@s.whatsapp.net`). Necessary
|
|
39
|
+
* even though the number itself is authenticated via QR/pairing-code
|
|
40
|
+
* linking: unlike a fresh bot token, a WhatsApp number is typically the
|
|
41
|
+
* owner's real number that other people already have — without this,
|
|
42
|
+
* anyone who knows the number could reach the agent once linked.
|
|
43
|
+
*/
|
|
44
|
+
dmPolicy?: 'open' | 'allowlist' | 'disabled';
|
|
45
|
+
/** Allowed sender JIDs/numbers (E.164 or bare digits — normalized at the access-gate boundary). */
|
|
46
|
+
dmAllowlist?: string[];
|
|
47
|
+
/**
|
|
48
|
+
* Group access policy (mirrors `slack.groupPolicy`/`line.groupPolicy`).
|
|
49
|
+
* Gates group JIDs (ending `@g.us`) the linked number is a member of.
|
|
50
|
+
*/
|
|
51
|
+
groupPolicy?: 'open' | 'allowlist' | 'disabled';
|
|
52
|
+
/** Allowed group JIDs. */
|
|
53
|
+
groupAllowlist?: string[];
|
|
54
|
+
/**
|
|
55
|
+
* In groups, only respond when the bot is @mentioned (mirrors
|
|
56
|
+
* `slack.requireMention`/`line.requireMention`). Default true. No
|
|
57
|
+
* effect on DMs.
|
|
58
|
+
*/
|
|
59
|
+
requireMention?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Pairing aid for the allowlist (mirrors `slack.pairing`/`line.pairing`
|
|
62
|
+
* exactly). Default true (absent ⇒ on). Only has an effect under
|
|
63
|
+
* `allowlist` (closed-default) for either tier.
|
|
64
|
+
*/
|
|
65
|
+
pairing?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Mark inbound messages as read (blue double-ticks) once they pass the
|
|
68
|
+
* access gate. Default true (absent ⇒ on) — the agent HAS received the
|
|
69
|
+
* message at that point, so the receipt is honest. Set false for a number
|
|
70
|
+
* whose owner would rather not advertise that. Phase 2.
|
|
71
|
+
*/
|
|
72
|
+
sendReadReceipts?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Ack-reaction level (ported from Slack's ack reaction). `'ack'` (default
|
|
75
|
+
* when absent) reacts ⏳ on the inbound message at receipt and clears it
|
|
76
|
+
* once the reply lands; `'off'` skips BOTH the add and the clear. Phase 2.
|
|
77
|
+
*/
|
|
78
|
+
reactionLevel?: 'off' | 'ack';
|
|
79
|
+
}
|
|
11
80
|
export interface AgentConfig {
|
|
12
81
|
id: string;
|
|
13
82
|
description: string;
|
|
@@ -125,6 +194,95 @@ export interface AgentConfig {
|
|
|
125
194
|
*/
|
|
126
195
|
pairing?: boolean;
|
|
127
196
|
};
|
|
197
|
+
/**
|
|
198
|
+
* WhatsApp — via WhatsApp Web device linking (the Baileys library), NOT
|
|
199
|
+
* the official Meta Cloud API. Unlike every other channel, there is NO
|
|
200
|
+
* credential field here: the "credential" is the linked device session
|
|
201
|
+
* itself, persisted on disk at `<workspace>/.whatsapp-state/` (Baileys'
|
|
202
|
+
* `creds.json`, written by `useMultiFileAuthState`), not in this config.
|
|
203
|
+
* Live link status (unlinked/pending-scan/linked/reconnecting) is
|
|
204
|
+
* ephemeral runtime state held by WhatsAppManager, never persisted here.
|
|
205
|
+
*
|
|
206
|
+
* Unofficial/reverse-engineered protocol — using it violates WhatsApp's
|
|
207
|
+
* ToS and carries real account-ban risk (elevated further since GetPod
|
|
208
|
+
* agents run on cloud/datacenter IPs, the exact traffic profile
|
|
209
|
+
* WhatsApp's abuse detection flags). This is a user-accepted trade-off,
|
|
210
|
+
* disclosed prominently in the web UI's connect flow — not enforced or
|
|
211
|
+
* mitigated by this config.
|
|
212
|
+
*/
|
|
213
|
+
whatsapp?: {
|
|
214
|
+
/**
|
|
215
|
+
* The linked numbers for this agent — one entry per WhatsApp account
|
|
216
|
+
* (Phase 1 of the WhatsApp feature-parity plan replaced the single flat
|
|
217
|
+
* block that used to live here with this array). Order is meaningful
|
|
218
|
+
* only for back-compat: `accounts[0]` is what the legacy flat
|
|
219
|
+
* `whatsapp_*` API response fields are derived from.
|
|
220
|
+
*
|
|
221
|
+
* An absent or empty array is treated as `[{ id: 'default' }]` at
|
|
222
|
+
* runtime (see `resolveWhatsAppAccounts` in src/config/whatsapp-accounts.ts),
|
|
223
|
+
* so an agent that has never been configured still gets a linkable
|
|
224
|
+
* 'default' account exactly like it did before multi-account.
|
|
225
|
+
*/
|
|
226
|
+
accounts: WhatsAppAccountConfig[];
|
|
227
|
+
};
|
|
228
|
+
/**
|
|
229
|
+
* WhatsApp — via the official Meta WhatsApp Business Cloud API (webhook +
|
|
230
|
+
* bearer-token REST, NOT the unofficial Baileys device-link bridge — see
|
|
231
|
+
* `whatsapp` above). Real, first-class credentials issued by Meta, so this
|
|
232
|
+
* channel follows the Slack template (webhook-based with real credentials)
|
|
233
|
+
* rather than the Baileys `whatsapp` block's on-disk-session pattern.
|
|
234
|
+
*
|
|
235
|
+
* DM-only: the Cloud API has no group concept (a WhatsApp Business number
|
|
236
|
+
* cannot be added to a group chat the way a personal/linked number can),
|
|
237
|
+
* so there is no groupPolicy/groupAllowlist/requireMention here — every
|
|
238
|
+
* inbound sender is a bare phone-number string, never a JID.
|
|
239
|
+
*/
|
|
240
|
+
whatsapp_cloud?: {
|
|
241
|
+
/** Permanent (or long-lived) access token — Bearer auth for the Graph API. */
|
|
242
|
+
accessToken: string;
|
|
243
|
+
/** The WhatsApp Business phone number id (Meta's numeric id, not the phone number itself). */
|
|
244
|
+
phoneNumberId: string;
|
|
245
|
+
/** App Secret — verifies X-Hub-Signature-256 (HMAC-SHA256 of the raw request body). */
|
|
246
|
+
appSecret: string;
|
|
247
|
+
/** Verify token — compared against `hub.verify_token` on the GET webhook handshake. */
|
|
248
|
+
verifyToken: string;
|
|
249
|
+
/**
|
|
250
|
+
* DM access policy (mirrors `slack.dmPolicy`/`line.dmPolicy` exactly).
|
|
251
|
+
* Gates inbound senders — a bare phone-number string (e.g. "66812345678"),
|
|
252
|
+
* NOT a JID. `dmAllowlist` entries must be bare digits.
|
|
253
|
+
*/
|
|
254
|
+
dmPolicy?: 'open' | 'allowlist' | 'disabled';
|
|
255
|
+
/** Allowed sender phone numbers — bare digits (E.164 without the leading "+"), never JIDs. */
|
|
256
|
+
dmAllowlist?: string[];
|
|
257
|
+
/**
|
|
258
|
+
* Pairing aid for the allowlist (mirrors `slack.pairing`/`line.pairing`
|
|
259
|
+
* exactly). Default true (absent ⇒ on). Only has an effect under
|
|
260
|
+
* `allowlist` (closed-default).
|
|
261
|
+
*/
|
|
262
|
+
pairing?: boolean;
|
|
263
|
+
/**
|
|
264
|
+
* Mark inbound messages as read (blue double-ticks) once they pass the
|
|
265
|
+
* access gate. Default true (absent ⇒ on). Same field/semantics as the
|
|
266
|
+
* per-account Baileys `WhatsAppAccountConfig.sendReadReceipts`. Phase 2.
|
|
267
|
+
*/
|
|
268
|
+
sendReadReceipts?: boolean;
|
|
269
|
+
/**
|
|
270
|
+
* Ack-reaction level (ported from Slack's ack reaction). `'ack'` (default
|
|
271
|
+
* when absent) reacts ⏳ on the inbound message at receipt and clears it
|
|
272
|
+
* once `whatsapp_cloud_reply` lands; `'off'` skips both. Phase 2.
|
|
273
|
+
*/
|
|
274
|
+
reactionLevel?: 'off' | 'ack';
|
|
275
|
+
/**
|
|
276
|
+
* Allow `whatsapp_cloud_reply` to send pre-approved message TEMPLATES
|
|
277
|
+
* (Phase 3). Default false — opt-in on purpose, unlike the other Phase 2/3
|
|
278
|
+
* booleans here which default on: a template is the one send that reaches
|
|
279
|
+
* a user OUTSIDE WhatsApp's 24h customer-service window, so an agent must
|
|
280
|
+
* never gain that reach silently just by upgrading the gateway. With this
|
|
281
|
+
* unset, a template send is refused with an explanatory error rather than
|
|
282
|
+
* being dropped.
|
|
283
|
+
*/
|
|
284
|
+
templatesEnabled?: boolean;
|
|
285
|
+
};
|
|
128
286
|
claude: {
|
|
129
287
|
model: string;
|
|
130
288
|
/** @deprecated --dangerously-skip-permissions is always passed now; this field is ignored. */
|
|
@@ -528,6 +686,10 @@ export interface SessionMeta {
|
|
|
528
686
|
* can restore the composer selection on reload; the agent's own context is the
|
|
529
687
|
* functional source of truth. Updated whenever a send carries image_params. */
|
|
530
688
|
imageConfig?: ImageParams;
|
|
689
|
+
/** Last composer video options sent for this session. Mirrors imageConfig —
|
|
690
|
+
* persisted so the web can restore the composer selection on reload. Updated
|
|
691
|
+
* whenever a send carries video_params. */
|
|
692
|
+
videoConfig?: VideoParams;
|
|
531
693
|
/** Real model from Claude stream, updated per turn (e.g. "claude-opus-4-8"). */
|
|
532
694
|
model?: string;
|
|
533
695
|
}
|
|
@@ -561,6 +723,27 @@ export type ImageParams = {
|
|
|
561
723
|
image_refs?: string[];
|
|
562
724
|
n?: number;
|
|
563
725
|
};
|
|
726
|
+
/**
|
|
727
|
+
* Video-generation options selected in the web composer (per-session), passed
|
|
728
|
+
* through the chat send body as `video_params` and surfaced to the agent so it
|
|
729
|
+
* calls the `generate_video` MCP tool with these exact values. Mirrors ImageParams.
|
|
730
|
+
* Without this bridge the agent receives no composer context for video and invents
|
|
731
|
+
* duration/aspect (e.g. a non-existent 10s cap, an 8+8 scene split, or a landscape
|
|
732
|
+
* clip when 9:16 was selected).
|
|
733
|
+
*/
|
|
734
|
+
export type VideoParams = {
|
|
735
|
+
model?: string;
|
|
736
|
+
resolution?: string;
|
|
737
|
+
aspect_ratio?: string;
|
|
738
|
+
/** Discrete clip length in seconds (model-specific, e.g. 6/10/15). */
|
|
739
|
+
duration?: number;
|
|
740
|
+
/**
|
|
741
|
+
* Source frame for image-to-video, if the composer selected one. A `ref` value
|
|
742
|
+
* (media-relative path or `artifact:<id>`) passed as the generate_video `image`
|
|
743
|
+
* argument. When present the source frame's own aspect wins over aspect_ratio.
|
|
744
|
+
*/
|
|
745
|
+
image_ref?: string;
|
|
746
|
+
};
|
|
564
747
|
export type StreamEvent = {
|
|
565
748
|
type: 'text_delta';
|
|
566
749
|
text: string;
|