hybrid 1.4.3 → 1.4.5

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.
@@ -47,7 +47,6 @@ function filterMessages(filters) {
47
47
  `\u{1F50D} [filter-messages] Processing message: ${messageContent}...`
48
48
  );
49
49
  const filterAPI = {
50
- fromSelf: () => import_xmtp.filter.fromSelf(context.message, context.client),
51
50
  hasContent: () => import_xmtp.filter.hasContent(context.message),
52
51
  isDM: () => import_xmtp.filter.isDM(context.conversation),
53
52
  isGroup: () => import_xmtp.filter.isGroup(context.conversation),
@@ -75,6 +74,7 @@ function filterMessages(filters) {
75
74
  isReply: () => import_xmtp.filter.isReply(context.message),
76
75
  isText: () => import_xmtp.filter.isText(context.message),
77
76
  isTextReply: () => import_xmtp.filter.isTextReply(context.message),
77
+ isFromSelf: () => context.message.senderInboxId === context.client.inboxId,
78
78
  hasMention: (mention) => {
79
79
  const content = typeof context.message.content === "string" ? context.message.content : String(context.message.content || "");
80
80
  return content.includes(mention);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/behaviors/index.ts","../src/behaviors/filter-messages.ts","../src/behaviors/react-with.ts","../src/behaviors/threaded-reply.ts"],"sourcesContent":["// Re-export XMTP Agent SDK filters for convenience\nexport { filter } from \"@hybrd/xmtp\"\n\nexport * from \"./filter-messages\"\nexport * from \"./react-with\"\nexport * from \"./threaded-reply\"\n\n// Re-export behavior types for convenience\nexport { BehaviorRegistryImpl } from \"@hybrd/types\"\nexport type {\n\tBehavior,\n\tBehaviorConfig,\n\tBehaviorContext,\n\tBehaviorObject,\n\tBehaviorRegistry\n} from \"@hybrd/types\"\n","import type { BehaviorContext, BehaviorObject } from \"@hybrd/types\"\nimport { logger } from \"@hybrd/utils\"\nimport { filter } from \"@hybrd/xmtp\"\n\n// Filter interface that matches XMTP SDK signatures\ninterface FilterAPI {\n\tfromSelf(): boolean\n\thasContent(): boolean\n\tisDM(): boolean\n\tisGroup(): boolean\n\tisGroupAdmin(): boolean\n\tisGroupSuperAdmin(): boolean\n\tisReaction(): boolean\n\tisReaction(emoji?: string, action?: \"added\" | \"removed\"): boolean\n\tisRemoteAttachment(): boolean\n\tisReply(): boolean\n\tisText(): boolean\n\tisTextReply(): boolean\n\thasMention(mention: string): boolean\n}\n\nexport function filterMessages(\n\tfilters: (api: FilterAPI) => boolean\n): BehaviorObject {\n\treturn {\n\t\tid: \"filter-messages\",\n\t\tconfig: {\n\t\t\tenabled: true,\n\t\t\tconfig: {\n\t\t\t\tfilters: 1\n\t\t\t}\n\t\t},\n\t\tasync before(context: BehaviorContext) {\n\t\t\tconst messageContent =\n\t\t\t\ttypeof context.message.content === \"string\"\n\t\t\t\t\t? context.message.content.substring(0, 100)\n\t\t\t\t\t: String(context.message.content || \"unknown\")\n\t\t\tlogger.debug(\n\t\t\t\t`🔍 [filter-messages] Processing message: ${messageContent}...`\n\t\t\t)\n\n\t\t\t// Create filter API wrapper\n\t\t\tconst filterAPI: FilterAPI = {\n\t\t\t\tfromSelf: () =>\n\t\t\t\t\tfilter.fromSelf(context.message as any, context.client as any),\n\t\t\t\thasContent: () => filter.hasContent(context.message as any),\n\t\t\t\tisDM: () => filter.isDM(context.conversation as any),\n\t\t\t\tisGroup: () => filter.isGroup(context.conversation as any),\n\t\t\t\tisGroupAdmin: () =>\n\t\t\t\t\tfilter.isGroupAdmin(\n\t\t\t\t\t\tcontext.conversation as any,\n\t\t\t\t\t\tcontext.message as any\n\t\t\t\t\t),\n\t\t\t\tisGroupSuperAdmin: () =>\n\t\t\t\t\tfilter.isGroupSuperAdmin(\n\t\t\t\t\t\tcontext.conversation as any,\n\t\t\t\t\t\tcontext.message as any\n\t\t\t\t\t),\n\t\t\t\tisReaction: (emoji?: string, action?: \"added\" | \"removed\") => {\n\t\t\t\t\tconst isReaction = filter.isReaction(context.message as any)\n\t\t\t\t\tif (!isReaction) return false\n\n\t\t\t\t\t// Check if message has reaction content\n\t\t\t\t\tif (\n\t\t\t\t\t\t!context.message.content ||\n\t\t\t\t\t\ttypeof context.message.content !== \"object\"\n\t\t\t\t\t)\n\t\t\t\t\t\treturn false\n\n\t\t\t\t\tconst reactionContent = context.message.content as any\n\n\t\t\t\t\t// Validate reaction content structure\n\t\t\t\t\tif (!reactionContent.content) return false\n\n\t\t\t\t\t// Check emoji if specified\n\t\t\t\t\tif (emoji && reactionContent.content !== emoji) return false\n\n\t\t\t\t\t// Check action if specified\n\t\t\t\t\tif (action && reactionContent.action !== action) return false\n\n\t\t\t\t\t// If no specific checks requested, just return true\n\t\t\t\t\tif (!emoji && !action) return true\n\n\t\t\t\t\treturn true\n\t\t\t\t},\n\t\t\t\tisRemoteAttachment: () =>\n\t\t\t\t\tfilter.isRemoteAttachment(context.message as any),\n\t\t\t\tisReply: () => filter.isReply(context.message as any),\n\t\t\t\tisText: () => filter.isText(context.message as any),\n\t\t\t\tisTextReply: () => filter.isTextReply(context.message as any),\n\t\t\t\thasMention: (mention: string) => {\n\t\t\t\t\tconst content =\n\t\t\t\t\t\ttypeof context.message.content === \"string\"\n\t\t\t\t\t\t\t? context.message.content\n\t\t\t\t\t\t\t: String(context.message.content || \"\")\n\t\t\t\t\treturn content.includes(mention)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst passes = filters(filterAPI)\n\n\t\t\t\tif (!passes) {\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t`🔇 [filter-messages] Filter failed - message filtered out`\n\t\t\t\t\t)\n\t\t\t\t\t// Message filtered, set flag and stop the chain\n\t\t\t\t\tif (!context.sendOptions) {\n\t\t\t\t\t\tcontext.sendOptions = {}\n\t\t\t\t\t}\n\t\t\t\t\tcontext.sendOptions.filtered = true\n\t\t\t\t\t// Don't call next() - this stops the middleware chain\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tlogger.debug(`✅ [filter-messages] Filter passed`)\n\t\t\t} catch (error) {\n\t\t\t\tlogger.error(\"Error executing message filter:\", error)\n\t\t\t\tthrow error // Re-throw to propagate the error\n\t\t\t}\n\n\t\t\tlogger.debug(`✅ [filter-messages] Filter passed, continuing chain`)\n\t\t\t// Filter passed, continue to next behavior\n\t\t\tawait context.next?.()\n\t\t}\n\t}\n}\n","import type { BehaviorContext, BehaviorObject } from \"@hybrd/types\"\nimport { logger } from \"@hybrd/utils\"\nimport { ContentTypeReaction } from \"@hybrd/xmtp\"\n\nexport interface ReactWithOptions {\n\treactToAll?: boolean\n\tenabled?: boolean\n}\n\nexport function reactWith(\n\treaction: string,\n\toptions: ReactWithOptions = {}\n): BehaviorObject {\n\treturn {\n\t\tid: `react-with-${reaction}`,\n\t\tconfig: {\n\t\t\tenabled: options.enabled ?? true,\n\t\t\tconfig: {\n\t\t\t\treaction,\n\t\t\t\treactToAll: options.reactToAll ?? true\n\t\t\t}\n\t\t},\n\t\tasync before(context: BehaviorContext) {\n\t\t\tif (!this.config.enabled) return\n\n\t\t\ttry {\n\t\t\t\tconst reactionMessage = {\n\t\t\t\t\tschema: \"unicode\",\n\t\t\t\t\treference: context.message.id,\n\t\t\t\t\taction: \"added\",\n\t\t\t\t\tcontentType: ContentTypeReaction,\n\t\t\t\t\tcontent: reaction\n\t\t\t\t}\n\n\t\t\t\tawait context.conversation.send(reactionMessage, ContentTypeReaction)\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`✅ [react-with] Reacted with ${reaction} to message ${context.message.id}`\n\t\t\t\t)\n\t\t\t} catch (error) {\n\t\t\t\tlogger.error(\n\t\t\t\t\t`❌ [react-with] Failed to add reaction ${reaction}:`,\n\t\t\t\t\terror\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n}\n","import type { BehaviorContext, BehaviorObject } from \"@hybrd/types\"\n\nexport interface ThreadedReplyOptions {\n\tenabled?: boolean\n}\n\nexport function threadedReply(\n\toptions: ThreadedReplyOptions = {}\n): BehaviorObject {\n\treturn {\n\t\tid: \"threaded-reply\",\n\t\tconfig: {\n\t\t\tenabled: options.enabled ?? true,\n\t\t\tconfig: {\n\t\t\t\talwaysThread: true\n\t\t\t}\n\t\t},\n\t\tasync after(context: BehaviorContext) {\n\t\t\tif (!this.config.enabled) return\n\n\t\t\tif (!context.sendOptions) {\n\t\t\t\tcontext.sendOptions = {}\n\t\t\t}\n\t\t\tcontext.sendOptions.threaded = true\n\t\t}\n\t}\n}\n"],"mappings":"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","names":["import_xmtp","import_utils","import_xmtp"]}
1
+ {"version":3,"sources":["../src/behaviors/index.ts","../src/behaviors/filter-messages.ts","../src/behaviors/react-with.ts","../src/behaviors/threaded-reply.ts"],"sourcesContent":["// Re-export XMTP Agent SDK filters for convenience\nexport { filter } from \"@hybrd/xmtp\"\n\nexport * from \"./filter-messages\"\nexport * from \"./react-with\"\nexport * from \"./threaded-reply\"\n\n// Re-export behavior types for convenience\nexport { BehaviorRegistryImpl } from \"@hybrd/types\"\nexport type {\n\tBehavior,\n\tBehaviorConfig,\n\tBehaviorContext,\n\tBehaviorObject,\n\tBehaviorRegistry\n} from \"@hybrd/types\"\n","import type { BehaviorContext, BehaviorObject } from \"@hybrd/types\"\nimport { logger } from \"@hybrd/utils\"\nimport { filter } from \"@hybrd/xmtp\"\n\n// Filter interface that matches XMTP SDK signatures\ninterface FilterAPI {\n\tisDM(): boolean\n\tisGroup(): boolean\n\tisGroupAdmin(): boolean\n\tisGroupSuperAdmin(): boolean\n\tisReaction(): boolean\n\tisReaction(emoji?: string, action?: \"added\" | \"removed\"): boolean\n\tisRemoteAttachment(): boolean\n\tisReply(): boolean\n\tisText(): boolean\n\tisTextReply(): boolean\n\tisFromSelf(): boolean\n\thasMention(mention: string): boolean\n\thasContent(): boolean\n}\n\nexport function filterMessages(\n\tfilters: (api: FilterAPI) => boolean\n): BehaviorObject {\n\treturn {\n\t\tid: \"filter-messages\",\n\t\tconfig: {\n\t\t\tenabled: true,\n\t\t\tconfig: {\n\t\t\t\tfilters: 1\n\t\t\t}\n\t\t},\n\t\tasync before(context: BehaviorContext) {\n\t\t\tconst messageContent =\n\t\t\t\ttypeof context.message.content === \"string\"\n\t\t\t\t\t? context.message.content.substring(0, 100)\n\t\t\t\t\t: String(context.message.content || \"unknown\")\n\t\t\tlogger.debug(\n\t\t\t\t`🔍 [filter-messages] Processing message: ${messageContent}...`\n\t\t\t)\n\n\t\t\t// Create filter API wrapper\n\t\t\tconst filterAPI: FilterAPI = {\n\t\t\t\thasContent: () => filter.hasContent(context.message as any),\n\t\t\t\tisDM: () => filter.isDM(context.conversation as any),\n\t\t\t\tisGroup: () => filter.isGroup(context.conversation as any),\n\t\t\t\tisGroupAdmin: () =>\n\t\t\t\t\tfilter.isGroupAdmin(\n\t\t\t\t\t\tcontext.conversation as any,\n\t\t\t\t\t\tcontext.message as any\n\t\t\t\t\t),\n\t\t\t\tisGroupSuperAdmin: () =>\n\t\t\t\t\tfilter.isGroupSuperAdmin(\n\t\t\t\t\t\tcontext.conversation as any,\n\t\t\t\t\t\tcontext.message as any\n\t\t\t\t\t),\n\t\t\t\tisReaction: (emoji?: string, action?: \"added\" | \"removed\") => {\n\t\t\t\t\tconst isReaction = filter.isReaction(context.message as any)\n\t\t\t\t\tif (!isReaction) return false\n\n\t\t\t\t\t// Check if message has reaction content\n\t\t\t\t\tif (\n\t\t\t\t\t\t!context.message.content ||\n\t\t\t\t\t\ttypeof context.message.content !== \"object\"\n\t\t\t\t\t)\n\t\t\t\t\t\treturn false\n\n\t\t\t\t\tconst reactionContent = context.message.content as any\n\n\t\t\t\t\t// Validate reaction content structure\n\t\t\t\t\tif (!reactionContent.content) return false\n\n\t\t\t\t\t// Check emoji if specified\n\t\t\t\t\tif (emoji && reactionContent.content !== emoji) return false\n\n\t\t\t\t\t// Check action if specified\n\t\t\t\t\tif (action && reactionContent.action !== action) return false\n\n\t\t\t\t\t// If no specific checks requested, just return true\n\t\t\t\t\tif (!emoji && !action) return true\n\n\t\t\t\t\treturn true\n\t\t\t\t},\n\t\t\t\tisRemoteAttachment: () =>\n\t\t\t\t\tfilter.isRemoteAttachment(context.message as any),\n\t\t\t\tisReply: () => filter.isReply(context.message as any),\n\t\t\t\tisText: () => filter.isText(context.message as any),\n\t\t\t\tisTextReply: () => filter.isTextReply(context.message as any),\n\t\t\t\tisFromSelf: () =>\n\t\t\t\t\tcontext.message.senderInboxId === context.client.inboxId,\n\t\t\t\thasMention: (mention: string) => {\n\t\t\t\t\tconst content =\n\t\t\t\t\t\ttypeof context.message.content === \"string\"\n\t\t\t\t\t\t\t? context.message.content\n\t\t\t\t\t\t\t: String(context.message.content || \"\")\n\t\t\t\t\treturn content.includes(mention)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst passes = filters(filterAPI)\n\n\t\t\t\tif (!passes) {\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t`🔇 [filter-messages] Filter failed - message filtered out`\n\t\t\t\t\t)\n\t\t\t\t\t// Message filtered, set flag and stop the chain\n\t\t\t\t\tif (!context.sendOptions) {\n\t\t\t\t\t\tcontext.sendOptions = {}\n\t\t\t\t\t}\n\t\t\t\t\tcontext.sendOptions.filtered = true\n\t\t\t\t\t// Don't call next() - this stops the middleware chain\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tlogger.debug(`✅ [filter-messages] Filter passed`)\n\t\t\t} catch (error) {\n\t\t\t\tlogger.error(\"Error executing message filter:\", error)\n\t\t\t\tthrow error // Re-throw to propagate the error\n\t\t\t}\n\n\t\t\tlogger.debug(`✅ [filter-messages] Filter passed, continuing chain`)\n\t\t\t// Filter passed, continue to next behavior\n\t\t\tawait context.next?.()\n\t\t}\n\t}\n}\n","import type { BehaviorContext, BehaviorObject } from \"@hybrd/types\"\nimport { logger } from \"@hybrd/utils\"\nimport { ContentTypeReaction } from \"@hybrd/xmtp\"\n\nexport interface ReactWithOptions {\n\treactToAll?: boolean\n\tenabled?: boolean\n}\n\nexport function reactWith(\n\treaction: string,\n\toptions: ReactWithOptions = {}\n): BehaviorObject {\n\treturn {\n\t\tid: `react-with-${reaction}`,\n\t\tconfig: {\n\t\t\tenabled: options.enabled ?? true,\n\t\t\tconfig: {\n\t\t\t\treaction,\n\t\t\t\treactToAll: options.reactToAll ?? true\n\t\t\t}\n\t\t},\n\t\tasync before(context: BehaviorContext) {\n\t\t\tif (!this.config.enabled) return\n\n\t\t\ttry {\n\t\t\t\tconst reactionMessage = {\n\t\t\t\t\tschema: \"unicode\",\n\t\t\t\t\treference: context.message.id,\n\t\t\t\t\taction: \"added\",\n\t\t\t\t\tcontentType: ContentTypeReaction,\n\t\t\t\t\tcontent: reaction\n\t\t\t\t}\n\n\t\t\t\tawait context.conversation.send(reactionMessage, ContentTypeReaction)\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`✅ [react-with] Reacted with ${reaction} to message ${context.message.id}`\n\t\t\t\t)\n\t\t\t} catch (error) {\n\t\t\t\tlogger.error(\n\t\t\t\t\t`❌ [react-with] Failed to add reaction ${reaction}:`,\n\t\t\t\t\terror\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n}\n","import type { BehaviorContext, BehaviorObject } from \"@hybrd/types\"\n\nexport interface ThreadedReplyOptions {\n\tenabled?: boolean\n}\n\nexport function threadedReply(\n\toptions: ThreadedReplyOptions = {}\n): BehaviorObject {\n\treturn {\n\t\tid: \"threaded-reply\",\n\t\tconfig: {\n\t\t\tenabled: options.enabled ?? true,\n\t\t\tconfig: {\n\t\t\t\talwaysThread: true\n\t\t\t}\n\t\t},\n\t\tasync after(context: BehaviorContext) {\n\t\t\tif (!this.config.enabled) return\n\n\t\t\tif (!context.sendOptions) {\n\t\t\t\tcontext.sendOptions = {}\n\t\t\t}\n\t\t\tcontext.sendOptions.threaded = true\n\t\t}\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,IAAAA,eAAuB;;;ACAvB,mBAAuB;AACvB,kBAAuB;AAmBhB,SAAS,eACf,SACiB;AACjB,SAAO;AAAA,IACN,IAAI;AAAA,IACJ,QAAQ;AAAA,MACP,SAAS;AAAA,MACT,QAAQ;AAAA,QACP,SAAS;AAAA,MACV;AAAA,IACD;AAAA,IACA,MAAM,OAAO,SAA0B;AACtC,YAAM,iBACL,OAAO,QAAQ,QAAQ,YAAY,WAChC,QAAQ,QAAQ,QAAQ,UAAU,GAAG,GAAG,IACxC,OAAO,QAAQ,QAAQ,WAAW,SAAS;AAC/C,0BAAO;AAAA,QACN,mDAA4C,cAAc;AAAA,MAC3D;AAGA,YAAM,YAAuB;AAAA,QAC5B,YAAY,MAAM,mBAAO,WAAW,QAAQ,OAAc;AAAA,QAC1D,MAAM,MAAM,mBAAO,KAAK,QAAQ,YAAmB;AAAA,QACnD,SAAS,MAAM,mBAAO,QAAQ,QAAQ,YAAmB;AAAA,QACzD,cAAc,MACb,mBAAO;AAAA,UACN,QAAQ;AAAA,UACR,QAAQ;AAAA,QACT;AAAA,QACD,mBAAmB,MAClB,mBAAO;AAAA,UACN,QAAQ;AAAA,UACR,QAAQ;AAAA,QACT;AAAA,QACD,YAAY,CAAC,OAAgB,WAAiC;AAC7D,gBAAM,aAAa,mBAAO,WAAW,QAAQ,OAAc;AAC3D,cAAI,CAAC,WAAY,QAAO;AAGxB,cACC,CAAC,QAAQ,QAAQ,WACjB,OAAO,QAAQ,QAAQ,YAAY;AAEnC,mBAAO;AAER,gBAAM,kBAAkB,QAAQ,QAAQ;AAGxC,cAAI,CAAC,gBAAgB,QAAS,QAAO;AAGrC,cAAI,SAAS,gBAAgB,YAAY,MAAO,QAAO;AAGvD,cAAI,UAAU,gBAAgB,WAAW,OAAQ,QAAO;AAGxD,cAAI,CAAC,SAAS,CAAC,OAAQ,QAAO;AAE9B,iBAAO;AAAA,QACR;AAAA,QACA,oBAAoB,MACnB,mBAAO,mBAAmB,QAAQ,OAAc;AAAA,QACjD,SAAS,MAAM,mBAAO,QAAQ,QAAQ,OAAc;AAAA,QACpD,QAAQ,MAAM,mBAAO,OAAO,QAAQ,OAAc;AAAA,QAClD,aAAa,MAAM,mBAAO,YAAY,QAAQ,OAAc;AAAA,QAC5D,YAAY,MACX,QAAQ,QAAQ,kBAAkB,QAAQ,OAAO;AAAA,QAClD,YAAY,CAAC,YAAoB;AAChC,gBAAM,UACL,OAAO,QAAQ,QAAQ,YAAY,WAChC,QAAQ,QAAQ,UAChB,OAAO,QAAQ,QAAQ,WAAW,EAAE;AACxC,iBAAO,QAAQ,SAAS,OAAO;AAAA,QAChC;AAAA,MACD;AAEA,UAAI;AACH,cAAM,SAAS,QAAQ,SAAS;AAEhC,YAAI,CAAC,QAAQ;AACZ,8BAAO;AAAA,YACN;AAAA,UACD;AAEA,cAAI,CAAC,QAAQ,aAAa;AACzB,oBAAQ,cAAc,CAAC;AAAA,UACxB;AACA,kBAAQ,YAAY,WAAW;AAE/B;AAAA,QACD;AAEA,4BAAO,MAAM,wCAAmC;AAAA,MACjD,SAAS,OAAO;AACf,4BAAO,MAAM,mCAAmC,KAAK;AACrD,cAAM;AAAA,MACP;AAEA,0BAAO,MAAM,0DAAqD;AAElE,YAAM,QAAQ,OAAO;AAAA,IACtB;AAAA,EACD;AACD;;;AC7HA,IAAAC,gBAAuB;AACvB,IAAAC,eAAoC;AAO7B,SAAS,UACf,UACA,UAA4B,CAAC,GACZ;AACjB,SAAO;AAAA,IACN,IAAI,cAAc,QAAQ;AAAA,IAC1B,QAAQ;AAAA,MACP,SAAS,QAAQ,WAAW;AAAA,MAC5B,QAAQ;AAAA,QACP;AAAA,QACA,YAAY,QAAQ,cAAc;AAAA,MACnC;AAAA,IACD;AAAA,IACA,MAAM,OAAO,SAA0B;AACtC,UAAI,CAAC,KAAK,OAAO,QAAS;AAE1B,UAAI;AACH,cAAM,kBAAkB;AAAA,UACvB,QAAQ;AAAA,UACR,WAAW,QAAQ,QAAQ;AAAA,UAC3B,QAAQ;AAAA,UACR,aAAa;AAAA,UACb,SAAS;AAAA,QACV;AAEA,cAAM,QAAQ,aAAa,KAAK,iBAAiB,gCAAmB;AACpE,6BAAO;AAAA,UACN,oCAA+B,QAAQ,eAAe,QAAQ,QAAQ,EAAE;AAAA,QACzE;AAAA,MACD,SAAS,OAAO;AACf,6BAAO;AAAA,UACN,8CAAyC,QAAQ;AAAA,UACjD;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACD;;;ACxCO,SAAS,cACf,UAAgC,CAAC,GAChB;AACjB,SAAO;AAAA,IACN,IAAI;AAAA,IACJ,QAAQ;AAAA,MACP,SAAS,QAAQ,WAAW;AAAA,MAC5B,QAAQ;AAAA,QACP,cAAc;AAAA,MACf;AAAA,IACD;AAAA,IACA,MAAM,MAAM,SAA0B;AACrC,UAAI,CAAC,KAAK,OAAO,QAAS;AAE1B,UAAI,CAAC,QAAQ,aAAa;AACzB,gBAAQ,cAAc,CAAC;AAAA,MACxB;AACA,cAAQ,YAAY,WAAW;AAAA,IAChC;AAAA,EACD;AACD;;;AHlBA,mBAAqC;","names":["import_xmtp","import_utils","import_xmtp"]}
@@ -3,8 +3,6 @@ import { BehaviorObject } from '@hybrd/types';
3
3
  export { Behavior, BehaviorConfig, BehaviorContext, BehaviorObject, BehaviorRegistry, BehaviorRegistryImpl } from '@hybrd/types';
4
4
 
5
5
  interface FilterAPI {
6
- fromSelf(): boolean;
7
- hasContent(): boolean;
8
6
  isDM(): boolean;
9
7
  isGroup(): boolean;
10
8
  isGroupAdmin(): boolean;
@@ -15,7 +13,9 @@ interface FilterAPI {
15
13
  isReply(): boolean;
16
14
  isText(): boolean;
17
15
  isTextReply(): boolean;
16
+ isFromSelf(): boolean;
18
17
  hasMention(mention: string): boolean;
18
+ hasContent(): boolean;
19
19
  }
20
20
  declare function filterMessages(filters: (api: FilterAPI) => boolean): BehaviorObject;
21
21
 
@@ -3,8 +3,6 @@ import { BehaviorObject } from '@hybrd/types';
3
3
  export { Behavior, BehaviorConfig, BehaviorContext, BehaviorObject, BehaviorRegistry, BehaviorRegistryImpl } from '@hybrd/types';
4
4
 
5
5
  interface FilterAPI {
6
- fromSelf(): boolean;
7
- hasContent(): boolean;
8
6
  isDM(): boolean;
9
7
  isGroup(): boolean;
10
8
  isGroupAdmin(): boolean;
@@ -15,7 +13,9 @@ interface FilterAPI {
15
13
  isReply(): boolean;
16
14
  isText(): boolean;
17
15
  isTextReply(): boolean;
16
+ isFromSelf(): boolean;
18
17
  hasMention(mention: string): boolean;
18
+ hasContent(): boolean;
19
19
  }
20
20
  declare function filterMessages(filters: (api: FilterAPI) => boolean): BehaviorObject;
21
21
 
package/dist/behaviors.js CHANGED
@@ -19,7 +19,6 @@ function filterMessages(filters) {
19
19
  `\u{1F50D} [filter-messages] Processing message: ${messageContent}...`
20
20
  );
21
21
  const filterAPI = {
22
- fromSelf: () => filter.fromSelf(context.message, context.client),
23
22
  hasContent: () => filter.hasContent(context.message),
24
23
  isDM: () => filter.isDM(context.conversation),
25
24
  isGroup: () => filter.isGroup(context.conversation),
@@ -47,6 +46,7 @@ function filterMessages(filters) {
47
46
  isReply: () => filter.isReply(context.message),
48
47
  isText: () => filter.isText(context.message),
49
48
  isTextReply: () => filter.isTextReply(context.message),
49
+ isFromSelf: () => context.message.senderInboxId === context.client.inboxId,
50
50
  hasMention: (mention) => {
51
51
  const content = typeof context.message.content === "string" ? context.message.content : String(context.message.content || "");
52
52
  return content.includes(mention);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/behaviors/index.ts","../src/behaviors/filter-messages.ts","../src/behaviors/react-with.ts","../src/behaviors/threaded-reply.ts"],"sourcesContent":["// Re-export XMTP Agent SDK filters for convenience\nexport { filter } from \"@hybrd/xmtp\"\n\nexport * from \"./filter-messages\"\nexport * from \"./react-with\"\nexport * from \"./threaded-reply\"\n\n// Re-export behavior types for convenience\nexport { BehaviorRegistryImpl } from \"@hybrd/types\"\nexport type {\n\tBehavior,\n\tBehaviorConfig,\n\tBehaviorContext,\n\tBehaviorObject,\n\tBehaviorRegistry\n} from \"@hybrd/types\"\n","import type { BehaviorContext, BehaviorObject } from \"@hybrd/types\"\nimport { logger } from \"@hybrd/utils\"\nimport { filter } from \"@hybrd/xmtp\"\n\n// Filter interface that matches XMTP SDK signatures\ninterface FilterAPI {\n\tfromSelf(): boolean\n\thasContent(): boolean\n\tisDM(): boolean\n\tisGroup(): boolean\n\tisGroupAdmin(): boolean\n\tisGroupSuperAdmin(): boolean\n\tisReaction(): boolean\n\tisReaction(emoji?: string, action?: \"added\" | \"removed\"): boolean\n\tisRemoteAttachment(): boolean\n\tisReply(): boolean\n\tisText(): boolean\n\tisTextReply(): boolean\n\thasMention(mention: string): boolean\n}\n\nexport function filterMessages(\n\tfilters: (api: FilterAPI) => boolean\n): BehaviorObject {\n\treturn {\n\t\tid: \"filter-messages\",\n\t\tconfig: {\n\t\t\tenabled: true,\n\t\t\tconfig: {\n\t\t\t\tfilters: 1\n\t\t\t}\n\t\t},\n\t\tasync before(context: BehaviorContext) {\n\t\t\tconst messageContent =\n\t\t\t\ttypeof context.message.content === \"string\"\n\t\t\t\t\t? context.message.content.substring(0, 100)\n\t\t\t\t\t: String(context.message.content || \"unknown\")\n\t\t\tlogger.debug(\n\t\t\t\t`🔍 [filter-messages] Processing message: ${messageContent}...`\n\t\t\t)\n\n\t\t\t// Create filter API wrapper\n\t\t\tconst filterAPI: FilterAPI = {\n\t\t\t\tfromSelf: () =>\n\t\t\t\t\tfilter.fromSelf(context.message as any, context.client as any),\n\t\t\t\thasContent: () => filter.hasContent(context.message as any),\n\t\t\t\tisDM: () => filter.isDM(context.conversation as any),\n\t\t\t\tisGroup: () => filter.isGroup(context.conversation as any),\n\t\t\t\tisGroupAdmin: () =>\n\t\t\t\t\tfilter.isGroupAdmin(\n\t\t\t\t\t\tcontext.conversation as any,\n\t\t\t\t\t\tcontext.message as any\n\t\t\t\t\t),\n\t\t\t\tisGroupSuperAdmin: () =>\n\t\t\t\t\tfilter.isGroupSuperAdmin(\n\t\t\t\t\t\tcontext.conversation as any,\n\t\t\t\t\t\tcontext.message as any\n\t\t\t\t\t),\n\t\t\t\tisReaction: (emoji?: string, action?: \"added\" | \"removed\") => {\n\t\t\t\t\tconst isReaction = filter.isReaction(context.message as any)\n\t\t\t\t\tif (!isReaction) return false\n\n\t\t\t\t\t// Check if message has reaction content\n\t\t\t\t\tif (\n\t\t\t\t\t\t!context.message.content ||\n\t\t\t\t\t\ttypeof context.message.content !== \"object\"\n\t\t\t\t\t)\n\t\t\t\t\t\treturn false\n\n\t\t\t\t\tconst reactionContent = context.message.content as any\n\n\t\t\t\t\t// Validate reaction content structure\n\t\t\t\t\tif (!reactionContent.content) return false\n\n\t\t\t\t\t// Check emoji if specified\n\t\t\t\t\tif (emoji && reactionContent.content !== emoji) return false\n\n\t\t\t\t\t// Check action if specified\n\t\t\t\t\tif (action && reactionContent.action !== action) return false\n\n\t\t\t\t\t// If no specific checks requested, just return true\n\t\t\t\t\tif (!emoji && !action) return true\n\n\t\t\t\t\treturn true\n\t\t\t\t},\n\t\t\t\tisRemoteAttachment: () =>\n\t\t\t\t\tfilter.isRemoteAttachment(context.message as any),\n\t\t\t\tisReply: () => filter.isReply(context.message as any),\n\t\t\t\tisText: () => filter.isText(context.message as any),\n\t\t\t\tisTextReply: () => filter.isTextReply(context.message as any),\n\t\t\t\thasMention: (mention: string) => {\n\t\t\t\t\tconst content =\n\t\t\t\t\t\ttypeof context.message.content === \"string\"\n\t\t\t\t\t\t\t? context.message.content\n\t\t\t\t\t\t\t: String(context.message.content || \"\")\n\t\t\t\t\treturn content.includes(mention)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst passes = filters(filterAPI)\n\n\t\t\t\tif (!passes) {\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t`🔇 [filter-messages] Filter failed - message filtered out`\n\t\t\t\t\t)\n\t\t\t\t\t// Message filtered, set flag and stop the chain\n\t\t\t\t\tif (!context.sendOptions) {\n\t\t\t\t\t\tcontext.sendOptions = {}\n\t\t\t\t\t}\n\t\t\t\t\tcontext.sendOptions.filtered = true\n\t\t\t\t\t// Don't call next() - this stops the middleware chain\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tlogger.debug(`✅ [filter-messages] Filter passed`)\n\t\t\t} catch (error) {\n\t\t\t\tlogger.error(\"Error executing message filter:\", error)\n\t\t\t\tthrow error // Re-throw to propagate the error\n\t\t\t}\n\n\t\t\tlogger.debug(`✅ [filter-messages] Filter passed, continuing chain`)\n\t\t\t// Filter passed, continue to next behavior\n\t\t\tawait context.next?.()\n\t\t}\n\t}\n}\n","import type { BehaviorContext, BehaviorObject } from \"@hybrd/types\"\nimport { logger } from \"@hybrd/utils\"\nimport { ContentTypeReaction } from \"@hybrd/xmtp\"\n\nexport interface ReactWithOptions {\n\treactToAll?: boolean\n\tenabled?: boolean\n}\n\nexport function reactWith(\n\treaction: string,\n\toptions: ReactWithOptions = {}\n): BehaviorObject {\n\treturn {\n\t\tid: `react-with-${reaction}`,\n\t\tconfig: {\n\t\t\tenabled: options.enabled ?? true,\n\t\t\tconfig: {\n\t\t\t\treaction,\n\t\t\t\treactToAll: options.reactToAll ?? true\n\t\t\t}\n\t\t},\n\t\tasync before(context: BehaviorContext) {\n\t\t\tif (!this.config.enabled) return\n\n\t\t\ttry {\n\t\t\t\tconst reactionMessage = {\n\t\t\t\t\tschema: \"unicode\",\n\t\t\t\t\treference: context.message.id,\n\t\t\t\t\taction: \"added\",\n\t\t\t\t\tcontentType: ContentTypeReaction,\n\t\t\t\t\tcontent: reaction\n\t\t\t\t}\n\n\t\t\t\tawait context.conversation.send(reactionMessage, ContentTypeReaction)\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`✅ [react-with] Reacted with ${reaction} to message ${context.message.id}`\n\t\t\t\t)\n\t\t\t} catch (error) {\n\t\t\t\tlogger.error(\n\t\t\t\t\t`❌ [react-with] Failed to add reaction ${reaction}:`,\n\t\t\t\t\terror\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n}\n","import type { BehaviorContext, BehaviorObject } from \"@hybrd/types\"\n\nexport interface ThreadedReplyOptions {\n\tenabled?: boolean\n}\n\nexport function threadedReply(\n\toptions: ThreadedReplyOptions = {}\n): BehaviorObject {\n\treturn {\n\t\tid: \"threaded-reply\",\n\t\tconfig: {\n\t\t\tenabled: options.enabled ?? true,\n\t\t\tconfig: {\n\t\t\t\talwaysThread: true\n\t\t\t}\n\t\t},\n\t\tasync after(context: BehaviorContext) {\n\t\t\tif (!this.config.enabled) return\n\n\t\t\tif (!context.sendOptions) {\n\t\t\t\tcontext.sendOptions = {}\n\t\t\t}\n\t\t\tcontext.sendOptions.threaded = true\n\t\t}\n\t}\n}\n"],"mappings":"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","names":["filter","logger"]}
1
+ {"version":3,"sources":["../src/behaviors/index.ts","../src/behaviors/filter-messages.ts","../src/behaviors/react-with.ts","../src/behaviors/threaded-reply.ts"],"sourcesContent":["// Re-export XMTP Agent SDK filters for convenience\nexport { filter } from \"@hybrd/xmtp\"\n\nexport * from \"./filter-messages\"\nexport * from \"./react-with\"\nexport * from \"./threaded-reply\"\n\n// Re-export behavior types for convenience\nexport { BehaviorRegistryImpl } from \"@hybrd/types\"\nexport type {\n\tBehavior,\n\tBehaviorConfig,\n\tBehaviorContext,\n\tBehaviorObject,\n\tBehaviorRegistry\n} from \"@hybrd/types\"\n","import type { BehaviorContext, BehaviorObject } from \"@hybrd/types\"\nimport { logger } from \"@hybrd/utils\"\nimport { filter } from \"@hybrd/xmtp\"\n\n// Filter interface that matches XMTP SDK signatures\ninterface FilterAPI {\n\tisDM(): boolean\n\tisGroup(): boolean\n\tisGroupAdmin(): boolean\n\tisGroupSuperAdmin(): boolean\n\tisReaction(): boolean\n\tisReaction(emoji?: string, action?: \"added\" | \"removed\"): boolean\n\tisRemoteAttachment(): boolean\n\tisReply(): boolean\n\tisText(): boolean\n\tisTextReply(): boolean\n\tisFromSelf(): boolean\n\thasMention(mention: string): boolean\n\thasContent(): boolean\n}\n\nexport function filterMessages(\n\tfilters: (api: FilterAPI) => boolean\n): BehaviorObject {\n\treturn {\n\t\tid: \"filter-messages\",\n\t\tconfig: {\n\t\t\tenabled: true,\n\t\t\tconfig: {\n\t\t\t\tfilters: 1\n\t\t\t}\n\t\t},\n\t\tasync before(context: BehaviorContext) {\n\t\t\tconst messageContent =\n\t\t\t\ttypeof context.message.content === \"string\"\n\t\t\t\t\t? context.message.content.substring(0, 100)\n\t\t\t\t\t: String(context.message.content || \"unknown\")\n\t\t\tlogger.debug(\n\t\t\t\t`🔍 [filter-messages] Processing message: ${messageContent}...`\n\t\t\t)\n\n\t\t\t// Create filter API wrapper\n\t\t\tconst filterAPI: FilterAPI = {\n\t\t\t\thasContent: () => filter.hasContent(context.message as any),\n\t\t\t\tisDM: () => filter.isDM(context.conversation as any),\n\t\t\t\tisGroup: () => filter.isGroup(context.conversation as any),\n\t\t\t\tisGroupAdmin: () =>\n\t\t\t\t\tfilter.isGroupAdmin(\n\t\t\t\t\t\tcontext.conversation as any,\n\t\t\t\t\t\tcontext.message as any\n\t\t\t\t\t),\n\t\t\t\tisGroupSuperAdmin: () =>\n\t\t\t\t\tfilter.isGroupSuperAdmin(\n\t\t\t\t\t\tcontext.conversation as any,\n\t\t\t\t\t\tcontext.message as any\n\t\t\t\t\t),\n\t\t\t\tisReaction: (emoji?: string, action?: \"added\" | \"removed\") => {\n\t\t\t\t\tconst isReaction = filter.isReaction(context.message as any)\n\t\t\t\t\tif (!isReaction) return false\n\n\t\t\t\t\t// Check if message has reaction content\n\t\t\t\t\tif (\n\t\t\t\t\t\t!context.message.content ||\n\t\t\t\t\t\ttypeof context.message.content !== \"object\"\n\t\t\t\t\t)\n\t\t\t\t\t\treturn false\n\n\t\t\t\t\tconst reactionContent = context.message.content as any\n\n\t\t\t\t\t// Validate reaction content structure\n\t\t\t\t\tif (!reactionContent.content) return false\n\n\t\t\t\t\t// Check emoji if specified\n\t\t\t\t\tif (emoji && reactionContent.content !== emoji) return false\n\n\t\t\t\t\t// Check action if specified\n\t\t\t\t\tif (action && reactionContent.action !== action) return false\n\n\t\t\t\t\t// If no specific checks requested, just return true\n\t\t\t\t\tif (!emoji && !action) return true\n\n\t\t\t\t\treturn true\n\t\t\t\t},\n\t\t\t\tisRemoteAttachment: () =>\n\t\t\t\t\tfilter.isRemoteAttachment(context.message as any),\n\t\t\t\tisReply: () => filter.isReply(context.message as any),\n\t\t\t\tisText: () => filter.isText(context.message as any),\n\t\t\t\tisTextReply: () => filter.isTextReply(context.message as any),\n\t\t\t\tisFromSelf: () =>\n\t\t\t\t\tcontext.message.senderInboxId === context.client.inboxId,\n\t\t\t\thasMention: (mention: string) => {\n\t\t\t\t\tconst content =\n\t\t\t\t\t\ttypeof context.message.content === \"string\"\n\t\t\t\t\t\t\t? context.message.content\n\t\t\t\t\t\t\t: String(context.message.content || \"\")\n\t\t\t\t\treturn content.includes(mention)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst passes = filters(filterAPI)\n\n\t\t\t\tif (!passes) {\n\t\t\t\t\tlogger.debug(\n\t\t\t\t\t\t`🔇 [filter-messages] Filter failed - message filtered out`\n\t\t\t\t\t)\n\t\t\t\t\t// Message filtered, set flag and stop the chain\n\t\t\t\t\tif (!context.sendOptions) {\n\t\t\t\t\t\tcontext.sendOptions = {}\n\t\t\t\t\t}\n\t\t\t\t\tcontext.sendOptions.filtered = true\n\t\t\t\t\t// Don't call next() - this stops the middleware chain\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tlogger.debug(`✅ [filter-messages] Filter passed`)\n\t\t\t} catch (error) {\n\t\t\t\tlogger.error(\"Error executing message filter:\", error)\n\t\t\t\tthrow error // Re-throw to propagate the error\n\t\t\t}\n\n\t\t\tlogger.debug(`✅ [filter-messages] Filter passed, continuing chain`)\n\t\t\t// Filter passed, continue to next behavior\n\t\t\tawait context.next?.()\n\t\t}\n\t}\n}\n","import type { BehaviorContext, BehaviorObject } from \"@hybrd/types\"\nimport { logger } from \"@hybrd/utils\"\nimport { ContentTypeReaction } from \"@hybrd/xmtp\"\n\nexport interface ReactWithOptions {\n\treactToAll?: boolean\n\tenabled?: boolean\n}\n\nexport function reactWith(\n\treaction: string,\n\toptions: ReactWithOptions = {}\n): BehaviorObject {\n\treturn {\n\t\tid: `react-with-${reaction}`,\n\t\tconfig: {\n\t\t\tenabled: options.enabled ?? true,\n\t\t\tconfig: {\n\t\t\t\treaction,\n\t\t\t\treactToAll: options.reactToAll ?? true\n\t\t\t}\n\t\t},\n\t\tasync before(context: BehaviorContext) {\n\t\t\tif (!this.config.enabled) return\n\n\t\t\ttry {\n\t\t\t\tconst reactionMessage = {\n\t\t\t\t\tschema: \"unicode\",\n\t\t\t\t\treference: context.message.id,\n\t\t\t\t\taction: \"added\",\n\t\t\t\t\tcontentType: ContentTypeReaction,\n\t\t\t\t\tcontent: reaction\n\t\t\t\t}\n\n\t\t\t\tawait context.conversation.send(reactionMessage, ContentTypeReaction)\n\t\t\t\tlogger.debug(\n\t\t\t\t\t`✅ [react-with] Reacted with ${reaction} to message ${context.message.id}`\n\t\t\t\t)\n\t\t\t} catch (error) {\n\t\t\t\tlogger.error(\n\t\t\t\t\t`❌ [react-with] Failed to add reaction ${reaction}:`,\n\t\t\t\t\terror\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n}\n","import type { BehaviorContext, BehaviorObject } from \"@hybrd/types\"\n\nexport interface ThreadedReplyOptions {\n\tenabled?: boolean\n}\n\nexport function threadedReply(\n\toptions: ThreadedReplyOptions = {}\n): BehaviorObject {\n\treturn {\n\t\tid: \"threaded-reply\",\n\t\tconfig: {\n\t\t\tenabled: options.enabled ?? true,\n\t\t\tconfig: {\n\t\t\t\talwaysThread: true\n\t\t\t}\n\t\t},\n\t\tasync after(context: BehaviorContext) {\n\t\t\tif (!this.config.enabled) return\n\n\t\t\tif (!context.sendOptions) {\n\t\t\t\tcontext.sendOptions = {}\n\t\t\t}\n\t\t\tcontext.sendOptions.threaded = true\n\t\t}\n\t}\n}\n"],"mappings":"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","names":["filter","logger"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hybrid",
3
- "version": "1.4.3",
3
+ "version": "1.4.5",
4
4
  "keywords": [
5
5
  "xmtp",
6
6
  "agent",
@@ -49,10 +49,10 @@
49
49
  "viem": "^2.33.3",
50
50
  "wagmi": "^2.15.6",
51
51
  "zod": "^3.22.0 || ^4",
52
- "@hybrd/types": "1.4.3",
53
- "@hybrd/xmtp": "1.4.3",
54
- "@hybrd/utils": "1.4.3",
55
- "@hybrd/ponder": "1.4.3"
52
+ "@hybrd/ponder": "1.4.5",
53
+ "@hybrd/utils": "1.4.5",
54
+ "@hybrd/types": "1.4.5",
55
+ "@hybrd/xmtp": "1.4.5"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/degit": "^2.8.4",
@@ -13,7 +13,7 @@ import { filter as xmtpFilter } from "@hybrd/xmtp"
13
13
  // Mock the XMTP filter
14
14
  vi.mock("@hybrd/xmtp", () => ({
15
15
  filter: {
16
- fromSelf: vi.fn(() => false),
16
+ isFromSelf: vi.fn(() => false),
17
17
  hasContent: vi.fn(() => true),
18
18
  isDM: vi.fn(() => false),
19
19
  isGroup: vi.fn(() => true),
@@ -124,7 +124,7 @@ describe("Filter Messages Behavior", () => {
124
124
 
125
125
  it("should work with callback syntax", () => {
126
126
  const behavior = filterMessages(
127
- (filter) => filter.isText() && !filter.fromSelf()
127
+ (filter) => filter.isText() && !filter.isFromSelf()
128
128
  )
129
129
 
130
130
  expect(behavior.id).toBe("filter-messages")
@@ -140,7 +140,7 @@ describe("Filter Messages Behavior", () => {
140
140
  }
141
141
 
142
142
  const behavior = filterMessages(
143
- (filter) => filter.isText() && !filter.fromSelf()
143
+ (filter) => filter.isText() && !filter.isFromSelf()
144
144
  )
145
145
 
146
146
  await behavior.before?.(context)
@@ -417,4 +417,84 @@ describe("Filter Messages Behavior", () => {
417
417
 
418
418
  expect(context.sendOptions?.filtered).toBe(true)
419
419
  })
420
+
421
+ describe("isFromSelf filter", () => {
422
+ it("should return true when message is from self", async () => {
423
+ const context: BehaviorContext = {
424
+ runtime: {
425
+ sender: {
426
+ address: "0x1234567890123456789012345678901234567890",
427
+ inboxId: "agent-inbox",
428
+ name: "Agent"
429
+ },
430
+ subjects: {}
431
+ } as any,
432
+ client: { inboxId: "agent-inbox" } as any,
433
+ conversation: mockConversation,
434
+ message: {
435
+ id: "test-message",
436
+ senderInboxId: "agent-inbox",
437
+ content: "test"
438
+ } as XmtpMessage,
439
+ sendOptions: {}
440
+ }
441
+
442
+ const behavior = filterMessages((filter) => filter.isFromSelf())
443
+ await behavior.before?.(context)
444
+
445
+ expect(context.sendOptions?.filtered).toBeUndefined()
446
+ })
447
+
448
+ it("should return false when message is not from self", async () => {
449
+ const context: BehaviorContext = {
450
+ runtime: {
451
+ sender: {
452
+ address: "0x9999999999999999999999999999999999999999",
453
+ inboxId: "other-inbox",
454
+ name: "Other User"
455
+ },
456
+ subjects: {}
457
+ } as any,
458
+ client: { inboxId: "agent-inbox" } as any,
459
+ conversation: mockConversation,
460
+ message: {
461
+ id: "test-message",
462
+ senderInboxId: "other-inbox",
463
+ content: "test"
464
+ } as XmtpMessage,
465
+ sendOptions: {}
466
+ }
467
+
468
+ const behavior = filterMessages((filter) => !filter.isFromSelf())
469
+ await behavior.before?.(context)
470
+
471
+ expect(context.sendOptions?.filtered).toBeUndefined()
472
+ })
473
+
474
+ it("should filter out message when isFromSelf returns false and filter expects true", async () => {
475
+ const context: BehaviorContext = {
476
+ runtime: {
477
+ sender: {
478
+ address: "0x9999999999999999999999999999999999999999",
479
+ inboxId: "other-inbox",
480
+ name: "Other User"
481
+ },
482
+ subjects: {}
483
+ } as any,
484
+ client: { inboxId: "agent-inbox" } as any,
485
+ conversation: mockConversation,
486
+ message: {
487
+ id: "test-message",
488
+ senderInboxId: "other-inbox",
489
+ content: "test"
490
+ } as XmtpMessage,
491
+ sendOptions: {}
492
+ }
493
+
494
+ const behavior = filterMessages((filter) => filter.isFromSelf())
495
+ await behavior.before?.(context)
496
+
497
+ expect(context.sendOptions?.filtered).toBe(true)
498
+ })
499
+ })
420
500
  })
@@ -4,8 +4,6 @@ import { filter } from "@hybrd/xmtp"
4
4
 
5
5
  // Filter interface that matches XMTP SDK signatures
6
6
  interface FilterAPI {
7
- fromSelf(): boolean
8
- hasContent(): boolean
9
7
  isDM(): boolean
10
8
  isGroup(): boolean
11
9
  isGroupAdmin(): boolean
@@ -16,7 +14,9 @@ interface FilterAPI {
16
14
  isReply(): boolean
17
15
  isText(): boolean
18
16
  isTextReply(): boolean
17
+ isFromSelf(): boolean
19
18
  hasMention(mention: string): boolean
19
+ hasContent(): boolean
20
20
  }
21
21
 
22
22
  export function filterMessages(
@@ -41,8 +41,6 @@ export function filterMessages(
41
41
 
42
42
  // Create filter API wrapper
43
43
  const filterAPI: FilterAPI = {
44
- fromSelf: () =>
45
- filter.fromSelf(context.message as any, context.client as any),
46
44
  hasContent: () => filter.hasContent(context.message as any),
47
45
  isDM: () => filter.isDM(context.conversation as any),
48
46
  isGroup: () => filter.isGroup(context.conversation as any),
@@ -88,6 +86,8 @@ export function filterMessages(
88
86
  isReply: () => filter.isReply(context.message as any),
89
87
  isText: () => filter.isText(context.message as any),
90
88
  isTextReply: () => filter.isTextReply(context.message as any),
89
+ isFromSelf: () =>
90
+ context.message.senderInboxId === context.client.inboxId,
91
91
  hasMention: (mention: string) => {
92
92
  const content =
93
93
  typeof context.message.content === "string"