chat 4.21.0 → 4.23.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/index.d.ts +175 -6
- package/dist/index.js +383 -68
- package/dist/index.js.map +1 -1
- package/docs/concurrency.mdx +223 -0
- package/docs/meta.json +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -131,7 +131,7 @@ async function toAiMessages(messages, options) {
|
|
|
131
131
|
filtered.map(async (msg) => {
|
|
132
132
|
const role = msg.author.isMe ? "assistant" : "user";
|
|
133
133
|
let textContent = includeNames && role === "user" ? `[${msg.author.userName}]: ${msg.text}` : msg.text;
|
|
134
|
-
if (msg.links.length > 0) {
|
|
134
|
+
if (msg.links && msg.links.length > 0) {
|
|
135
135
|
const linkParts = msg.links.map((link2) => {
|
|
136
136
|
const parts = link2.fetchMessage ? [`[Embedded message: ${link2.url}]`] : [link2.url];
|
|
137
137
|
if (link2.title) {
|
|
@@ -153,7 +153,7 @@ ${linkParts}`;
|
|
|
153
153
|
let aiMessage;
|
|
154
154
|
if (role === "user") {
|
|
155
155
|
const attachmentParts = [];
|
|
156
|
-
for (const att of msg.attachments) {
|
|
156
|
+
for (const att of msg.attachments ?? []) {
|
|
157
157
|
const part = await attachmentToPart(att);
|
|
158
158
|
if (part) {
|
|
159
159
|
attachmentParts.push(part);
|
|
@@ -469,6 +469,7 @@ function isAsyncIterable(value) {
|
|
|
469
469
|
var ChannelImpl = class _ChannelImpl {
|
|
470
470
|
id;
|
|
471
471
|
isDM;
|
|
472
|
+
channelVisibility;
|
|
472
473
|
_adapter;
|
|
473
474
|
_adapterName;
|
|
474
475
|
_stateAdapterInstance;
|
|
@@ -477,6 +478,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
477
478
|
constructor(config) {
|
|
478
479
|
this.id = config.id;
|
|
479
480
|
this.isDM = config.isDM ?? false;
|
|
481
|
+
this.channelVisibility = config.channelVisibility ?? "unknown";
|
|
480
482
|
if (isLazyConfig(config)) {
|
|
481
483
|
this._adapterName = config.adapterName;
|
|
482
484
|
} else {
|
|
@@ -695,6 +697,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
695
697
|
_type: "chat:Channel",
|
|
696
698
|
id: this.id,
|
|
697
699
|
adapterName: this.adapter.name,
|
|
700
|
+
channelVisibility: this.channelVisibility,
|
|
698
701
|
isDM: this.isDM
|
|
699
702
|
};
|
|
700
703
|
}
|
|
@@ -702,6 +705,7 @@ var ChannelImpl = class _ChannelImpl {
|
|
|
702
705
|
const channel = new _ChannelImpl({
|
|
703
706
|
id: json.id,
|
|
704
707
|
adapterName: json.adapterName,
|
|
708
|
+
channelVisibility: json.channelVisibility,
|
|
705
709
|
isDM: json.isDM
|
|
706
710
|
});
|
|
707
711
|
if (adapter) {
|
|
@@ -1099,6 +1103,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1099
1103
|
id;
|
|
1100
1104
|
channelId;
|
|
1101
1105
|
isDM;
|
|
1106
|
+
channelVisibility;
|
|
1102
1107
|
/** Direct adapter instance (if provided) */
|
|
1103
1108
|
_adapter;
|
|
1104
1109
|
/** Adapter name for lazy resolution */
|
|
@@ -1122,6 +1127,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1122
1127
|
this.id = config.id;
|
|
1123
1128
|
this.channelId = config.channelId;
|
|
1124
1129
|
this.isDM = config.isDM ?? false;
|
|
1130
|
+
this.channelVisibility = config.channelVisibility ?? "unknown";
|
|
1125
1131
|
this._isSubscribedContext = config.isSubscribedContext ?? false;
|
|
1126
1132
|
this._currentMessage = config.currentMessage;
|
|
1127
1133
|
this._logger = config.logger;
|
|
@@ -1212,6 +1218,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1212
1218
|
adapter: this.adapter,
|
|
1213
1219
|
stateAdapter: this._stateAdapter,
|
|
1214
1220
|
isDM: this.isDM,
|
|
1221
|
+
channelVisibility: this.channelVisibility,
|
|
1215
1222
|
messageHistory: this._messageHistory
|
|
1216
1223
|
});
|
|
1217
1224
|
}
|
|
@@ -1573,6 +1580,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1573
1580
|
_type: "chat:Thread",
|
|
1574
1581
|
id: this.id,
|
|
1575
1582
|
channelId: this.channelId,
|
|
1583
|
+
channelVisibility: this.channelVisibility,
|
|
1576
1584
|
currentMessage: this._currentMessage?.toJSON(),
|
|
1577
1585
|
isDM: this.isDM,
|
|
1578
1586
|
adapterName: this.adapter.name
|
|
@@ -1597,6 +1605,7 @@ var ThreadImpl = class _ThreadImpl {
|
|
|
1597
1605
|
id: json.id,
|
|
1598
1606
|
adapterName: json.adapterName,
|
|
1599
1607
|
channelId: json.channelId,
|
|
1608
|
+
channelVisibility: json.channelVisibility,
|
|
1600
1609
|
currentMessage: json.currentMessage ? Message.fromJSON(json.currentMessage) : void 0,
|
|
1601
1610
|
isDM: json.isDM
|
|
1602
1611
|
});
|
|
@@ -1766,6 +1775,9 @@ function extractMessageContent2(message) {
|
|
|
1766
1775
|
|
|
1767
1776
|
// src/chat.ts
|
|
1768
1777
|
var DEFAULT_LOCK_TTL_MS = 3e4;
|
|
1778
|
+
function sleep(ms) {
|
|
1779
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1780
|
+
}
|
|
1769
1781
|
var SLACK_USER_ID_REGEX = /^U[A-Z0-9]+$/i;
|
|
1770
1782
|
var DISCORD_SNOWFLAKE_REGEX = /^\d{17,19}$/;
|
|
1771
1783
|
var DEDUPE_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -1810,6 +1822,9 @@ var Chat = class {
|
|
|
1810
1822
|
_dedupeTtlMs;
|
|
1811
1823
|
_onLockConflict;
|
|
1812
1824
|
_messageHistory;
|
|
1825
|
+
_concurrencyStrategy;
|
|
1826
|
+
_concurrencyConfig;
|
|
1827
|
+
_lockScope;
|
|
1813
1828
|
mentionHandlers = [];
|
|
1814
1829
|
directMessageHandlers = [];
|
|
1815
1830
|
messagePatterns = [];
|
|
@@ -1840,6 +1855,38 @@ var Chat = class {
|
|
|
1840
1855
|
this._fallbackStreamingPlaceholderText = config.fallbackStreamingPlaceholderText !== void 0 ? config.fallbackStreamingPlaceholderText : "...";
|
|
1841
1856
|
this._dedupeTtlMs = config.dedupeTtlMs ?? DEDUPE_TTL_MS;
|
|
1842
1857
|
this._onLockConflict = config.onLockConflict;
|
|
1858
|
+
this._lockScope = config.lockScope;
|
|
1859
|
+
const concurrency = config.concurrency;
|
|
1860
|
+
if (concurrency) {
|
|
1861
|
+
if (typeof concurrency === "string") {
|
|
1862
|
+
this._concurrencyStrategy = concurrency;
|
|
1863
|
+
this._concurrencyConfig = {
|
|
1864
|
+
debounceMs: 1500,
|
|
1865
|
+
maxConcurrent: Number.POSITIVE_INFINITY,
|
|
1866
|
+
maxQueueSize: 10,
|
|
1867
|
+
onQueueFull: "drop-oldest",
|
|
1868
|
+
queueEntryTtlMs: 9e4
|
|
1869
|
+
};
|
|
1870
|
+
} else {
|
|
1871
|
+
this._concurrencyStrategy = concurrency.strategy;
|
|
1872
|
+
this._concurrencyConfig = {
|
|
1873
|
+
debounceMs: concurrency.debounceMs ?? 1500,
|
|
1874
|
+
maxConcurrent: concurrency.maxConcurrent ?? Number.POSITIVE_INFINITY,
|
|
1875
|
+
maxQueueSize: concurrency.maxQueueSize ?? 10,
|
|
1876
|
+
onQueueFull: concurrency.onQueueFull ?? "drop-oldest",
|
|
1877
|
+
queueEntryTtlMs: concurrency.queueEntryTtlMs ?? 9e4
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
} else {
|
|
1881
|
+
this._concurrencyStrategy = "drop";
|
|
1882
|
+
this._concurrencyConfig = {
|
|
1883
|
+
debounceMs: 1500,
|
|
1884
|
+
maxConcurrent: Number.POSITIVE_INFINITY,
|
|
1885
|
+
maxQueueSize: 10,
|
|
1886
|
+
onQueueFull: "drop-oldest",
|
|
1887
|
+
queueEntryTtlMs: 9e4
|
|
1888
|
+
};
|
|
1889
|
+
}
|
|
1843
1890
|
this._messageHistory = new MessageHistoryCache(
|
|
1844
1891
|
this._stateAdapter,
|
|
1845
1892
|
config.messageHistory
|
|
@@ -2771,6 +2818,27 @@ var Chat = class {
|
|
|
2771
2818
|
"UNKNOWN_USER_ID_FORMAT"
|
|
2772
2819
|
);
|
|
2773
2820
|
}
|
|
2821
|
+
/**
|
|
2822
|
+
* Resolve the lock key for a message based on lock scope.
|
|
2823
|
+
* With 'thread' scope, returns threadId. With 'channel' scope,
|
|
2824
|
+
* returns channelId (derived via adapter.channelIdFromThreadId).
|
|
2825
|
+
*/
|
|
2826
|
+
async getLockKey(adapter, threadId) {
|
|
2827
|
+
const channelId = adapter.channelIdFromThreadId(threadId);
|
|
2828
|
+
let scope;
|
|
2829
|
+
if (typeof this._lockScope === "function") {
|
|
2830
|
+
const isDM = adapter.isDM?.(threadId) ?? false;
|
|
2831
|
+
scope = await this._lockScope({
|
|
2832
|
+
adapter,
|
|
2833
|
+
channelId,
|
|
2834
|
+
isDM,
|
|
2835
|
+
threadId
|
|
2836
|
+
});
|
|
2837
|
+
} else {
|
|
2838
|
+
scope = this._lockScope ?? adapter.lockScope ?? "thread";
|
|
2839
|
+
}
|
|
2840
|
+
return scope === "channel" ? channelId : threadId;
|
|
2841
|
+
}
|
|
2774
2842
|
/**
|
|
2775
2843
|
* Handle an incoming message from an adapter.
|
|
2776
2844
|
* This is called by adapters when they receive a webhook.
|
|
@@ -2779,7 +2847,7 @@ var Chat = class {
|
|
|
2779
2847
|
* - Deduplication: Same message may arrive multiple times (e.g., Slack sends
|
|
2780
2848
|
* both `message` and `app_mention` events, GChat sends direct webhook + Pub/Sub)
|
|
2781
2849
|
* - Bot filtering: Messages from the bot itself are skipped
|
|
2782
|
-
* -
|
|
2850
|
+
* - Concurrency: Controlled by `concurrency` config (drop, queue, debounce, concurrent)
|
|
2783
2851
|
*/
|
|
2784
2852
|
async handleIncomingMessage(adapter, threadId, message) {
|
|
2785
2853
|
this.logger.debug("Incoming message", {
|
|
@@ -2821,109 +2889,319 @@ var Chat = class {
|
|
|
2821
2889
|
}
|
|
2822
2890
|
await Promise.all(appends);
|
|
2823
2891
|
}
|
|
2892
|
+
const lockKey = await this.getLockKey(adapter, threadId);
|
|
2893
|
+
const strategy = this._concurrencyStrategy;
|
|
2894
|
+
if (strategy === "concurrent") {
|
|
2895
|
+
await this.handleConcurrent(adapter, threadId, message);
|
|
2896
|
+
return;
|
|
2897
|
+
}
|
|
2898
|
+
if (strategy === "queue" || strategy === "debounce") {
|
|
2899
|
+
await this.handleQueueOrDebounce(
|
|
2900
|
+
adapter,
|
|
2901
|
+
threadId,
|
|
2902
|
+
lockKey,
|
|
2903
|
+
message,
|
|
2904
|
+
strategy
|
|
2905
|
+
);
|
|
2906
|
+
return;
|
|
2907
|
+
}
|
|
2908
|
+
await this.handleDrop(adapter, threadId, lockKey, message);
|
|
2909
|
+
}
|
|
2910
|
+
/**
|
|
2911
|
+
* Drop strategy: acquire lock or fail. Original behavior.
|
|
2912
|
+
*/
|
|
2913
|
+
async handleDrop(adapter, threadId, lockKey, message) {
|
|
2824
2914
|
let lock = await this._stateAdapter.acquireLock(
|
|
2825
|
-
|
|
2915
|
+
lockKey,
|
|
2826
2916
|
DEFAULT_LOCK_TTL_MS
|
|
2827
2917
|
);
|
|
2828
2918
|
if (!lock) {
|
|
2829
2919
|
const resolution = typeof this._onLockConflict === "function" ? await this._onLockConflict(threadId, message) : this._onLockConflict ?? "drop";
|
|
2830
2920
|
if (resolution === "force") {
|
|
2831
|
-
this.logger.info("Force-releasing lock on thread", {
|
|
2832
|
-
await this._stateAdapter.forceReleaseLock(threadId);
|
|
2833
|
-
lock = await this._stateAdapter.acquireLock(
|
|
2921
|
+
this.logger.info("Force-releasing lock on thread", {
|
|
2834
2922
|
threadId,
|
|
2923
|
+
lockKey
|
|
2924
|
+
});
|
|
2925
|
+
await this._stateAdapter.forceReleaseLock(lockKey);
|
|
2926
|
+
lock = await this._stateAdapter.acquireLock(
|
|
2927
|
+
lockKey,
|
|
2835
2928
|
DEFAULT_LOCK_TTL_MS
|
|
2836
2929
|
);
|
|
2837
2930
|
}
|
|
2838
2931
|
if (!lock) {
|
|
2839
|
-
this.logger.warn("Could not acquire lock on thread", {
|
|
2932
|
+
this.logger.warn("Could not acquire lock on thread", {
|
|
2933
|
+
threadId,
|
|
2934
|
+
lockKey
|
|
2935
|
+
});
|
|
2840
2936
|
throw new LockError(
|
|
2841
2937
|
`Could not acquire lock on thread ${threadId}. Another instance may be processing.`
|
|
2842
2938
|
);
|
|
2843
2939
|
}
|
|
2844
2940
|
}
|
|
2845
|
-
this.logger.debug("Lock acquired", {
|
|
2941
|
+
this.logger.debug("Lock acquired", {
|
|
2942
|
+
threadId,
|
|
2943
|
+
lockKey,
|
|
2944
|
+
token: lock.token
|
|
2945
|
+
});
|
|
2846
2946
|
try {
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
this.
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2947
|
+
await this.dispatchToHandlers(adapter, threadId, message);
|
|
2948
|
+
} finally {
|
|
2949
|
+
await this._stateAdapter.releaseLock(lock);
|
|
2950
|
+
this.logger.debug("Lock released", { threadId, lockKey });
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
/**
|
|
2954
|
+
* Queue/Debounce strategy: enqueue if lock is busy, drain after processing.
|
|
2955
|
+
*/
|
|
2956
|
+
async handleQueueOrDebounce(adapter, threadId, lockKey, message, strategy) {
|
|
2957
|
+
const { maxQueueSize, queueEntryTtlMs, onQueueFull, debounceMs } = this._concurrencyConfig;
|
|
2958
|
+
const lock = await this._stateAdapter.acquireLock(
|
|
2959
|
+
lockKey,
|
|
2960
|
+
DEFAULT_LOCK_TTL_MS
|
|
2961
|
+
);
|
|
2962
|
+
if (!lock) {
|
|
2963
|
+
const effectiveMaxSize = strategy === "debounce" ? 1 : maxQueueSize;
|
|
2964
|
+
const depth = await this._stateAdapter.queueDepth(lockKey);
|
|
2965
|
+
if (depth >= effectiveMaxSize && strategy !== "debounce" && onQueueFull === "drop-newest") {
|
|
2966
|
+
this.logger.info("message-dropped", {
|
|
2863
2967
|
threadId,
|
|
2864
|
-
|
|
2968
|
+
lockKey,
|
|
2969
|
+
messageId: message.id,
|
|
2970
|
+
reason: "queue-full"
|
|
2865
2971
|
});
|
|
2866
|
-
const channel = thread.channel;
|
|
2867
|
-
for (const handler of this.directMessageHandlers) {
|
|
2868
|
-
await handler(thread, message, channel);
|
|
2869
|
-
}
|
|
2870
2972
|
return;
|
|
2871
2973
|
}
|
|
2872
|
-
|
|
2873
|
-
|
|
2974
|
+
await this._stateAdapter.enqueue(
|
|
2975
|
+
lockKey,
|
|
2976
|
+
{
|
|
2977
|
+
message,
|
|
2978
|
+
enqueuedAt: Date.now(),
|
|
2979
|
+
expiresAt: Date.now() + queueEntryTtlMs
|
|
2980
|
+
},
|
|
2981
|
+
effectiveMaxSize
|
|
2982
|
+
);
|
|
2983
|
+
this.logger.info(
|
|
2984
|
+
strategy === "debounce" ? "message-debounce-reset" : "message-queued",
|
|
2985
|
+
{
|
|
2986
|
+
threadId,
|
|
2987
|
+
lockKey,
|
|
2988
|
+
messageId: message.id,
|
|
2989
|
+
queueDepth: Math.min(depth + 1, effectiveMaxSize)
|
|
2990
|
+
}
|
|
2991
|
+
);
|
|
2992
|
+
return;
|
|
2993
|
+
}
|
|
2994
|
+
this.logger.debug("Lock acquired", {
|
|
2995
|
+
threadId,
|
|
2996
|
+
lockKey,
|
|
2997
|
+
token: lock.token
|
|
2998
|
+
});
|
|
2999
|
+
try {
|
|
3000
|
+
if (strategy === "debounce") {
|
|
3001
|
+
await this._stateAdapter.enqueue(
|
|
3002
|
+
lockKey,
|
|
3003
|
+
{
|
|
3004
|
+
message,
|
|
3005
|
+
enqueuedAt: Date.now(),
|
|
3006
|
+
expiresAt: Date.now() + queueEntryTtlMs
|
|
3007
|
+
},
|
|
3008
|
+
1
|
|
3009
|
+
);
|
|
3010
|
+
this.logger.info("message-debouncing", {
|
|
3011
|
+
threadId,
|
|
3012
|
+
lockKey,
|
|
3013
|
+
messageId: message.id,
|
|
3014
|
+
debounceMs
|
|
3015
|
+
});
|
|
3016
|
+
await this.debounceLoop(lock, adapter, threadId, lockKey);
|
|
3017
|
+
} else {
|
|
3018
|
+
await this.dispatchToHandlers(adapter, threadId, message);
|
|
3019
|
+
await this.drainQueue(lock, adapter, threadId, lockKey);
|
|
2874
3020
|
}
|
|
2875
|
-
|
|
2876
|
-
|
|
3021
|
+
} finally {
|
|
3022
|
+
await this._stateAdapter.releaseLock(lock);
|
|
3023
|
+
this.logger.debug("Lock released", { threadId, lockKey });
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
/**
|
|
3027
|
+
* Debounce loop: wait for debounceMs, check if newer message arrived,
|
|
3028
|
+
* repeat until no new messages, then process the final message.
|
|
3029
|
+
*/
|
|
3030
|
+
async debounceLoop(lock, adapter, threadId, lockKey) {
|
|
3031
|
+
const { debounceMs } = this._concurrencyConfig;
|
|
3032
|
+
while (true) {
|
|
3033
|
+
await sleep(debounceMs);
|
|
3034
|
+
await this._stateAdapter.extendLock(lock, DEFAULT_LOCK_TTL_MS);
|
|
3035
|
+
const entry = await this._stateAdapter.dequeue(lockKey);
|
|
3036
|
+
if (!entry) {
|
|
3037
|
+
break;
|
|
3038
|
+
}
|
|
3039
|
+
const msg = this.rehydrateMessage(entry.message);
|
|
3040
|
+
if (Date.now() > entry.expiresAt) {
|
|
3041
|
+
this.logger.info("message-expired", {
|
|
2877
3042
|
threadId,
|
|
2878
|
-
|
|
3043
|
+
lockKey,
|
|
3044
|
+
messageId: msg.id
|
|
2879
3045
|
});
|
|
2880
|
-
|
|
2881
|
-
return;
|
|
3046
|
+
continue;
|
|
2882
3047
|
}
|
|
2883
|
-
|
|
2884
|
-
|
|
3048
|
+
const depth = await this._stateAdapter.queueDepth(lockKey);
|
|
3049
|
+
if (depth > 0) {
|
|
3050
|
+
this.logger.info("message-superseded", {
|
|
2885
3051
|
threadId,
|
|
2886
|
-
|
|
3052
|
+
lockKey,
|
|
3053
|
+
droppedId: msg.id
|
|
2887
3054
|
});
|
|
2888
|
-
|
|
2889
|
-
return;
|
|
3055
|
+
continue;
|
|
2890
3056
|
}
|
|
2891
|
-
this.logger.
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
3057
|
+
this.logger.info("message-dequeued", {
|
|
3058
|
+
threadId,
|
|
3059
|
+
lockKey,
|
|
3060
|
+
messageId: msg.id
|
|
2895
3061
|
});
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
3062
|
+
await this.dispatchToHandlers(adapter, threadId, msg);
|
|
3063
|
+
break;
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
/**
|
|
3067
|
+
* Drain queue: collect all pending messages, dispatch the latest with
|
|
3068
|
+
* skipped context, then check for more.
|
|
3069
|
+
*/
|
|
3070
|
+
async drainQueue(lock, adapter, threadId, lockKey) {
|
|
3071
|
+
while (true) {
|
|
3072
|
+
const pending = [];
|
|
3073
|
+
while (true) {
|
|
3074
|
+
const entry = await this._stateAdapter.dequeue(lockKey);
|
|
3075
|
+
if (!entry) {
|
|
3076
|
+
break;
|
|
3077
|
+
}
|
|
3078
|
+
const msg = this.rehydrateMessage(entry.message);
|
|
3079
|
+
if (Date.now() <= entry.expiresAt) {
|
|
3080
|
+
pending.push({ message: msg, expiresAt: entry.expiresAt });
|
|
3081
|
+
} else {
|
|
3082
|
+
this.logger.info("message-expired", {
|
|
3083
|
+
threadId,
|
|
3084
|
+
lockKey,
|
|
3085
|
+
messageId: msg.id
|
|
2907
3086
|
});
|
|
2908
|
-
matchedPattern = true;
|
|
2909
|
-
await handler(thread, message);
|
|
2910
3087
|
}
|
|
2911
3088
|
}
|
|
2912
|
-
if (
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
3089
|
+
if (pending.length === 0) {
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
3092
|
+
await this._stateAdapter.extendLock(lock, DEFAULT_LOCK_TTL_MS);
|
|
3093
|
+
const latest = pending.at(-1);
|
|
3094
|
+
if (!latest) {
|
|
3095
|
+
return;
|
|
3096
|
+
}
|
|
3097
|
+
const skipped = pending.slice(0, -1).map((e) => e.message);
|
|
3098
|
+
this.logger.info("message-dequeued", {
|
|
3099
|
+
threadId,
|
|
3100
|
+
lockKey,
|
|
3101
|
+
messageId: latest.message.id,
|
|
3102
|
+
skippedCount: skipped.length,
|
|
3103
|
+
totalSinceLastHandler: pending.length
|
|
3104
|
+
});
|
|
3105
|
+
const context = {
|
|
3106
|
+
skipped,
|
|
3107
|
+
totalSinceLastHandler: pending.length
|
|
3108
|
+
};
|
|
3109
|
+
await this.dispatchToHandlers(adapter, threadId, latest.message, context);
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
/**
|
|
3113
|
+
* Concurrent strategy: no locking, process immediately.
|
|
3114
|
+
*/
|
|
3115
|
+
async handleConcurrent(adapter, threadId, message) {
|
|
3116
|
+
await this.dispatchToHandlers(adapter, threadId, message);
|
|
3117
|
+
}
|
|
3118
|
+
/**
|
|
3119
|
+
* Dispatch a message to the appropriate handler chain based on
|
|
3120
|
+
* subscription status, mention detection, and pattern matching.
|
|
3121
|
+
*/
|
|
3122
|
+
async dispatchToHandlers(adapter, threadId, message, context) {
|
|
3123
|
+
message.isMention = message.isMention || this.detectMention(adapter, message);
|
|
3124
|
+
const isSubscribed = await this._stateAdapter.isSubscribed(threadId);
|
|
3125
|
+
this.logger.debug("Subscription check", {
|
|
3126
|
+
threadId,
|
|
3127
|
+
isSubscribed,
|
|
3128
|
+
subscribedHandlerCount: this.subscribedMessageHandlers.length
|
|
3129
|
+
});
|
|
3130
|
+
const thread = await this.createThread(
|
|
3131
|
+
adapter,
|
|
3132
|
+
threadId,
|
|
3133
|
+
message,
|
|
3134
|
+
isSubscribed
|
|
3135
|
+
);
|
|
3136
|
+
const isDM = adapter.isDM?.(threadId) ?? false;
|
|
3137
|
+
if (isDM && this.directMessageHandlers.length > 0) {
|
|
3138
|
+
this.logger.debug("Direct message received - calling handlers", {
|
|
3139
|
+
threadId,
|
|
3140
|
+
handlerCount: this.directMessageHandlers.length
|
|
3141
|
+
});
|
|
3142
|
+
const channel = thread.channel;
|
|
3143
|
+
for (const handler of this.directMessageHandlers) {
|
|
3144
|
+
await handler(thread, message, channel, context);
|
|
3145
|
+
}
|
|
3146
|
+
return;
|
|
3147
|
+
}
|
|
3148
|
+
if (isDM) {
|
|
3149
|
+
message.isMention = true;
|
|
3150
|
+
}
|
|
3151
|
+
if (isSubscribed) {
|
|
3152
|
+
this.logger.debug("Message in subscribed thread - calling handlers", {
|
|
3153
|
+
threadId,
|
|
3154
|
+
handlerCount: this.subscribedMessageHandlers.length
|
|
3155
|
+
});
|
|
3156
|
+
await this.runHandlers(
|
|
3157
|
+
this.subscribedMessageHandlers,
|
|
3158
|
+
thread,
|
|
3159
|
+
message,
|
|
3160
|
+
context
|
|
3161
|
+
);
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
if (message.isMention) {
|
|
3165
|
+
this.logger.debug("Bot mentioned", {
|
|
3166
|
+
threadId,
|
|
3167
|
+
text: message.text.slice(0, 100)
|
|
3168
|
+
});
|
|
3169
|
+
await this.runHandlers(this.mentionHandlers, thread, message, context);
|
|
3170
|
+
return;
|
|
3171
|
+
}
|
|
3172
|
+
this.logger.debug("Checking message patterns", {
|
|
3173
|
+
patternCount: this.messagePatterns.length,
|
|
3174
|
+
patterns: this.messagePatterns.map((p) => p.pattern.toString()),
|
|
3175
|
+
messageText: message.text
|
|
3176
|
+
});
|
|
3177
|
+
let matchedPattern = false;
|
|
3178
|
+
for (const { pattern, handler } of this.messagePatterns) {
|
|
3179
|
+
const matches = pattern.test(message.text);
|
|
3180
|
+
this.logger.debug("Pattern test", {
|
|
3181
|
+
pattern: pattern.toString(),
|
|
3182
|
+
text: message.text,
|
|
3183
|
+
matches
|
|
3184
|
+
});
|
|
3185
|
+
if (matches) {
|
|
3186
|
+
this.logger.debug("Message matched pattern - calling handler", {
|
|
3187
|
+
pattern: pattern.toString()
|
|
2916
3188
|
});
|
|
3189
|
+
matchedPattern = true;
|
|
3190
|
+
await handler(thread, message, context);
|
|
2917
3191
|
}
|
|
2918
|
-
}
|
|
2919
|
-
|
|
2920
|
-
this.logger.debug("
|
|
3192
|
+
}
|
|
3193
|
+
if (!matchedPattern) {
|
|
3194
|
+
this.logger.debug("No handlers matched message", {
|
|
3195
|
+
threadId,
|
|
3196
|
+
text: message.text.slice(0, 100)
|
|
3197
|
+
});
|
|
2921
3198
|
}
|
|
2922
3199
|
}
|
|
2923
3200
|
createThread(adapter, threadId, initialMessage, isSubscribedContext = false) {
|
|
2924
3201
|
const parts = threadId.split(":");
|
|
2925
3202
|
const channelId = parts[1] || "";
|
|
2926
3203
|
const isDM = adapter.isDM?.(threadId) ?? false;
|
|
3204
|
+
const channelVisibility = adapter.getChannelVisibility?.(threadId) ?? "unknown";
|
|
2927
3205
|
return new ThreadImpl({
|
|
2928
3206
|
id: threadId,
|
|
2929
3207
|
adapter,
|
|
@@ -2932,6 +3210,7 @@ var Chat = class {
|
|
|
2932
3210
|
initialMessage,
|
|
2933
3211
|
isSubscribedContext,
|
|
2934
3212
|
isDM,
|
|
3213
|
+
channelVisibility,
|
|
2935
3214
|
currentMessage: initialMessage,
|
|
2936
3215
|
logger: this.logger,
|
|
2937
3216
|
streamingUpdateIntervalMs: this._streamingUpdateIntervalMs,
|
|
@@ -2974,9 +3253,45 @@ var Chat = class {
|
|
|
2974
3253
|
escapeRegex(str) {
|
|
2975
3254
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2976
3255
|
}
|
|
2977
|
-
|
|
3256
|
+
/**
|
|
3257
|
+
* Reconstruct a proper Message instance from a dequeued entry.
|
|
3258
|
+
* After JSON roundtrip through the state adapter, the message is a plain
|
|
3259
|
+
* object (not a Message instance). This restores class invariants like
|
|
3260
|
+
* `links` defaulting to `[]` and `metadata.dateSent` being a Date.
|
|
3261
|
+
*/
|
|
3262
|
+
rehydrateMessage(raw) {
|
|
3263
|
+
if (raw instanceof Message) {
|
|
3264
|
+
return raw;
|
|
3265
|
+
}
|
|
3266
|
+
const obj = raw;
|
|
3267
|
+
if (obj._type === "chat:Message") {
|
|
3268
|
+
return Message.fromJSON(obj);
|
|
3269
|
+
}
|
|
3270
|
+
const metadata = obj.metadata;
|
|
3271
|
+
const dateSent = metadata.dateSent;
|
|
3272
|
+
const editedAt = metadata.editedAt;
|
|
3273
|
+
return new Message({
|
|
3274
|
+
id: obj.id,
|
|
3275
|
+
threadId: obj.threadId,
|
|
3276
|
+
text: obj.text,
|
|
3277
|
+
formatted: obj.formatted,
|
|
3278
|
+
raw: obj.raw,
|
|
3279
|
+
author: obj.author,
|
|
3280
|
+
metadata: {
|
|
3281
|
+
dateSent: dateSent instanceof Date ? dateSent : new Date(dateSent),
|
|
3282
|
+
edited: metadata.edited,
|
|
3283
|
+
editedAt: editedAt ? new Date(
|
|
3284
|
+
editedAt instanceof Date ? editedAt.toISOString() : editedAt
|
|
3285
|
+
) : void 0
|
|
3286
|
+
},
|
|
3287
|
+
attachments: obj.attachments ?? [],
|
|
3288
|
+
isMention: obj.isMention,
|
|
3289
|
+
links: obj.links ?? []
|
|
3290
|
+
});
|
|
3291
|
+
}
|
|
3292
|
+
async runHandlers(handlers, thread, message, context) {
|
|
2978
3293
|
for (const handler of handlers) {
|
|
2979
|
-
await handler(thread, message);
|
|
3294
|
+
await handler(thread, message, context);
|
|
2980
3295
|
}
|
|
2981
3296
|
}
|
|
2982
3297
|
};
|