@herdctl/discord 0.0.1

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.
Files changed (137) hide show
  1. package/dist/__tests__/auto-mode-handler.test.d.ts +2 -0
  2. package/dist/__tests__/auto-mode-handler.test.d.ts.map +1 -0
  3. package/dist/__tests__/auto-mode-handler.test.js +362 -0
  4. package/dist/__tests__/auto-mode-handler.test.js.map +1 -0
  5. package/dist/__tests__/discord-connector.test.d.ts +2 -0
  6. package/dist/__tests__/discord-connector.test.d.ts.map +1 -0
  7. package/dist/__tests__/discord-connector.test.js +958 -0
  8. package/dist/__tests__/discord-connector.test.js.map +1 -0
  9. package/dist/__tests__/error-handler.test.d.ts +2 -0
  10. package/dist/__tests__/error-handler.test.d.ts.map +1 -0
  11. package/dist/__tests__/error-handler.test.js +509 -0
  12. package/dist/__tests__/error-handler.test.js.map +1 -0
  13. package/dist/__tests__/errors.test.d.ts +2 -0
  14. package/dist/__tests__/errors.test.d.ts.map +1 -0
  15. package/dist/__tests__/errors.test.js +152 -0
  16. package/dist/__tests__/errors.test.js.map +1 -0
  17. package/dist/__tests__/logger.test.d.ts +2 -0
  18. package/dist/__tests__/logger.test.d.ts.map +1 -0
  19. package/dist/__tests__/logger.test.js +282 -0
  20. package/dist/__tests__/logger.test.js.map +1 -0
  21. package/dist/__tests__/mention-handler.test.d.ts +2 -0
  22. package/dist/__tests__/mention-handler.test.d.ts.map +1 -0
  23. package/dist/__tests__/mention-handler.test.js +547 -0
  24. package/dist/__tests__/mention-handler.test.js.map +1 -0
  25. package/dist/auto-mode-handler.d.ts +145 -0
  26. package/dist/auto-mode-handler.d.ts.map +1 -0
  27. package/dist/auto-mode-handler.js +211 -0
  28. package/dist/auto-mode-handler.js.map +1 -0
  29. package/dist/commands/__tests__/command-manager.test.d.ts +2 -0
  30. package/dist/commands/__tests__/command-manager.test.d.ts.map +1 -0
  31. package/dist/commands/__tests__/command-manager.test.js +307 -0
  32. package/dist/commands/__tests__/command-manager.test.js.map +1 -0
  33. package/dist/commands/__tests__/help.test.d.ts +2 -0
  34. package/dist/commands/__tests__/help.test.d.ts.map +1 -0
  35. package/dist/commands/__tests__/help.test.js +105 -0
  36. package/dist/commands/__tests__/help.test.js.map +1 -0
  37. package/dist/commands/__tests__/reset.test.d.ts +2 -0
  38. package/dist/commands/__tests__/reset.test.d.ts.map +1 -0
  39. package/dist/commands/__tests__/reset.test.js +140 -0
  40. package/dist/commands/__tests__/reset.test.js.map +1 -0
  41. package/dist/commands/__tests__/status.test.d.ts +2 -0
  42. package/dist/commands/__tests__/status.test.d.ts.map +1 -0
  43. package/dist/commands/__tests__/status.test.js +205 -0
  44. package/dist/commands/__tests__/status.test.js.map +1 -0
  45. package/dist/commands/command-manager.d.ts +66 -0
  46. package/dist/commands/command-manager.d.ts.map +1 -0
  47. package/dist/commands/command-manager.js +191 -0
  48. package/dist/commands/command-manager.js.map +1 -0
  49. package/dist/commands/help.d.ts +8 -0
  50. package/dist/commands/help.d.ts.map +1 -0
  51. package/dist/commands/help.js +27 -0
  52. package/dist/commands/help.js.map +1 -0
  53. package/dist/commands/index.d.ts +12 -0
  54. package/dist/commands/index.d.ts.map +1 -0
  55. package/dist/commands/index.js +13 -0
  56. package/dist/commands/index.js.map +1 -0
  57. package/dist/commands/reset.d.ts +9 -0
  58. package/dist/commands/reset.d.ts.map +1 -0
  59. package/dist/commands/reset.js +28 -0
  60. package/dist/commands/reset.js.map +1 -0
  61. package/dist/commands/status.d.ts +9 -0
  62. package/dist/commands/status.d.ts.map +1 -0
  63. package/dist/commands/status.js +102 -0
  64. package/dist/commands/status.js.map +1 -0
  65. package/dist/commands/types.d.ts +87 -0
  66. package/dist/commands/types.d.ts.map +1 -0
  67. package/dist/commands/types.js +8 -0
  68. package/dist/commands/types.js.map +1 -0
  69. package/dist/discord-connector.d.ts +154 -0
  70. package/dist/discord-connector.d.ts.map +1 -0
  71. package/dist/discord-connector.js +638 -0
  72. package/dist/discord-connector.js.map +1 -0
  73. package/dist/error-handler.d.ts +237 -0
  74. package/dist/error-handler.d.ts.map +1 -0
  75. package/dist/error-handler.js +433 -0
  76. package/dist/error-handler.js.map +1 -0
  77. package/dist/errors.d.ts +61 -0
  78. package/dist/errors.d.ts.map +1 -0
  79. package/dist/errors.js +77 -0
  80. package/dist/errors.js.map +1 -0
  81. package/dist/index.d.ts +34 -0
  82. package/dist/index.d.ts.map +1 -0
  83. package/dist/index.js +36 -0
  84. package/dist/index.js.map +1 -0
  85. package/dist/logger.d.ts +119 -0
  86. package/dist/logger.d.ts.map +1 -0
  87. package/dist/logger.js +198 -0
  88. package/dist/logger.js.map +1 -0
  89. package/dist/mention-handler.d.ts +176 -0
  90. package/dist/mention-handler.d.ts.map +1 -0
  91. package/dist/mention-handler.js +236 -0
  92. package/dist/mention-handler.js.map +1 -0
  93. package/dist/session-manager/__tests__/errors.test.d.ts +2 -0
  94. package/dist/session-manager/__tests__/errors.test.d.ts.map +1 -0
  95. package/dist/session-manager/__tests__/errors.test.js +124 -0
  96. package/dist/session-manager/__tests__/errors.test.js.map +1 -0
  97. package/dist/session-manager/__tests__/session-manager.test.d.ts +2 -0
  98. package/dist/session-manager/__tests__/session-manager.test.d.ts.map +1 -0
  99. package/dist/session-manager/__tests__/session-manager.test.js +517 -0
  100. package/dist/session-manager/__tests__/session-manager.test.js.map +1 -0
  101. package/dist/session-manager/__tests__/types.test.d.ts +2 -0
  102. package/dist/session-manager/__tests__/types.test.d.ts.map +1 -0
  103. package/dist/session-manager/__tests__/types.test.js +169 -0
  104. package/dist/session-manager/__tests__/types.test.js.map +1 -0
  105. package/dist/session-manager/errors.d.ts +58 -0
  106. package/dist/session-manager/errors.d.ts.map +1 -0
  107. package/dist/session-manager/errors.js +70 -0
  108. package/dist/session-manager/errors.js.map +1 -0
  109. package/dist/session-manager/index.d.ts +11 -0
  110. package/dist/session-manager/index.d.ts.map +1 -0
  111. package/dist/session-manager/index.js +12 -0
  112. package/dist/session-manager/index.js.map +1 -0
  113. package/dist/session-manager/session-manager.d.ts +107 -0
  114. package/dist/session-manager/session-manager.d.ts.map +1 -0
  115. package/dist/session-manager/session-manager.js +347 -0
  116. package/dist/session-manager/session-manager.js.map +1 -0
  117. package/dist/session-manager/types.d.ts +167 -0
  118. package/dist/session-manager/types.d.ts.map +1 -0
  119. package/dist/session-manager/types.js +57 -0
  120. package/dist/session-manager/types.js.map +1 -0
  121. package/dist/types.d.ts +323 -0
  122. package/dist/types.d.ts.map +1 -0
  123. package/dist/types.js +8 -0
  124. package/dist/types.js.map +1 -0
  125. package/dist/utils/__tests__/formatting.test.d.ts +2 -0
  126. package/dist/utils/__tests__/formatting.test.d.ts.map +1 -0
  127. package/dist/utils/__tests__/formatting.test.js +571 -0
  128. package/dist/utils/__tests__/formatting.test.js.map +1 -0
  129. package/dist/utils/formatting.d.ts +211 -0
  130. package/dist/utils/formatting.d.ts.map +1 -0
  131. package/dist/utils/formatting.js +305 -0
  132. package/dist/utils/formatting.js.map +1 -0
  133. package/dist/utils/index.d.ts +5 -0
  134. package/dist/utils/index.d.ts.map +1 -0
  135. package/dist/utils/index.js +9 -0
  136. package/dist/utils/index.js.map +1 -0
  137. package/package.json +49 -0
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Auto mode handler for Discord DMs and dedicated channels
3
+ *
4
+ * Provides utilities for:
5
+ * - Checking if DMs should be processed (auto mode by default)
6
+ * - Filtering users via allowlist/blocklist
7
+ * - Determining channel mode configuration
8
+ */
9
+ import type { DiscordDM, DiscordChannel, DiscordGuild } from "@herdctl/core";
10
+ /**
11
+ * Result of checking if a user is allowed to DM the bot
12
+ */
13
+ export interface DMFilterResult {
14
+ /** Whether the user is allowed to send DMs */
15
+ allowed: boolean;
16
+ /** Reason for filtering decision */
17
+ reason: "allowed" | "dm_disabled" | "not_in_allowlist" | "in_blocklist";
18
+ }
19
+ /**
20
+ * Configuration for DM handling
21
+ */
22
+ export interface DMConfig {
23
+ /** Whether DMs are enabled */
24
+ enabled: boolean;
25
+ /** Mode for DM processing */
26
+ mode: "mention" | "auto";
27
+ /** User IDs that are explicitly allowed (if set, only these users can DM) */
28
+ allowlist?: string[];
29
+ /** User IDs that are explicitly blocked */
30
+ blocklist?: string[];
31
+ }
32
+ /**
33
+ * Result of resolving channel configuration
34
+ */
35
+ export interface ResolvedChannelConfig {
36
+ /** The mode for this channel */
37
+ mode: "mention" | "auto";
38
+ /** Number of context messages to include */
39
+ contextMessages: number;
40
+ /** Whether this is a DM channel */
41
+ isDM: boolean;
42
+ /** The guild ID if applicable */
43
+ guildId: string | null;
44
+ }
45
+ /**
46
+ * Check if DMs are enabled based on configuration
47
+ *
48
+ * @param dmConfig - DM configuration from agent's Discord config
49
+ * @returns true if DMs are enabled, false otherwise
50
+ */
51
+ export declare function isDMEnabled(dmConfig?: DiscordDM): boolean;
52
+ /**
53
+ * Get the mode for DM processing
54
+ *
55
+ * @param dmConfig - DM configuration from agent's Discord config
56
+ * @returns The mode for DM processing (defaults to "auto")
57
+ */
58
+ export declare function getDMMode(dmConfig?: DiscordDM): "mention" | "auto";
59
+ /**
60
+ * Check if a user is allowed to send DMs to the bot
61
+ *
62
+ * Filtering rules:
63
+ * 1. If DMs are disabled, no users are allowed
64
+ * 2. If a blocklist is defined and user is on it, they are blocked
65
+ * 3. If an allowlist is defined, only users on it are allowed
66
+ * 4. If neither list is defined, all users are allowed
67
+ *
68
+ * @param userId - Discord user ID to check
69
+ * @param dmConfig - DM configuration from agent's Discord config
70
+ * @returns Filter result with allowed status and reason
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * const result = checkDMUserFilter("123456789", dmConfig);
75
+ * if (!result.allowed) {
76
+ * console.log(`User blocked: ${result.reason}`);
77
+ * }
78
+ * ```
79
+ */
80
+ export declare function checkDMUserFilter(userId: string, dmConfig?: DiscordDM): DMFilterResult;
81
+ /**
82
+ * Default number of context messages for DMs
83
+ */
84
+ export declare const DEFAULT_DM_CONTEXT_MESSAGES = 10;
85
+ /**
86
+ * Default number of context messages for channels
87
+ */
88
+ export declare const DEFAULT_CHANNEL_CONTEXT_MESSAGES = 10;
89
+ /**
90
+ * Find channel configuration from guild config
91
+ *
92
+ * @param channelId - Discord channel ID
93
+ * @param guilds - Array of guild configurations
94
+ * @returns Channel config and guild ID, or null if not found
95
+ */
96
+ export declare function findChannelConfig(channelId: string, guilds: DiscordGuild[]): {
97
+ channel: DiscordChannel;
98
+ guildId: string;
99
+ } | null;
100
+ /**
101
+ * Resolve channel configuration for a message
102
+ *
103
+ * Determines the mode and context settings for a given channel,
104
+ * handling both guild channels and DMs appropriately.
105
+ *
106
+ * @param channelId - Discord channel ID
107
+ * @param guildId - Guild ID (null for DMs)
108
+ * @param guilds - Array of guild configurations
109
+ * @param dmConfig - Global DM configuration
110
+ * @returns Resolved channel configuration or null if channel not configured
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const config = resolveChannelConfig(
115
+ * message.channel.id,
116
+ * message.guildId,
117
+ * discordConfig.guilds,
118
+ * discordConfig.dm
119
+ * );
120
+ *
121
+ * if (config) {
122
+ * if (config.mode === 'auto') {
123
+ * // Process all non-bot messages
124
+ * } else {
125
+ * // Only process mentions
126
+ * }
127
+ * }
128
+ * ```
129
+ */
130
+ export declare function resolveChannelConfig(channelId: string, guildId: string | null, guilds: DiscordGuild[], dmConfig?: DiscordDM): ResolvedChannelConfig | null;
131
+ /**
132
+ * Check if a message should be processed in auto mode
133
+ *
134
+ * In auto mode:
135
+ * - All non-bot messages are processed
136
+ * - No mention is required
137
+ * - Full conversation context is maintained
138
+ *
139
+ * @param isBot - Whether the message author is a bot
140
+ * @param mode - The channel mode
141
+ * @param wasMentioned - Whether the bot was mentioned
142
+ * @returns true if the message should be processed
143
+ */
144
+ export declare function shouldProcessInMode(isBot: boolean, mode: "mention" | "auto", wasMentioned: boolean): boolean;
145
+ //# sourceMappingURL=auto-mode-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-mode-handler.d.ts","sourceRoot":"","sources":["../src/auto-mode-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAM7E;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,8CAA8C;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,oCAAoC;IACpC,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,kBAAkB,GAAG,cAAc,CAAC;CACzE;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,8BAA8B;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,6BAA6B;IAC7B,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;IACzB,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,gCAAgC;IAChC,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;IACzB,4CAA4C;IAC5C,eAAe,EAAE,MAAM,CAAC;IACxB,mCAAmC;IACnC,IAAI,EAAE,OAAO,CAAC;IACd,iCAAiC;IACjC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAMD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,QAAQ,CAAC,EAAE,SAAS,GAAG,OAAO,CAMzD;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,QAAQ,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAMlE;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,SAAS,GACnB,cAAc,CA4ChB;AAMD;;GAEG;AACH,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C;;GAEG;AACH,eAAO,MAAM,gCAAgC,KAAK,CAAC;AAEnD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EAAE,GACrB;IAAE,OAAO,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAQrD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,MAAM,EAAE,YAAY,EAAE,EACtB,QAAQ,CAAC,EAAE,SAAS,GACnB,qBAAqB,GAAG,IAAI,CAiC9B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,SAAS,GAAG,MAAM,EACxB,YAAY,EAAE,OAAO,GACpB,OAAO,CAaT"}
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Auto mode handler for Discord DMs and dedicated channels
3
+ *
4
+ * Provides utilities for:
5
+ * - Checking if DMs should be processed (auto mode by default)
6
+ * - Filtering users via allowlist/blocklist
7
+ * - Determining channel mode configuration
8
+ */
9
+ // =============================================================================
10
+ // DM Filtering
11
+ // =============================================================================
12
+ /**
13
+ * Check if DMs are enabled based on configuration
14
+ *
15
+ * @param dmConfig - DM configuration from agent's Discord config
16
+ * @returns true if DMs are enabled, false otherwise
17
+ */
18
+ export function isDMEnabled(dmConfig) {
19
+ // If no DM config provided, DMs are enabled by default
20
+ if (!dmConfig) {
21
+ return true;
22
+ }
23
+ return dmConfig.enabled;
24
+ }
25
+ /**
26
+ * Get the mode for DM processing
27
+ *
28
+ * @param dmConfig - DM configuration from agent's Discord config
29
+ * @returns The mode for DM processing (defaults to "auto")
30
+ */
31
+ export function getDMMode(dmConfig) {
32
+ // DMs default to auto mode (no mention required)
33
+ if (!dmConfig) {
34
+ return "auto";
35
+ }
36
+ return dmConfig.mode;
37
+ }
38
+ /**
39
+ * Check if a user is allowed to send DMs to the bot
40
+ *
41
+ * Filtering rules:
42
+ * 1. If DMs are disabled, no users are allowed
43
+ * 2. If a blocklist is defined and user is on it, they are blocked
44
+ * 3. If an allowlist is defined, only users on it are allowed
45
+ * 4. If neither list is defined, all users are allowed
46
+ *
47
+ * @param userId - Discord user ID to check
48
+ * @param dmConfig - DM configuration from agent's Discord config
49
+ * @returns Filter result with allowed status and reason
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * const result = checkDMUserFilter("123456789", dmConfig);
54
+ * if (!result.allowed) {
55
+ * console.log(`User blocked: ${result.reason}`);
56
+ * }
57
+ * ```
58
+ */
59
+ export function checkDMUserFilter(userId, dmConfig) {
60
+ // If DMs are disabled, no users are allowed
61
+ if (!isDMEnabled(dmConfig)) {
62
+ return {
63
+ allowed: false,
64
+ reason: "dm_disabled",
65
+ };
66
+ }
67
+ // If no config, all users are allowed
68
+ if (!dmConfig) {
69
+ return {
70
+ allowed: true,
71
+ reason: "allowed",
72
+ };
73
+ }
74
+ const { allowlist, blocklist } = dmConfig;
75
+ // Check blocklist first (takes precedence)
76
+ if (blocklist && blocklist.length > 0) {
77
+ if (blocklist.includes(userId)) {
78
+ return {
79
+ allowed: false,
80
+ reason: "in_blocklist",
81
+ };
82
+ }
83
+ }
84
+ // Check allowlist (if defined, only users on it are allowed)
85
+ if (allowlist && allowlist.length > 0) {
86
+ if (!allowlist.includes(userId)) {
87
+ return {
88
+ allowed: false,
89
+ reason: "not_in_allowlist",
90
+ };
91
+ }
92
+ }
93
+ // User is allowed
94
+ return {
95
+ allowed: true,
96
+ reason: "allowed",
97
+ };
98
+ }
99
+ // =============================================================================
100
+ // Channel Configuration
101
+ // =============================================================================
102
+ /**
103
+ * Default number of context messages for DMs
104
+ */
105
+ export const DEFAULT_DM_CONTEXT_MESSAGES = 10;
106
+ /**
107
+ * Default number of context messages for channels
108
+ */
109
+ export const DEFAULT_CHANNEL_CONTEXT_MESSAGES = 10;
110
+ /**
111
+ * Find channel configuration from guild config
112
+ *
113
+ * @param channelId - Discord channel ID
114
+ * @param guilds - Array of guild configurations
115
+ * @returns Channel config and guild ID, or null if not found
116
+ */
117
+ export function findChannelConfig(channelId, guilds) {
118
+ for (const guild of guilds) {
119
+ const channel = guild.channels?.find((c) => c.id === channelId);
120
+ if (channel) {
121
+ return { channel, guildId: guild.id };
122
+ }
123
+ }
124
+ return null;
125
+ }
126
+ /**
127
+ * Resolve channel configuration for a message
128
+ *
129
+ * Determines the mode and context settings for a given channel,
130
+ * handling both guild channels and DMs appropriately.
131
+ *
132
+ * @param channelId - Discord channel ID
133
+ * @param guildId - Guild ID (null for DMs)
134
+ * @param guilds - Array of guild configurations
135
+ * @param dmConfig - Global DM configuration
136
+ * @returns Resolved channel configuration or null if channel not configured
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * const config = resolveChannelConfig(
141
+ * message.channel.id,
142
+ * message.guildId,
143
+ * discordConfig.guilds,
144
+ * discordConfig.dm
145
+ * );
146
+ *
147
+ * if (config) {
148
+ * if (config.mode === 'auto') {
149
+ * // Process all non-bot messages
150
+ * } else {
151
+ * // Only process mentions
152
+ * }
153
+ * }
154
+ * ```
155
+ */
156
+ export function resolveChannelConfig(channelId, guildId, guilds, dmConfig) {
157
+ // Handle DMs
158
+ if (!guildId) {
159
+ // Check if DMs are enabled
160
+ if (!isDMEnabled(dmConfig)) {
161
+ return null;
162
+ }
163
+ return {
164
+ mode: getDMMode(dmConfig),
165
+ contextMessages: DEFAULT_DM_CONTEXT_MESSAGES,
166
+ isDM: true,
167
+ guildId: null,
168
+ };
169
+ }
170
+ // Handle guild channels
171
+ const guildConfig = guilds.find((g) => g.id === guildId);
172
+ if (!guildConfig) {
173
+ return null;
174
+ }
175
+ const channelConfig = guildConfig.channels?.find((c) => c.id === channelId);
176
+ if (!channelConfig) {
177
+ return null;
178
+ }
179
+ return {
180
+ mode: channelConfig.mode,
181
+ contextMessages: channelConfig.context_messages,
182
+ isDM: false,
183
+ guildId,
184
+ };
185
+ }
186
+ /**
187
+ * Check if a message should be processed in auto mode
188
+ *
189
+ * In auto mode:
190
+ * - All non-bot messages are processed
191
+ * - No mention is required
192
+ * - Full conversation context is maintained
193
+ *
194
+ * @param isBot - Whether the message author is a bot
195
+ * @param mode - The channel mode
196
+ * @param wasMentioned - Whether the bot was mentioned
197
+ * @returns true if the message should be processed
198
+ */
199
+ export function shouldProcessInMode(isBot, mode, wasMentioned) {
200
+ // Never process bot messages
201
+ if (isBot) {
202
+ return false;
203
+ }
204
+ // In auto mode, process all non-bot messages
205
+ if (mode === "auto") {
206
+ return true;
207
+ }
208
+ // In mention mode, only process if mentioned
209
+ return wasMentioned;
210
+ }
211
+ //# sourceMappingURL=auto-mode-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-mode-handler.js","sourceRoot":"","sources":["../src/auto-mode-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=command-manager.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-manager.test.d.ts","sourceRoot":"","sources":["../../../src/commands/__tests__/command-manager.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,307 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ // =============================================================================
3
+ // Mock discord.js REST and Routes
4
+ // =============================================================================
5
+ const mockRestPut = vi.fn().mockResolvedValue([]);
6
+ vi.mock("discord.js", async () => {
7
+ const actual = await vi.importActual("discord.js");
8
+ // Create a mock REST class
9
+ class MockREST {
10
+ setToken() {
11
+ return this;
12
+ }
13
+ put = mockRestPut;
14
+ }
15
+ return {
16
+ ...actual,
17
+ REST: MockREST,
18
+ Routes: {
19
+ applicationCommands: (clientId) => `/applications/${clientId}/commands`,
20
+ },
21
+ };
22
+ });
23
+ // Import after mock
24
+ import { CommandManager } from "../command-manager.js";
25
+ // =============================================================================
26
+ // Test Fixtures
27
+ // =============================================================================
28
+ function createMockClient() {
29
+ return {
30
+ user: {
31
+ id: "bot-123",
32
+ username: "TestBot",
33
+ },
34
+ };
35
+ }
36
+ function createMockSessionManager() {
37
+ return {
38
+ agentName: "test-agent",
39
+ getOrCreateSession: vi.fn(),
40
+ touchSession: vi.fn(),
41
+ getSession: vi.fn(),
42
+ clearSession: vi.fn(),
43
+ cleanupExpiredSessions: vi.fn(),
44
+ };
45
+ }
46
+ function createMockConnectorState() {
47
+ return {
48
+ status: "connected",
49
+ connectedAt: new Date().toISOString(),
50
+ disconnectedAt: null,
51
+ reconnectAttempts: 0,
52
+ lastError: null,
53
+ botUser: {
54
+ id: "bot-123",
55
+ username: "TestBot",
56
+ discriminator: "0001",
57
+ },
58
+ rateLimits: {
59
+ totalCount: 0,
60
+ lastRateLimitAt: null,
61
+ isRateLimited: false,
62
+ currentResetTime: 0,
63
+ },
64
+ messageStats: {
65
+ received: 0,
66
+ sent: 0,
67
+ ignored: 0,
68
+ },
69
+ };
70
+ }
71
+ function createMockInteraction(commandName, channelId = "channel-123") {
72
+ return {
73
+ commandName,
74
+ channelId,
75
+ reply: vi.fn().mockResolvedValue(undefined),
76
+ replied: false,
77
+ deferred: false,
78
+ user: {
79
+ id: "user-123",
80
+ username: "TestUser",
81
+ },
82
+ };
83
+ }
84
+ function createMockLogger() {
85
+ return {
86
+ debug: vi.fn(),
87
+ info: vi.fn(),
88
+ warn: vi.fn(),
89
+ error: vi.fn(),
90
+ };
91
+ }
92
+ // =============================================================================
93
+ // Tests
94
+ // =============================================================================
95
+ describe("CommandManager", () => {
96
+ let client;
97
+ let sessionManager;
98
+ let connectorState;
99
+ let logger;
100
+ beforeEach(() => {
101
+ vi.clearAllMocks();
102
+ client = createMockClient();
103
+ sessionManager = createMockSessionManager();
104
+ connectorState = createMockConnectorState();
105
+ logger = createMockLogger();
106
+ });
107
+ describe("constructor", () => {
108
+ it("creates command manager with valid options", () => {
109
+ const manager = new CommandManager({
110
+ agentName: "test-agent",
111
+ client,
112
+ botToken: "test-token",
113
+ sessionManager,
114
+ getConnectorState: () => connectorState,
115
+ logger,
116
+ });
117
+ expect(manager).toBeDefined();
118
+ });
119
+ it("registers built-in commands", () => {
120
+ const manager = new CommandManager({
121
+ agentName: "test-agent",
122
+ client,
123
+ botToken: "test-token",
124
+ sessionManager,
125
+ getConnectorState: () => connectorState,
126
+ logger,
127
+ });
128
+ const commands = manager.getCommands();
129
+ expect(commands.has("help")).toBe(true);
130
+ expect(commands.has("reset")).toBe(true);
131
+ expect(commands.has("status")).toBe(true);
132
+ });
133
+ });
134
+ describe("registerCommands", () => {
135
+ it("registers commands with Discord API", async () => {
136
+ const manager = new CommandManager({
137
+ agentName: "test-agent",
138
+ client,
139
+ botToken: "test-token",
140
+ sessionManager,
141
+ getConnectorState: () => connectorState,
142
+ logger,
143
+ });
144
+ await manager.registerCommands();
145
+ expect(mockRestPut).toHaveBeenCalledTimes(1);
146
+ expect(mockRestPut).toHaveBeenCalledWith(expect.stringContaining("bot-123"), expect.objectContaining({
147
+ body: expect.any(Array),
148
+ }));
149
+ });
150
+ it("logs successful registration", async () => {
151
+ const manager = new CommandManager({
152
+ agentName: "test-agent",
153
+ client,
154
+ botToken: "test-token",
155
+ sessionManager,
156
+ getConnectorState: () => connectorState,
157
+ logger,
158
+ });
159
+ await manager.registerCommands();
160
+ expect(logger.info).toHaveBeenCalledWith(expect.stringContaining("Registering"), expect.any(Object));
161
+ expect(logger.info).toHaveBeenCalledWith(expect.stringContaining("Successfully"));
162
+ });
163
+ it("throws when client user ID not available", async () => {
164
+ const clientWithoutUser = {
165
+ user: null,
166
+ };
167
+ const manager = new CommandManager({
168
+ agentName: "test-agent",
169
+ client: clientWithoutUser,
170
+ botToken: "test-token",
171
+ sessionManager,
172
+ getConnectorState: () => connectorState,
173
+ logger,
174
+ });
175
+ await expect(manager.registerCommands()).rejects.toThrow("Client user ID not available");
176
+ });
177
+ it("logs error on registration failure", async () => {
178
+ mockRestPut.mockRejectedValueOnce(new Error("API Error"));
179
+ const manager = new CommandManager({
180
+ agentName: "test-agent",
181
+ client,
182
+ botToken: "test-token",
183
+ sessionManager,
184
+ getConnectorState: () => connectorState,
185
+ logger,
186
+ });
187
+ await expect(manager.registerCommands()).rejects.toThrow("API Error");
188
+ expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Failed"), expect.any(Object));
189
+ });
190
+ });
191
+ describe("handleInteraction", () => {
192
+ it("executes known commands", async () => {
193
+ const manager = new CommandManager({
194
+ agentName: "test-agent",
195
+ client,
196
+ botToken: "test-token",
197
+ sessionManager,
198
+ getConnectorState: () => connectorState,
199
+ logger,
200
+ });
201
+ const interaction = createMockInteraction("help");
202
+ await manager.handleInteraction(interaction);
203
+ const reply = interaction.reply;
204
+ expect(reply).toHaveBeenCalledTimes(1);
205
+ });
206
+ it("responds with error for unknown commands", async () => {
207
+ const manager = new CommandManager({
208
+ agentName: "test-agent",
209
+ client,
210
+ botToken: "test-token",
211
+ sessionManager,
212
+ getConnectorState: () => connectorState,
213
+ logger,
214
+ });
215
+ const interaction = createMockInteraction("unknown-command");
216
+ await manager.handleInteraction(interaction);
217
+ const reply = interaction.reply;
218
+ expect(reply).toHaveBeenCalledWith({
219
+ content: "Unknown command.",
220
+ ephemeral: true,
221
+ });
222
+ });
223
+ it("logs warning for unknown commands", async () => {
224
+ const manager = new CommandManager({
225
+ agentName: "test-agent",
226
+ client,
227
+ botToken: "test-token",
228
+ sessionManager,
229
+ getConnectorState: () => connectorState,
230
+ logger,
231
+ });
232
+ const interaction = createMockInteraction("unknown-command");
233
+ await manager.handleInteraction(interaction);
234
+ expect(logger.warn).toHaveBeenCalledWith(expect.stringContaining("Unknown command"), expect.any(Object));
235
+ });
236
+ it("logs command execution", async () => {
237
+ const manager = new CommandManager({
238
+ agentName: "test-agent",
239
+ client,
240
+ botToken: "test-token",
241
+ sessionManager,
242
+ getConnectorState: () => connectorState,
243
+ logger,
244
+ });
245
+ const interaction = createMockInteraction("help");
246
+ await manager.handleInteraction(interaction);
247
+ expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining("Executing"), expect.objectContaining({
248
+ commandName: "help",
249
+ }));
250
+ });
251
+ it("handles command execution errors gracefully", async () => {
252
+ const manager = new CommandManager({
253
+ agentName: "test-agent",
254
+ client,
255
+ botToken: "test-token",
256
+ sessionManager,
257
+ getConnectorState: () => connectorState,
258
+ logger,
259
+ });
260
+ // Create an interaction that will fail when executing
261
+ const interaction = createMockInteraction("reset");
262
+ sessionManager.clearSession.mockRejectedValueOnce(new Error("Session error"));
263
+ await manager.handleInteraction(interaction);
264
+ // ErrorHandler logs with different format
265
+ expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Error during"), expect.any(Object));
266
+ const reply = interaction.reply;
267
+ // User-friendly error message (no stack traces)
268
+ expect(reply).toHaveBeenCalledWith({
269
+ content: "Sorry, I encountered an error processing your request. Please try again.",
270
+ ephemeral: true,
271
+ });
272
+ });
273
+ it("provides correct context to command handler", async () => {
274
+ const manager = new CommandManager({
275
+ agentName: "my-agent",
276
+ client,
277
+ botToken: "test-token",
278
+ sessionManager,
279
+ getConnectorState: () => connectorState,
280
+ logger,
281
+ });
282
+ const interaction = createMockInteraction("help");
283
+ await manager.handleInteraction(interaction);
284
+ const reply = interaction.reply;
285
+ // Help command should include agent name in response
286
+ expect(reply).toHaveBeenCalledWith(expect.objectContaining({
287
+ content: expect.stringContaining("my-agent"),
288
+ }));
289
+ });
290
+ });
291
+ describe("getCommands", () => {
292
+ it("returns readonly map of commands", () => {
293
+ const manager = new CommandManager({
294
+ agentName: "test-agent",
295
+ client,
296
+ botToken: "test-token",
297
+ sessionManager,
298
+ getConnectorState: () => connectorState,
299
+ logger,
300
+ });
301
+ const commands = manager.getCommands();
302
+ expect(commands).toBeInstanceOf(Map);
303
+ expect(commands.size).toBe(3);
304
+ });
305
+ });
306
+ });
307
+ //# sourceMappingURL=command-manager.test.js.map