@herdctl/chat 0.0.1 → 0.2.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 (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,394 @@
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 { mkdir } from "node:fs/promises";
11
+ import { join, dirname } from "node:path";
12
+ import { randomUUID } from "node:crypto";
13
+ import { stringify as stringifyYaml, parse as parseYaml } from "yaml";
14
+ import { readFile, writeFile, rename, unlink } from "node:fs/promises";
15
+ import { randomBytes } from "node:crypto";
16
+ import { createLogger } from "@herdctl/core";
17
+ import { ChatSessionStateSchema, createInitialSessionState, } from "./types.js";
18
+ import { SessionStateReadError, SessionStateWriteError, SessionDirectoryCreateError, } from "./errors.js";
19
+ // =============================================================================
20
+ // Default Logger
21
+ // =============================================================================
22
+ function createDefaultLogger(platform, agentName) {
23
+ return createLogger(`${platform}-session:${agentName}`);
24
+ }
25
+ // =============================================================================
26
+ // Session Manager Implementation
27
+ // =============================================================================
28
+ /**
29
+ * ChatSessionManager manages per-channel Claude sessions for a chat agent.
30
+ *
31
+ * Each agent has its own ChatSessionManager instance, storing session mappings
32
+ * in a YAML file at .herdctl/<platform>-sessions/<agent-name>.yaml
33
+ *
34
+ * Features:
35
+ * - Create new sessions for channels/DMs
36
+ * - Resume existing sessions when user sends messages
37
+ * - Automatic session expiry based on configurable timeout
38
+ * - Cleanup expired sessions on startup
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const sessionManager = new ChatSessionManager({
43
+ * platform: 'discord',
44
+ * agentName: 'my-agent',
45
+ * stateDir: '.herdctl',
46
+ * sessionExpiryHours: 24,
47
+ * });
48
+ *
49
+ * // Get or create a session for a channel
50
+ * const { sessionId, isNew } = await sessionManager.getOrCreateSession('channel-123');
51
+ *
52
+ * // After sending/receiving a message
53
+ * await sessionManager.touchSession('channel-123');
54
+ *
55
+ * // Cleanup expired sessions on startup
56
+ * const cleanedUp = await sessionManager.cleanupExpiredSessions();
57
+ * ```
58
+ */
59
+ export class ChatSessionManager {
60
+ agentName;
61
+ platform;
62
+ stateDir;
63
+ sessionExpiryHours;
64
+ logger;
65
+ stateFilePath;
66
+ // In-memory cache of session state
67
+ state = null;
68
+ constructor(options) {
69
+ this.platform = options.platform;
70
+ this.agentName = options.agentName;
71
+ this.stateDir = options.stateDir;
72
+ this.sessionExpiryHours = options.sessionExpiryHours ?? 24;
73
+ this.logger =
74
+ options.logger ?? createDefaultLogger(options.platform, options.agentName);
75
+ // Compute state file path: .herdctl/<platform>-sessions/<agent>.yaml
76
+ this.stateFilePath = join(this.stateDir, `${this.platform}-sessions`, `${this.agentName}.yaml`);
77
+ }
78
+ // ===========================================================================
79
+ // Public API
80
+ // ===========================================================================
81
+ /**
82
+ * Get or create a session for a channel/DM
83
+ *
84
+ * If an active (non-expired) session exists, returns it.
85
+ * Otherwise, creates a new session with a generated session ID.
86
+ */
87
+ async getOrCreateSession(channelId) {
88
+ const existingSession = await this.getSession(channelId);
89
+ if (existingSession) {
90
+ this.logger.info("Resuming existing session", {
91
+ channelId,
92
+ sessionId: existingSession.sessionId,
93
+ });
94
+ return {
95
+ sessionId: existingSession.sessionId,
96
+ isNew: false,
97
+ };
98
+ }
99
+ // Create new session
100
+ const sessionId = this.generateSessionId();
101
+ const state = await this.loadState();
102
+ const now = new Date().toISOString();
103
+ state.channels[channelId] = {
104
+ sessionId,
105
+ lastMessageAt: now,
106
+ };
107
+ await this.saveState(state);
108
+ this.logger.info("Created new session", { channelId, sessionId });
109
+ return {
110
+ sessionId,
111
+ isNew: true,
112
+ };
113
+ }
114
+ /**
115
+ * Update the last message timestamp for a session
116
+ */
117
+ async touchSession(channelId) {
118
+ const state = await this.loadState();
119
+ const session = state.channels[channelId];
120
+ if (!session) {
121
+ this.logger.warn("Attempted to touch non-existent session", {
122
+ channelId,
123
+ });
124
+ return;
125
+ }
126
+ session.lastMessageAt = new Date().toISOString();
127
+ await this.saveState(state);
128
+ this.logger.debug("Touched session", {
129
+ channelId,
130
+ sessionId: session.sessionId,
131
+ });
132
+ }
133
+ /**
134
+ * Get an existing session without creating one
135
+ *
136
+ * Returns null if no session exists or if the session is expired.
137
+ */
138
+ async getSession(channelId) {
139
+ const state = await this.loadState();
140
+ const session = state.channels[channelId];
141
+ if (!session) {
142
+ return null;
143
+ }
144
+ // Check if session is expired
145
+ if (this.isSessionExpired(session)) {
146
+ this.logger.info("Session expired", {
147
+ channelId,
148
+ sessionId: session.sessionId,
149
+ lastMessageAt: session.lastMessageAt,
150
+ expiryHours: this.sessionExpiryHours,
151
+ });
152
+ return null;
153
+ }
154
+ return session;
155
+ }
156
+ /**
157
+ * Store or update the session ID for a channel
158
+ *
159
+ * Called after a job completes to store the SDK-provided session ID.
160
+ */
161
+ async setSession(channelId, sessionId) {
162
+ const state = await this.loadState();
163
+ const now = new Date().toISOString();
164
+ const existingSession = state.channels[channelId];
165
+ state.channels[channelId] = {
166
+ sessionId,
167
+ lastMessageAt: now,
168
+ };
169
+ await this.saveState(state);
170
+ if (existingSession) {
171
+ this.logger.debug("Updated session", {
172
+ channelId,
173
+ oldSessionId: existingSession.sessionId,
174
+ newSessionId: sessionId,
175
+ });
176
+ }
177
+ else {
178
+ this.logger.info("Stored new session", { channelId, sessionId });
179
+ }
180
+ }
181
+ /**
182
+ * Clear a specific session
183
+ */
184
+ async clearSession(channelId) {
185
+ const state = await this.loadState();
186
+ if (!state.channels[channelId]) {
187
+ return false;
188
+ }
189
+ const sessionId = state.channels[channelId].sessionId;
190
+ delete state.channels[channelId];
191
+ await this.saveState(state);
192
+ this.logger.info("Cleared session", { channelId, sessionId });
193
+ return true;
194
+ }
195
+ /**
196
+ * Clean up all expired sessions
197
+ *
198
+ * Should be called on connector startup and periodically.
199
+ */
200
+ async cleanupExpiredSessions() {
201
+ const state = await this.loadState();
202
+ const channelIds = Object.keys(state.channels);
203
+ let cleanedUp = 0;
204
+ for (const channelId of channelIds) {
205
+ const session = state.channels[channelId];
206
+ if (this.isSessionExpired(session)) {
207
+ this.logger.debug("Cleaning up expired session", {
208
+ channelId,
209
+ sessionId: session.sessionId,
210
+ lastMessageAt: session.lastMessageAt,
211
+ });
212
+ delete state.channels[channelId];
213
+ cleanedUp++;
214
+ }
215
+ }
216
+ if (cleanedUp > 0) {
217
+ await this.saveState(state);
218
+ this.logger.info("Cleaned up expired sessions", { count: cleanedUp });
219
+ }
220
+ return cleanedUp;
221
+ }
222
+ /**
223
+ * Get the count of active (non-expired) sessions
224
+ *
225
+ * Useful for logging during shutdown to confirm sessions are preserved.
226
+ */
227
+ async getActiveSessionCount() {
228
+ const state = await this.loadState();
229
+ let activeCount = 0;
230
+ for (const channelId of Object.keys(state.channels)) {
231
+ const session = state.channels[channelId];
232
+ if (!this.isSessionExpired(session)) {
233
+ activeCount++;
234
+ }
235
+ }
236
+ return activeCount;
237
+ }
238
+ // ===========================================================================
239
+ // Private Helpers
240
+ // ===========================================================================
241
+ /**
242
+ * Generate a unique session ID
243
+ * Format: <platform>-<agentName>-<uuid>
244
+ */
245
+ generateSessionId() {
246
+ return `${this.platform}-${this.agentName}-${randomUUID()}`;
247
+ }
248
+ /**
249
+ * Check if a session is expired
250
+ */
251
+ isSessionExpired(session) {
252
+ const lastMessageAt = new Date(session.lastMessageAt);
253
+ const now = new Date();
254
+ const expiryMs = this.sessionExpiryHours * 60 * 60 * 1000;
255
+ return now.getTime() - lastMessageAt.getTime() > expiryMs;
256
+ }
257
+ /**
258
+ * Load session state from disk
259
+ *
260
+ * Returns cached state if available, otherwise loads from file.
261
+ * Creates initial state if file doesn't exist.
262
+ */
263
+ async loadState() {
264
+ if (this.state) {
265
+ return this.state;
266
+ }
267
+ let content;
268
+ try {
269
+ content = await readFile(this.stateFilePath, "utf-8");
270
+ }
271
+ catch (error) {
272
+ const code = error.code;
273
+ // File doesn't exist - create initial state
274
+ if (code === "ENOENT") {
275
+ this.state = createInitialSessionState(this.agentName);
276
+ return this.state;
277
+ }
278
+ // Other read errors are fatal
279
+ throw new SessionStateReadError(this.agentName, this.stateFilePath, {
280
+ cause: error,
281
+ });
282
+ }
283
+ // Handle empty file
284
+ if (content.trim() === "") {
285
+ this.state = createInitialSessionState(this.agentName);
286
+ return this.state;
287
+ }
288
+ // Parse YAML - treat parse errors as corrupted state
289
+ let parsed;
290
+ try {
291
+ parsed = parseYaml(content);
292
+ }
293
+ catch (error) {
294
+ this.logger.warn("Corrupted session state file, creating fresh state", {
295
+ error: error.message,
296
+ });
297
+ this.state = createInitialSessionState(this.agentName);
298
+ return this.state;
299
+ }
300
+ // Validate against schema - treat validation errors as corrupted state
301
+ const validated = ChatSessionStateSchema.safeParse(parsed);
302
+ if (!validated.success) {
303
+ this.logger.warn("Corrupted session state file, creating fresh state", {
304
+ error: validated.error.message,
305
+ });
306
+ this.state = createInitialSessionState(this.agentName);
307
+ return this.state;
308
+ }
309
+ this.state = validated.data;
310
+ return this.state;
311
+ }
312
+ /**
313
+ * Save session state to disk atomically
314
+ */
315
+ async saveState(state) {
316
+ // Ensure directory exists
317
+ await this.ensureDirectoryExists();
318
+ // Update in-memory cache
319
+ this.state = state;
320
+ // Write atomically
321
+ const yamlContent = stringifyYaml(state, { indent: 2, lineWidth: 120 });
322
+ const tempPath = this.generateTempPath(this.stateFilePath);
323
+ try {
324
+ await writeFile(tempPath, yamlContent, "utf-8");
325
+ await this.renameWithRetry(tempPath, this.stateFilePath);
326
+ }
327
+ catch (error) {
328
+ // Clean up temp file on failure
329
+ try {
330
+ await unlink(tempPath);
331
+ }
332
+ catch {
333
+ // Ignore cleanup errors
334
+ }
335
+ throw new SessionStateWriteError(this.agentName, this.stateFilePath, {
336
+ cause: error,
337
+ });
338
+ }
339
+ }
340
+ /**
341
+ * Ensure the sessions directory exists
342
+ */
343
+ async ensureDirectoryExists() {
344
+ const dir = dirname(this.stateFilePath);
345
+ try {
346
+ await mkdir(dir, { recursive: true });
347
+ }
348
+ catch (error) {
349
+ const code = error.code;
350
+ // EEXIST is fine - directory already exists
351
+ if (code !== "EEXIST") {
352
+ throw new SessionDirectoryCreateError(this.agentName, dir, {
353
+ cause: error,
354
+ });
355
+ }
356
+ }
357
+ }
358
+ /**
359
+ * Generate a temp file path for atomic writes
360
+ */
361
+ generateTempPath(targetPath) {
362
+ const dir = dirname(targetPath);
363
+ const random = randomBytes(8).toString("hex");
364
+ const filename = `${this.agentName}.yaml`;
365
+ return join(dir, `.${filename}.tmp.${random}`);
366
+ }
367
+ /**
368
+ * Rename with retry logic for Windows compatibility
369
+ */
370
+ async renameWithRetry(oldPath, newPath, maxRetries = 3, baseDelayMs = 50) {
371
+ let lastError;
372
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
373
+ try {
374
+ await rename(oldPath, newPath);
375
+ return;
376
+ }
377
+ catch (error) {
378
+ lastError = error;
379
+ const code = error.code;
380
+ // Only retry on Windows-specific errors
381
+ if (code !== "EACCES" && code !== "EPERM") {
382
+ throw error;
383
+ }
384
+ // Don't delay on the last attempt
385
+ if (attempt < maxRetries) {
386
+ const delay = baseDelayMs * Math.pow(2, attempt);
387
+ await new Promise((resolve) => setTimeout(resolve, delay));
388
+ }
389
+ }
390
+ }
391
+ throw lastError;
392
+ }
393
+ }
394
+ //# sourceMappingURL=session-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-manager.js","sourceRoot":"","sources":["../../src/session-manager/session-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Type definitions for chat session management
3
+ *
4
+ * Provides interfaces for per-channel session state tracking,
5
+ * enabling conversation context preservation across chat channels.
6
+ *
7
+ * This module is shared between Discord, Slack, and other chat platforms.
8
+ */
9
+ import { z } from "zod";
10
+ /**
11
+ * Schema for individual channel session mapping
12
+ */
13
+ export declare const ChannelSessionSchema: z.ZodObject<{
14
+ /** Claude session ID for resuming conversations */
15
+ sessionId: z.ZodString;
16
+ /** ISO timestamp when last message was sent/received */
17
+ lastMessageAt: z.ZodString;
18
+ }, "strip", z.ZodTypeAny, {
19
+ sessionId: string;
20
+ lastMessageAt: string;
21
+ }, {
22
+ sessionId: string;
23
+ lastMessageAt: string;
24
+ }>;
25
+ /**
26
+ * Schema for the entire agent's chat session state file
27
+ *
28
+ * Stored at .herdctl/<platform>-sessions/<agent-name>.yaml
29
+ * For example:
30
+ * - .herdctl/discord-sessions/my-agent.yaml
31
+ * - .herdctl/slack-sessions/my-agent.yaml
32
+ */
33
+ export declare const ChatSessionStateSchema: z.ZodObject<{
34
+ /**
35
+ * Schema version. Accepts 1, 2, and 3 — all have identical structure.
36
+ * (1 was Discord's original, 2 was Slack's, 3 was a mistaken bump.)
37
+ * Only increment this when the schema shape actually changes, and add
38
+ * migration logic to handle older versions.
39
+ */
40
+ version: z.ZodUnion<[z.ZodLiteral<1>, z.ZodLiteral<2>, z.ZodLiteral<3>]>;
41
+ /** Agent name this session state belongs to */
42
+ agentName: z.ZodString;
43
+ /** Map of channel ID to session info */
44
+ channels: z.ZodRecord<z.ZodString, z.ZodObject<{
45
+ /** Claude session ID for resuming conversations */
46
+ sessionId: z.ZodString;
47
+ /** ISO timestamp when last message was sent/received */
48
+ lastMessageAt: z.ZodString;
49
+ }, "strip", z.ZodTypeAny, {
50
+ sessionId: string;
51
+ lastMessageAt: string;
52
+ }, {
53
+ sessionId: string;
54
+ lastMessageAt: string;
55
+ }>>;
56
+ }, "strip", z.ZodTypeAny, {
57
+ version: 3 | 2 | 1;
58
+ agentName: string;
59
+ channels: Record<string, {
60
+ sessionId: string;
61
+ lastMessageAt: string;
62
+ }>;
63
+ }, {
64
+ version: 3 | 2 | 1;
65
+ agentName: string;
66
+ channels: Record<string, {
67
+ sessionId: string;
68
+ lastMessageAt: string;
69
+ }>;
70
+ }>;
71
+ export type ChannelSession = z.infer<typeof ChannelSessionSchema>;
72
+ export type ChatSessionState = z.infer<typeof ChatSessionStateSchema>;
73
+ /**
74
+ * Logger interface for session manager operations
75
+ */
76
+ export interface SessionManagerLogger {
77
+ debug(message: string, data?: Record<string, unknown>): void;
78
+ info(message: string, data?: Record<string, unknown>): void;
79
+ warn(message: string, data?: Record<string, unknown>): void;
80
+ error(message: string, data?: Record<string, unknown>): void;
81
+ }
82
+ /**
83
+ * Options for configuring the ChatSessionManager
84
+ */
85
+ export interface ChatSessionManagerOptions {
86
+ /**
87
+ * Platform identifier (e.g., "discord", "slack")
88
+ * Used to compute storage path and session ID prefix
89
+ */
90
+ platform: string;
91
+ /**
92
+ * Name of the agent this session manager is for
93
+ */
94
+ agentName: string;
95
+ /**
96
+ * Root path for state storage (e.g., .herdctl)
97
+ * Sessions will be stored at <stateDir>/<platform>-sessions/<agent-name>.yaml
98
+ */
99
+ stateDir: string;
100
+ /**
101
+ * Session expiry timeout in hours
102
+ * Sessions inactive for longer than this will be considered expired
103
+ *
104
+ * @default 24
105
+ */
106
+ sessionExpiryHours?: number;
107
+ /**
108
+ * Logger for session manager operations
109
+ *
110
+ * @default console-based logger using createLogger from @herdctl/core
111
+ */
112
+ logger?: SessionManagerLogger;
113
+ }
114
+ /**
115
+ * Result of getting or creating a session
116
+ */
117
+ export interface SessionResult {
118
+ /** Claude session ID */
119
+ sessionId: string;
120
+ /** Whether this is a newly created session */
121
+ isNew: boolean;
122
+ }
123
+ /**
124
+ * Interface that all chat session managers must implement
125
+ */
126
+ export interface IChatSessionManager {
127
+ /**
128
+ * Get or create a session for a channel
129
+ *
130
+ * If an active (non-expired) session exists, returns it.
131
+ * Otherwise, creates a new session.
132
+ *
133
+ * @param channelId - Channel or DM ID
134
+ * @returns Session info with sessionId and isNew flag
135
+ */
136
+ getOrCreateSession(channelId: string): Promise<SessionResult>;
137
+ /**
138
+ * Update the last message timestamp for a session
139
+ *
140
+ * Called after each message to keep the session active.
141
+ *
142
+ * @param channelId - Channel or DM ID
143
+ */
144
+ touchSession(channelId: string): Promise<void>;
145
+ /**
146
+ * Get an existing session without creating one
147
+ *
148
+ * Returns null if no session exists or if the session is expired.
149
+ *
150
+ * @param channelId - Channel or DM ID
151
+ * @returns The session if it exists and is not expired, null otherwise
152
+ */
153
+ getSession(channelId: string): Promise<ChannelSession | null>;
154
+ /**
155
+ * Store or update the session ID for a channel
156
+ *
157
+ * Called after a job completes to store the SDK-provided session ID.
158
+ * This enables conversation continuity by allowing subsequent requests
159
+ * to resume from this session.
160
+ *
161
+ * @param channelId - Channel or DM ID
162
+ * @param sessionId - The Claude Agent SDK session ID
163
+ */
164
+ setSession(channelId: string, sessionId: string): Promise<void>;
165
+ /**
166
+ * Clear a specific session
167
+ *
168
+ * @param channelId - Channel or DM ID
169
+ * @returns true if the session was cleared, false if it didn't exist
170
+ */
171
+ clearSession(channelId: string): Promise<boolean>;
172
+ /**
173
+ * Clean up all expired sessions
174
+ *
175
+ * Should be called on connector startup and periodically.
176
+ *
177
+ * @returns Number of sessions that were cleaned up
178
+ */
179
+ cleanupExpiredSessions(): Promise<number>;
180
+ /**
181
+ * Get the count of active (non-expired) sessions
182
+ *
183
+ * Useful for logging during shutdown to confirm sessions are preserved.
184
+ *
185
+ * @returns Number of active sessions
186
+ */
187
+ getActiveSessionCount(): Promise<number>;
188
+ /**
189
+ * Name of the agent this session manager is for
190
+ */
191
+ readonly agentName: string;
192
+ /**
193
+ * Platform identifier this session manager is for
194
+ */
195
+ readonly platform: string;
196
+ }
197
+ /**
198
+ * Create initial session state for a new agent
199
+ */
200
+ export declare function createInitialSessionState(agentName: string): ChatSessionState;
201
+ /**
202
+ * Create a new channel session
203
+ */
204
+ export declare function createChannelSession(sessionId: string): ChannelSession;
205
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/session-manager/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Type definitions for chat session management
3
+ *
4
+ * Provides interfaces for per-channel session state tracking,
5
+ * enabling conversation context preservation across chat channels.
6
+ *
7
+ * This module is shared between Discord, Slack, and other chat platforms.
8
+ */
9
+ import { z } from "zod";
10
+ // =============================================================================
11
+ // Session Schema
12
+ // =============================================================================
13
+ /**
14
+ * Schema for individual channel session mapping
15
+ */
16
+ export const ChannelSessionSchema = z.object({
17
+ /** Claude session ID for resuming conversations */
18
+ sessionId: z.string().min(1, "Session ID cannot be empty"),
19
+ /** ISO timestamp when last message was sent/received */
20
+ lastMessageAt: z.string().datetime({
21
+ message: "lastMessageAt must be a valid ISO datetime string",
22
+ }),
23
+ });
24
+ /**
25
+ * Schema for the entire agent's chat session state file
26
+ *
27
+ * Stored at .herdctl/<platform>-sessions/<agent-name>.yaml
28
+ * For example:
29
+ * - .herdctl/discord-sessions/my-agent.yaml
30
+ * - .herdctl/slack-sessions/my-agent.yaml
31
+ */
32
+ export const ChatSessionStateSchema = z.object({
33
+ /**
34
+ * Schema version. Accepts 1, 2, and 3 — all have identical structure.
35
+ * (1 was Discord's original, 2 was Slack's, 3 was a mistaken bump.)
36
+ * Only increment this when the schema shape actually changes, and add
37
+ * migration logic to handle older versions.
38
+ */
39
+ version: z.union([z.literal(1), z.literal(2), z.literal(3)]),
40
+ /** Agent name this session state belongs to */
41
+ agentName: z.string().min(1, "Agent name cannot be empty"),
42
+ /** Map of channel ID to session info */
43
+ channels: z.record(z.string(), ChannelSessionSchema),
44
+ });
45
+ // =============================================================================
46
+ // Factory Functions
47
+ // =============================================================================
48
+ /**
49
+ * Create initial session state for a new agent
50
+ */
51
+ export function createInitialSessionState(agentName) {
52
+ return {
53
+ version: 1,
54
+ agentName,
55
+ channels: {},
56
+ };
57
+ }
58
+ /**
59
+ * Create a new channel session
60
+ */
61
+ export function createChannelSession(sessionId) {
62
+ return {
63
+ sessionId,
64
+ lastMessageAt: new Date().toISOString(),
65
+ };
66
+ }
67
+ //# sourceMappingURL=types.js.map