@herdctl/chat 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/LICENSE +21 -0
  2. package/dist/__tests__/dm-filter.test.d.ts +5 -0
  3. package/dist/__tests__/dm-filter.test.d.ts.map +1 -0
  4. package/dist/__tests__/dm-filter.test.js +136 -0
  5. package/dist/__tests__/dm-filter.test.js.map +1 -0
  6. package/dist/__tests__/error-handler.test.d.ts +5 -0
  7. package/dist/__tests__/error-handler.test.d.ts.map +1 -0
  8. package/dist/__tests__/error-handler.test.js +235 -0
  9. package/dist/__tests__/error-handler.test.js.map +1 -0
  10. package/dist/__tests__/errors.test.d.ts +5 -0
  11. package/dist/__tests__/errors.test.d.ts.map +1 -0
  12. package/dist/__tests__/errors.test.js +140 -0
  13. package/dist/__tests__/errors.test.js.map +1 -0
  14. package/dist/__tests__/index.test.d.ts +2 -0
  15. package/dist/__tests__/index.test.d.ts.map +1 -0
  16. package/dist/__tests__/index.test.js +25 -0
  17. package/dist/__tests__/index.test.js.map +1 -0
  18. package/dist/__tests__/message-extraction.test.d.ts +5 -0
  19. package/dist/__tests__/message-extraction.test.d.ts.map +1 -0
  20. package/dist/__tests__/message-extraction.test.js +157 -0
  21. package/dist/__tests__/message-extraction.test.js.map +1 -0
  22. package/dist/__tests__/message-splitting.test.d.ts +5 -0
  23. package/dist/__tests__/message-splitting.test.d.ts.map +1 -0
  24. package/dist/__tests__/message-splitting.test.js +153 -0
  25. package/dist/__tests__/message-splitting.test.js.map +1 -0
  26. package/dist/__tests__/session-manager.test.d.ts +2 -0
  27. package/dist/__tests__/session-manager.test.d.ts.map +1 -0
  28. package/dist/__tests__/session-manager.test.js +779 -0
  29. package/dist/__tests__/session-manager.test.js.map +1 -0
  30. package/dist/__tests__/status-formatting.test.d.ts +5 -0
  31. package/dist/__tests__/status-formatting.test.d.ts.map +1 -0
  32. package/dist/__tests__/status-formatting.test.js +160 -0
  33. package/dist/__tests__/status-formatting.test.js.map +1 -0
  34. package/dist/__tests__/streaming-responder.test.d.ts +5 -0
  35. package/dist/__tests__/streaming-responder.test.d.ts.map +1 -0
  36. package/dist/__tests__/streaming-responder.test.js +154 -0
  37. package/dist/__tests__/streaming-responder.test.js.map +1 -0
  38. package/dist/dm-filter.d.ts +121 -0
  39. package/dist/dm-filter.d.ts.map +1 -0
  40. package/dist/dm-filter.js +162 -0
  41. package/dist/dm-filter.js.map +1 -0
  42. package/dist/error-handler.d.ts +217 -0
  43. package/dist/error-handler.d.ts.map +1 -0
  44. package/dist/error-handler.js +313 -0
  45. package/dist/error-handler.js.map +1 -0
  46. package/dist/errors.d.ts +118 -0
  47. package/dist/errors.d.ts.map +1 -0
  48. package/dist/errors.js +157 -0
  49. package/dist/errors.js.map +1 -0
  50. package/dist/index.d.ts +22 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +69 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/message-extraction.d.ts +81 -0
  55. package/dist/message-extraction.d.ts.map +1 -0
  56. package/dist/message-extraction.js +90 -0
  57. package/dist/message-extraction.js.map +1 -0
  58. package/dist/message-splitting.d.ts +133 -0
  59. package/dist/message-splitting.d.ts.map +1 -0
  60. package/dist/message-splitting.js +188 -0
  61. package/dist/message-splitting.js.map +1 -0
  62. package/dist/session-manager/errors.d.ts +59 -0
  63. package/dist/session-manager/errors.d.ts.map +1 -0
  64. package/dist/session-manager/errors.js +71 -0
  65. package/dist/session-manager/errors.js.map +1 -0
  66. package/dist/session-manager/index.d.ts +10 -0
  67. package/dist/session-manager/index.d.ts.map +1 -0
  68. package/dist/session-manager/index.js +14 -0
  69. package/dist/session-manager/index.js.map +1 -0
  70. package/dist/session-manager/session-manager.d.ts +123 -0
  71. package/dist/session-manager/session-manager.d.ts.map +1 -0
  72. package/dist/session-manager/session-manager.js +394 -0
  73. package/dist/session-manager/session-manager.js.map +1 -0
  74. package/dist/session-manager/types.d.ts +205 -0
  75. package/dist/session-manager/types.d.ts.map +1 -0
  76. package/dist/session-manager/types.js +67 -0
  77. package/dist/session-manager/types.js.map +1 -0
  78. package/dist/status-formatting.d.ts +147 -0
  79. package/dist/status-formatting.d.ts.map +1 -0
  80. package/dist/status-formatting.js +234 -0
  81. package/dist/status-formatting.js.map +1 -0
  82. package/dist/streaming-responder.d.ts +130 -0
  83. package/dist/streaming-responder.d.ts.map +1 -0
  84. package/dist/streaming-responder.js +178 -0
  85. package/dist/streaming-responder.js.map +1 -0
  86. package/dist/types.d.ts +184 -0
  87. package/dist/types.d.ts.map +1 -0
  88. package/dist/types.js +8 -0
  89. package/dist/types.js.map +1 -0
  90. package/package.json +39 -4
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Message splitting utilities for chat platforms
3
+ *
4
+ * Provides utilities for:
5
+ * - Splitting long messages to fit platform character limits
6
+ * - Maintaining message coherence when splitting (avoiding mid-sentence breaks)
7
+ * - Preserving code blocks when splitting
8
+ */
9
+ /**
10
+ * Default delay between sending split messages (in milliseconds)
11
+ */
12
+ export declare const DEFAULT_MESSAGE_DELAY_MS = 500;
13
+ /**
14
+ * Minimum chunk size when splitting messages
15
+ * Prevents creating very small message fragments
16
+ */
17
+ export declare const MIN_CHUNK_SIZE = 100;
18
+ /**
19
+ * Options for splitting messages
20
+ */
21
+ export interface MessageSplitOptions {
22
+ /**
23
+ * Maximum length for each message chunk
24
+ * Required - no default since it varies by platform
25
+ */
26
+ maxLength: number;
27
+ /**
28
+ * Whether to try to split at natural boundaries like sentences (default: true)
29
+ */
30
+ preserveBoundaries?: boolean;
31
+ /**
32
+ * Characters to use as split points, in order of preference
33
+ * Default: ['\n\n', '\n', '. ', '! ', '? ', ', ', ' ']
34
+ */
35
+ splitPoints?: string[];
36
+ }
37
+ /**
38
+ * Result from splitting a message
39
+ */
40
+ export interface SplitResult {
41
+ /** Array of message chunks */
42
+ chunks: string[];
43
+ /** Whether the message was split */
44
+ wasSplit: boolean;
45
+ /** Original message length */
46
+ originalLength: number;
47
+ }
48
+ /**
49
+ * Default split points in order of preference
50
+ *
51
+ * We prefer to split at paragraph breaks, then sentences, then clauses, then words
52
+ */
53
+ export declare const DEFAULT_SPLIT_POINTS: string[];
54
+ /**
55
+ * Find the best split point within a text chunk
56
+ *
57
+ * @param text - Text to find split point in
58
+ * @param maxLength - Maximum length for the chunk
59
+ * @param splitPoints - Split points to search for, in order of preference
60
+ * @returns Index to split at, or maxLength if no good split point found
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * const splitIndex = findSplitPoint(longText, 2000);
65
+ * const firstPart = longText.slice(0, splitIndex);
66
+ * const secondPart = longText.slice(splitIndex);
67
+ * ```
68
+ */
69
+ export declare function findSplitPoint(text: string, maxLength: number, splitPoints?: string[]): number;
70
+ /**
71
+ * Split a message into chunks that fit within the specified max length
72
+ *
73
+ * @param content - Message content to split
74
+ * @param options - Split options including maxLength
75
+ * @returns Split result with chunks array
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * const result = splitMessage(longText, { maxLength: 2000 });
80
+ * for (const chunk of result.chunks) {
81
+ * await channel.send(chunk);
82
+ * }
83
+ * ```
84
+ */
85
+ export declare function splitMessage(content: string, options: MessageSplitOptions): SplitResult;
86
+ /**
87
+ * Check if a message needs to be split
88
+ *
89
+ * @param content - Message content to check
90
+ * @param maxLength - Maximum message length
91
+ * @returns true if the message exceeds the max length
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * if (needsSplit(message, 2000)) {
96
+ * const { chunks } = splitMessage(message, { maxLength: 2000 });
97
+ * // Send each chunk
98
+ * } else {
99
+ * // Send as-is
100
+ * }
101
+ * ```
102
+ */
103
+ export declare function needsSplit(content: string, maxLength: number): boolean;
104
+ /**
105
+ * Truncate a message to fit within the max length, adding an ellipsis
106
+ *
107
+ * @param content - Message content to truncate
108
+ * @param maxLength - Maximum length
109
+ * @param ellipsis - Ellipsis to append (default: '...')
110
+ * @returns Truncated message
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const short = truncateMessage(longText, 100);
115
+ * // Returns: "This is a very long text that has been trun..."
116
+ * ```
117
+ */
118
+ export declare function truncateMessage(content: string, maxLength: number, ellipsis?: string): string;
119
+ /**
120
+ * Format code as a code block with optional language
121
+ *
122
+ * @param code - Code to format
123
+ * @param language - Optional language for syntax highlighting
124
+ * @returns Formatted code block
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * const formatted = formatCodeBlock('const x = 1;', 'typescript');
129
+ * // Returns: "```typescript\nconst x = 1;\n```"
130
+ * ```
131
+ */
132
+ export declare function formatCodeBlock(code: string, language?: string): string;
133
+ //# sourceMappingURL=message-splitting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-splitting.d.ts","sourceRoot":"","sources":["../src/message-splitting.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH;;GAEG;AACH,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C;;;GAGG;AACH,eAAO,MAAM,cAAc,MAAM,CAAC;AAMlC;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,8BAA8B;IAC9B,MAAM,EAAE,MAAM,EAAE,CAAC;IAEjB,oCAAoC;IACpC,QAAQ,EAAE,OAAO,CAAC;IAElB,8BAA8B;IAC9B,cAAc,EAAE,MAAM,CAAC;CACxB;AAMD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,UAA8C,CAAC;AAMhF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,WAAW,GAAE,MAAM,EAAyB,GAC3C,MAAM,CA4BR;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,mBAAmB,GAC3B,WAAW,CAoDb;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAEtE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,MAAc,GACvB,MAAM,CAOR;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAGvE"}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Message splitting utilities for chat platforms
3
+ *
4
+ * Provides utilities for:
5
+ * - Splitting long messages to fit platform character limits
6
+ * - Maintaining message coherence when splitting (avoiding mid-sentence breaks)
7
+ * - Preserving code blocks when splitting
8
+ */
9
+ // =============================================================================
10
+ // Constants
11
+ // =============================================================================
12
+ /**
13
+ * Default delay between sending split messages (in milliseconds)
14
+ */
15
+ export const DEFAULT_MESSAGE_DELAY_MS = 500;
16
+ /**
17
+ * Minimum chunk size when splitting messages
18
+ * Prevents creating very small message fragments
19
+ */
20
+ export const MIN_CHUNK_SIZE = 100;
21
+ // =============================================================================
22
+ // Default Split Points
23
+ // =============================================================================
24
+ /**
25
+ * Default split points in order of preference
26
+ *
27
+ * We prefer to split at paragraph breaks, then sentences, then clauses, then words
28
+ */
29
+ export const DEFAULT_SPLIT_POINTS = ["\n\n", "\n", ". ", "! ", "? ", ", ", " "];
30
+ // =============================================================================
31
+ // Message Splitting Functions
32
+ // =============================================================================
33
+ /**
34
+ * Find the best split point within a text chunk
35
+ *
36
+ * @param text - Text to find split point in
37
+ * @param maxLength - Maximum length for the chunk
38
+ * @param splitPoints - Split points to search for, in order of preference
39
+ * @returns Index to split at, or maxLength if no good split point found
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * const splitIndex = findSplitPoint(longText, 2000);
44
+ * const firstPart = longText.slice(0, splitIndex);
45
+ * const secondPart = longText.slice(splitIndex);
46
+ * ```
47
+ */
48
+ export function findSplitPoint(text, maxLength, splitPoints = DEFAULT_SPLIT_POINTS) {
49
+ // If text fits, no split needed
50
+ if (text.length <= maxLength) {
51
+ return text.length;
52
+ }
53
+ // Try each split point in order of preference
54
+ for (const splitPoint of splitPoints) {
55
+ // Search backwards from maxLength to find the last occurrence of this split point
56
+ const searchText = text.slice(0, maxLength);
57
+ const lastIndex = searchText.lastIndexOf(splitPoint);
58
+ // If found and results in a reasonable chunk size
59
+ if (lastIndex > MIN_CHUNK_SIZE) {
60
+ // Include the split point in the first chunk (e.g., keep the period with the sentence)
61
+ return lastIndex + splitPoint.length;
62
+ }
63
+ }
64
+ // No good split point found - fall back to hard split at maxLength
65
+ // But try to avoid splitting in the middle of a word
66
+ const hardSplitIndex = text.lastIndexOf(" ", maxLength);
67
+ if (hardSplitIndex > MIN_CHUNK_SIZE) {
68
+ return hardSplitIndex + 1; // Include the space in the first chunk
69
+ }
70
+ // Last resort: hard split at maxLength
71
+ return maxLength;
72
+ }
73
+ /**
74
+ * Split a message into chunks that fit within the specified max length
75
+ *
76
+ * @param content - Message content to split
77
+ * @param options - Split options including maxLength
78
+ * @returns Split result with chunks array
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * const result = splitMessage(longText, { maxLength: 2000 });
83
+ * for (const chunk of result.chunks) {
84
+ * await channel.send(chunk);
85
+ * }
86
+ * ```
87
+ */
88
+ export function splitMessage(content, options) {
89
+ const { maxLength, preserveBoundaries = true, splitPoints = DEFAULT_SPLIT_POINTS, } = options;
90
+ const originalLength = content.length;
91
+ // If content fits in one message, return as-is
92
+ if (content.length <= maxLength) {
93
+ return {
94
+ chunks: [content],
95
+ wasSplit: false,
96
+ originalLength,
97
+ };
98
+ }
99
+ const chunks = [];
100
+ let remaining = content;
101
+ while (remaining.length > 0) {
102
+ if (remaining.length <= maxLength) {
103
+ // Remaining text fits in one message
104
+ chunks.push(remaining.trim());
105
+ break;
106
+ }
107
+ // Find the best split point
108
+ let splitIndex;
109
+ if (preserveBoundaries) {
110
+ splitIndex = findSplitPoint(remaining, maxLength, splitPoints);
111
+ }
112
+ else {
113
+ // Simple split at maxLength
114
+ splitIndex = maxLength;
115
+ }
116
+ // Extract the chunk and trim
117
+ const chunk = remaining.slice(0, splitIndex).trim();
118
+ if (chunk.length > 0) {
119
+ chunks.push(chunk);
120
+ }
121
+ // Update remaining text
122
+ remaining = remaining.slice(splitIndex).trim();
123
+ }
124
+ return {
125
+ chunks,
126
+ wasSplit: chunks.length > 1,
127
+ originalLength,
128
+ };
129
+ }
130
+ /**
131
+ * Check if a message needs to be split
132
+ *
133
+ * @param content - Message content to check
134
+ * @param maxLength - Maximum message length
135
+ * @returns true if the message exceeds the max length
136
+ *
137
+ * @example
138
+ * ```typescript
139
+ * if (needsSplit(message, 2000)) {
140
+ * const { chunks } = splitMessage(message, { maxLength: 2000 });
141
+ * // Send each chunk
142
+ * } else {
143
+ * // Send as-is
144
+ * }
145
+ * ```
146
+ */
147
+ export function needsSplit(content, maxLength) {
148
+ return content.length > maxLength;
149
+ }
150
+ /**
151
+ * Truncate a message to fit within the max length, adding an ellipsis
152
+ *
153
+ * @param content - Message content to truncate
154
+ * @param maxLength - Maximum length
155
+ * @param ellipsis - Ellipsis to append (default: '...')
156
+ * @returns Truncated message
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * const short = truncateMessage(longText, 100);
161
+ * // Returns: "This is a very long text that has been trun..."
162
+ * ```
163
+ */
164
+ export function truncateMessage(content, maxLength, ellipsis = "...") {
165
+ if (content.length <= maxLength) {
166
+ return content;
167
+ }
168
+ const truncatedLength = maxLength - ellipsis.length;
169
+ return content.slice(0, truncatedLength) + ellipsis;
170
+ }
171
+ /**
172
+ * Format code as a code block with optional language
173
+ *
174
+ * @param code - Code to format
175
+ * @param language - Optional language for syntax highlighting
176
+ * @returns Formatted code block
177
+ *
178
+ * @example
179
+ * ```typescript
180
+ * const formatted = formatCodeBlock('const x = 1;', 'typescript');
181
+ * // Returns: "```typescript\nconst x = 1;\n```"
182
+ * ```
183
+ */
184
+ export function formatCodeBlock(code, language) {
185
+ const langTag = language ?? "";
186
+ return `\`\`\`${langTag}\n${code}\n\`\`\``;
187
+ }
188
+ //# sourceMappingURL=message-splitting.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-splitting.js","sourceRoot":"","sources":["../src/message-splitting.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Error classes for chat session management
3
+ *
4
+ * Provides typed errors for session persistence and retrieval failures.
5
+ * These error classes are shared between Discord, Slack, and other chat platforms.
6
+ */
7
+ /**
8
+ * Error codes for session manager operations
9
+ */
10
+ export declare enum SessionErrorCode {
11
+ STATE_READ_FAILED = "SESSION_STATE_READ_FAILED",
12
+ STATE_WRITE_FAILED = "SESSION_STATE_WRITE_FAILED",
13
+ DIRECTORY_CREATE_FAILED = "SESSION_DIRECTORY_CREATE_FAILED",
14
+ SESSION_NOT_FOUND = "SESSION_NOT_FOUND",
15
+ SESSION_EXPIRED = "SESSION_EXPIRED",
16
+ INVALID_STATE = "SESSION_INVALID_STATE"
17
+ }
18
+ /**
19
+ * Base error class for session manager operations
20
+ */
21
+ export declare class SessionManagerError extends Error {
22
+ readonly code: SessionErrorCode;
23
+ readonly agentName: string;
24
+ constructor(message: string, code: SessionErrorCode, agentName: string, options?: {
25
+ cause?: Error;
26
+ });
27
+ }
28
+ /**
29
+ * Error thrown when session state file cannot be read
30
+ */
31
+ export declare class SessionStateReadError extends SessionManagerError {
32
+ readonly path: string;
33
+ constructor(agentName: string, path: string, options?: {
34
+ cause?: Error;
35
+ });
36
+ }
37
+ /**
38
+ * Error thrown when session state file cannot be written
39
+ */
40
+ export declare class SessionStateWriteError extends SessionManagerError {
41
+ readonly path: string;
42
+ constructor(agentName: string, path: string, options?: {
43
+ cause?: Error;
44
+ });
45
+ }
46
+ /**
47
+ * Error thrown when session directory cannot be created
48
+ */
49
+ export declare class SessionDirectoryCreateError extends SessionManagerError {
50
+ readonly path: string;
51
+ constructor(agentName: string, path: string, options?: {
52
+ cause?: Error;
53
+ });
54
+ }
55
+ /**
56
+ * Type guard to check if an error is a SessionManagerError
57
+ */
58
+ export declare function isSessionManagerError(error: unknown): error is SessionManagerError;
59
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/session-manager/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,oBAAY,gBAAgB;IAC1B,iBAAiB,8BAA8B;IAC/C,kBAAkB,+BAA+B;IACjD,uBAAuB,oCAAoC;IAC3D,iBAAiB,sBAAsB;IACvC,eAAe,oBAAoB;IACnC,aAAa,0BAA0B;CACxC;AAED;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,SAAgB,IAAI,EAAE,gBAAgB,CAAC;IACvC,SAAgB,SAAS,EAAE,MAAM,CAAC;gBAGhC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,gBAAgB,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE;CAO9B;AAED;;GAEG;AACH,qBAAa,qBAAsB,SAAQ,mBAAmB;IAC5D,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAG3B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE;CAW9B;AAED;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,mBAAmB;IAC7D,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAG3B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE;CAW9B;AAED;;GAEG;AACH,qBAAa,2BAA4B,SAAQ,mBAAmB;IAClE,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAG3B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE;CAW9B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,OAAO,GACb,KAAK,IAAI,mBAAmB,CAE9B"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Error classes for chat session management
3
+ *
4
+ * Provides typed errors for session persistence and retrieval failures.
5
+ * These error classes are shared between Discord, Slack, and other chat platforms.
6
+ */
7
+ /**
8
+ * Error codes for session manager operations
9
+ */
10
+ export var SessionErrorCode;
11
+ (function (SessionErrorCode) {
12
+ SessionErrorCode["STATE_READ_FAILED"] = "SESSION_STATE_READ_FAILED";
13
+ SessionErrorCode["STATE_WRITE_FAILED"] = "SESSION_STATE_WRITE_FAILED";
14
+ SessionErrorCode["DIRECTORY_CREATE_FAILED"] = "SESSION_DIRECTORY_CREATE_FAILED";
15
+ SessionErrorCode["SESSION_NOT_FOUND"] = "SESSION_NOT_FOUND";
16
+ SessionErrorCode["SESSION_EXPIRED"] = "SESSION_EXPIRED";
17
+ SessionErrorCode["INVALID_STATE"] = "SESSION_INVALID_STATE";
18
+ })(SessionErrorCode || (SessionErrorCode = {}));
19
+ /**
20
+ * Base error class for session manager operations
21
+ */
22
+ export class SessionManagerError extends Error {
23
+ code;
24
+ agentName;
25
+ constructor(message, code, agentName, options) {
26
+ super(message, options);
27
+ this.name = "SessionManagerError";
28
+ this.code = code;
29
+ this.agentName = agentName;
30
+ }
31
+ }
32
+ /**
33
+ * Error thrown when session state file cannot be read
34
+ */
35
+ export class SessionStateReadError extends SessionManagerError {
36
+ path;
37
+ constructor(agentName, path, options) {
38
+ super(`Failed to read session state for agent '${agentName}' from '${path}'`, SessionErrorCode.STATE_READ_FAILED, agentName, options);
39
+ this.name = "SessionStateReadError";
40
+ this.path = path;
41
+ }
42
+ }
43
+ /**
44
+ * Error thrown when session state file cannot be written
45
+ */
46
+ export class SessionStateWriteError extends SessionManagerError {
47
+ path;
48
+ constructor(agentName, path, options) {
49
+ super(`Failed to write session state for agent '${agentName}' to '${path}'`, SessionErrorCode.STATE_WRITE_FAILED, agentName, options);
50
+ this.name = "SessionStateWriteError";
51
+ this.path = path;
52
+ }
53
+ }
54
+ /**
55
+ * Error thrown when session directory cannot be created
56
+ */
57
+ export class SessionDirectoryCreateError extends SessionManagerError {
58
+ path;
59
+ constructor(agentName, path, options) {
60
+ super(`Failed to create session directory for agent '${agentName}' at '${path}'`, SessionErrorCode.DIRECTORY_CREATE_FAILED, agentName, options);
61
+ this.name = "SessionDirectoryCreateError";
62
+ this.path = path;
63
+ }
64
+ }
65
+ /**
66
+ * Type guard to check if an error is a SessionManagerError
67
+ */
68
+ export function isSessionManagerError(error) {
69
+ return error instanceof SessionManagerError;
70
+ }
71
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/session-manager/errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;GAEG;AACH,MAAM,CAAN,IAAY,gBAOX;AAPD,WAAY,gBAAgB;IAC1B,mEAA+C,CAAA;IAC/C,qEAAiD,CAAA;IACjD,+EAA2D,CAAA;IAC3D,2DAAuC,CAAA;IACvC,uDAAmC,CAAA;IACnC,2DAAuC,CAAA;AACzC,CAAC,EAPW,gBAAgB,KAAhB,gBAAgB,QAO3B;AAED;;GAEG;AACH,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC5B,IAAI,CAAmB;IACvB,SAAS,CAAS;IAElC,YACE,OAAe,EACf,IAAsB,EACtB,SAAiB,EACjB,OAA2B;QAE3B,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,qBAAsB,SAAQ,mBAAmB;IAC5C,IAAI,CAAS;IAE7B,YACE,SAAiB,EACjB,IAAY,EACZ,OAA2B;QAE3B,KAAK,CACH,2CAA2C,SAAS,WAAW,IAAI,GAAG,EACtE,gBAAgB,CAAC,iBAAiB,EAClC,SAAS,EACT,OAAO,CACR,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,sBAAuB,SAAQ,mBAAmB;IAC7C,IAAI,CAAS;IAE7B,YACE,SAAiB,EACjB,IAAY,EACZ,OAA2B;QAE3B,KAAK,CACH,4CAA4C,SAAS,SAAS,IAAI,GAAG,EACrE,gBAAgB,CAAC,kBAAkB,EACnC,SAAS,EACT,OAAO,CACR,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;QACrC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,2BAA4B,SAAQ,mBAAmB;IAClD,IAAI,CAAS;IAE7B,YACE,SAAiB,EACjB,IAAY,EACZ,OAA2B;QAE3B,KAAK,CACH,iDAAiD,SAAS,SAAS,IAAI,GAAG,EAC1E,gBAAgB,CAAC,uBAAuB,EACxC,SAAS,EACT,OAAO,CACR,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,6BAA6B,CAAC;QAC1C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAAc;IAEd,OAAO,KAAK,YAAY,mBAAmB,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Session manager module for chat platforms
3
+ *
4
+ * Provides per-channel session management for Claude conversations.
5
+ * This module is shared between Discord, Slack, and other chat platforms.
6
+ */
7
+ export { ChatSessionManager } from "./session-manager.js";
8
+ export { ChannelSessionSchema, ChatSessionStateSchema, type ChannelSession, type ChatSessionState, type SessionManagerLogger, type ChatSessionManagerOptions, type SessionResult, type IChatSessionManager, createInitialSessionState, createChannelSession, } from "./types.js";
9
+ export { SessionErrorCode, SessionManagerError, SessionStateReadError, SessionStateWriteError, SessionDirectoryCreateError, isSessionManagerError, } from "./errors.js";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/session-manager/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAEL,oBAAoB,EACpB,sBAAsB,EAEtB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,oBAAoB,EACzB,KAAK,yBAAyB,EAC9B,KAAK,aAAa,EAClB,KAAK,mBAAmB,EAExB,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,2BAA2B,EAC3B,qBAAqB,GACtB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Session manager module for chat platforms
3
+ *
4
+ * Provides per-channel session management for Claude conversations.
5
+ * This module is shared between Discord, Slack, and other chat platforms.
6
+ */
7
+ export { ChatSessionManager } from "./session-manager.js";
8
+ export {
9
+ // Schemas
10
+ ChannelSessionSchema, ChatSessionStateSchema,
11
+ // Factory functions
12
+ createInitialSessionState, createChannelSession, } from "./types.js";
13
+ export { SessionErrorCode, SessionManagerError, SessionStateReadError, SessionStateWriteError, SessionDirectoryCreateError, isSessionManagerError, } from "./errors.js";
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/session-manager/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO;AACL,UAAU;AACV,oBAAoB,EACpB,sBAAsB;AAQtB,oBAAoB;AACpB,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,sBAAsB,EACtB,2BAA2B,EAC3B,qBAAqB,GACtB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Session manager for chat channel conversations
3
+ *
4
+ * Provides per-channel session management for Claude conversations,
5
+ * enabling conversation context preservation across chat channels.
6
+ *
7
+ * This implementation is shared between Discord, Slack, and other chat platforms.
8
+ * Sessions are stored at .herdctl/<platform>-sessions/<agent-name>.yaml
9
+ */
10
+ import { type ChatSessionManagerOptions, type IChatSessionManager, type SessionResult, type ChannelSession } from "./types.js";
11
+ /**
12
+ * ChatSessionManager manages per-channel Claude sessions for a chat agent.
13
+ *
14
+ * Each agent has its own ChatSessionManager instance, storing session mappings
15
+ * in a YAML file at .herdctl/<platform>-sessions/<agent-name>.yaml
16
+ *
17
+ * Features:
18
+ * - Create new sessions for channels/DMs
19
+ * - Resume existing sessions when user sends messages
20
+ * - Automatic session expiry based on configurable timeout
21
+ * - Cleanup expired sessions on startup
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const sessionManager = new ChatSessionManager({
26
+ * platform: 'discord',
27
+ * agentName: 'my-agent',
28
+ * stateDir: '.herdctl',
29
+ * sessionExpiryHours: 24,
30
+ * });
31
+ *
32
+ * // Get or create a session for a channel
33
+ * const { sessionId, isNew } = await sessionManager.getOrCreateSession('channel-123');
34
+ *
35
+ * // After sending/receiving a message
36
+ * await sessionManager.touchSession('channel-123');
37
+ *
38
+ * // Cleanup expired sessions on startup
39
+ * const cleanedUp = await sessionManager.cleanupExpiredSessions();
40
+ * ```
41
+ */
42
+ export declare class ChatSessionManager implements IChatSessionManager {
43
+ readonly agentName: string;
44
+ readonly platform: string;
45
+ private readonly stateDir;
46
+ private readonly sessionExpiryHours;
47
+ private readonly logger;
48
+ private readonly stateFilePath;
49
+ private state;
50
+ constructor(options: ChatSessionManagerOptions);
51
+ /**
52
+ * Get or create a session for a channel/DM
53
+ *
54
+ * If an active (non-expired) session exists, returns it.
55
+ * Otherwise, creates a new session with a generated session ID.
56
+ */
57
+ getOrCreateSession(channelId: string): Promise<SessionResult>;
58
+ /**
59
+ * Update the last message timestamp for a session
60
+ */
61
+ touchSession(channelId: string): Promise<void>;
62
+ /**
63
+ * Get an existing session without creating one
64
+ *
65
+ * Returns null if no session exists or if the session is expired.
66
+ */
67
+ getSession(channelId: string): Promise<ChannelSession | null>;
68
+ /**
69
+ * Store or update the session ID for a channel
70
+ *
71
+ * Called after a job completes to store the SDK-provided session ID.
72
+ */
73
+ setSession(channelId: string, sessionId: string): Promise<void>;
74
+ /**
75
+ * Clear a specific session
76
+ */
77
+ clearSession(channelId: string): Promise<boolean>;
78
+ /**
79
+ * Clean up all expired sessions
80
+ *
81
+ * Should be called on connector startup and periodically.
82
+ */
83
+ cleanupExpiredSessions(): Promise<number>;
84
+ /**
85
+ * Get the count of active (non-expired) sessions
86
+ *
87
+ * Useful for logging during shutdown to confirm sessions are preserved.
88
+ */
89
+ getActiveSessionCount(): Promise<number>;
90
+ /**
91
+ * Generate a unique session ID
92
+ * Format: <platform>-<agentName>-<uuid>
93
+ */
94
+ private generateSessionId;
95
+ /**
96
+ * Check if a session is expired
97
+ */
98
+ private isSessionExpired;
99
+ /**
100
+ * Load session state from disk
101
+ *
102
+ * Returns cached state if available, otherwise loads from file.
103
+ * Creates initial state if file doesn't exist.
104
+ */
105
+ private loadState;
106
+ /**
107
+ * Save session state to disk atomically
108
+ */
109
+ private saveState;
110
+ /**
111
+ * Ensure the sessions directory exists
112
+ */
113
+ private ensureDirectoryExists;
114
+ /**
115
+ * Generate a temp file path for atomic writes
116
+ */
117
+ private generateTempPath;
118
+ /**
119
+ * Rename with retry logic for Windows compatibility
120
+ */
121
+ private renameWithRetry;
122
+ }
123
+ //# sourceMappingURL=session-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../src/session-manager/session-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH,OAAO,EACL,KAAK,yBAAyB,EAE9B,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,KAAK,cAAc,EAIpB,MAAM,YAAY,CAAC;AAsBpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,kBAAmB,YAAW,mBAAmB;IAC5D,SAAgB,SAAS,EAAE,MAAM,CAAC;IAClC,SAAgB,QAAQ,EAAE,MAAM,CAAC;IAEjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAC9C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAGvC,OAAO,CAAC,KAAK,CAAiC;gBAElC,OAAO,EAAE,yBAAyB;IAoB9C;;;;;OAKG;IACG,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAkCnE;;OAEG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBpD;;;;OAIG;IACG,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAsBnE;;;;OAIG;IACG,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuBrE;;OAEG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAevD;;;;OAIG;IACG,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC;IA0B/C;;;;OAIG;IACG,qBAAqB,IAAI,OAAO,CAAC,MAAM,CAAC;IAkB9C;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAOxB;;;;;OAKG;YACW,SAAS;IAuDvB;;OAEG;YACW,SAAS;IA4BvB;;OAEG;YACW,qBAAqB;IAenC;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAOxB;;OAEG;YACW,eAAe;CA+B9B"}