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.
- package/dist/behaviors.cjs +1 -1
- package/dist/behaviors.cjs.map +1 -1
- package/dist/behaviors.d.cts +2 -2
- package/dist/behaviors.d.ts +2 -2
- package/dist/behaviors.js +1 -1
- package/dist/behaviors.js.map +1 -1
- package/package.json +5 -5
- package/src/behaviors/filter-messages.test.ts +83 -3
- package/src/behaviors/filter-messages.ts +4 -4
package/dist/behaviors.cjs
CHANGED
|
@@ -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);
|
package/dist/behaviors.cjs.map
CHANGED
|
@@ -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\
|
|
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":["import_xmtp","import_utils","import_xmtp"]}
|
package/dist/behaviors.d.cts
CHANGED
|
@@ -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.d.ts
CHANGED
|
@@ -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);
|
package/dist/behaviors.js.map
CHANGED
|
@@ -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\
|
|
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
|
+
"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/
|
|
53
|
-
"@hybrd/
|
|
54
|
-
"@hybrd/
|
|
55
|
-
"@hybrd/
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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"
|