11// Thread Ownership plugin entrypoint registers its OpenClaw integration.
2+ import { pruneMapToMaxSize } from "openclaw/plugin-sdk/collection-runtime" ;
23import { resolveLivePluginConfigObject } from "openclaw/plugin-sdk/plugin-config-runtime" ;
4+ import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http" ;
35import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime" ;
46import { escapeRegExp } from "openclaw/plugin-sdk/text-utility-runtime" ;
57import {
@@ -22,6 +24,7 @@ type ThreadOwnershipMessageSendingResult = { cancel: true } | undefined;
2224// Entries expire after 5 minutes.
2325const mentionedThreads = new Map < string , number > ( ) ;
2426const MENTION_TTL_MS = 5 * 60 * 1000 ;
27+ const MENTIONED_THREADS_MAX_ENTRIES = 1000 ;
2528
2629function isThreadOwnershipConfig ( value : unknown ) : value is ThreadOwnershipConfig {
2730 return value !== null && typeof value === "object" ;
@@ -51,6 +54,15 @@ function cleanExpiredMentions(): void {
5154 }
5255}
5356
57+ function trackMentionedThread ( key : string ) : void {
58+ cleanExpiredMentions ( ) ;
59+ if ( mentionedThreads . has ( key ) ) {
60+ mentionedThreads . delete ( key ) ;
61+ }
62+ mentionedThreads . set ( key , Date . now ( ) ) ;
63+ pruneMapToMaxSize ( mentionedThreads , MENTIONED_THREADS_MAX_ENTRIES ) ;
64+ }
65+
5466function containsAgentNameMention ( text : string , agentName : string ) : boolean {
5567 const trimmedName = agentName . trim ( ) ;
5668 if ( ! trimmedName ) {
@@ -136,8 +148,7 @@ export default definePluginEntry({
136148 containsAgentNameMention ( text , agent . name ) ||
137149 ( botUserId && text . includes ( `<@${ botUserId } >` ) ) ;
138150 if ( mentioned ) {
139- cleanExpiredMentions ( ) ;
140- mentionedThreads . set ( `${ channelId } :${ threadTs } ` , Date . now ( ) ) ;
151+ trackMentionedThread ( `${ channelId } :${ threadTs } ` ) ;
141152 }
142153 } ) ;
143154
@@ -189,7 +200,10 @@ export default definePluginEntry({
189200 return undefined ;
190201 }
191202 if ( resp . status === 409 ) {
192- const body = ( await resp . json ( ) ) as { owner ?: string } ;
203+ const body = await readProviderJsonResponse < { owner ?: string } > (
204+ resp ,
205+ "thread-ownership.ownership-conflict" ,
206+ ) ;
193207 api . logger . info ?.(
194208 `thread-ownership: cancelled send to ${ channelId } :${ threadTs } — owned by ${ body . owner } ` ,
195209 ) ;
@@ -208,3 +222,7 @@ export default definePluginEntry({
208222 } ) ;
209223 } ,
210224} ) ;
225+
226+ export function resetMentionedThreadsForTests ( ) : void {
227+ mentionedThreads . clear ( ) ;
228+ }
0 commit comments