@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.
- package/LICENSE +21 -0
- package/dist/__tests__/dm-filter.test.d.ts +5 -0
- package/dist/__tests__/dm-filter.test.d.ts.map +1 -0
- package/dist/__tests__/dm-filter.test.js +136 -0
- package/dist/__tests__/dm-filter.test.js.map +1 -0
- package/dist/__tests__/error-handler.test.d.ts +5 -0
- package/dist/__tests__/error-handler.test.d.ts.map +1 -0
- package/dist/__tests__/error-handler.test.js +235 -0
- package/dist/__tests__/error-handler.test.js.map +1 -0
- package/dist/__tests__/errors.test.d.ts +5 -0
- package/dist/__tests__/errors.test.d.ts.map +1 -0
- package/dist/__tests__/errors.test.js +140 -0
- package/dist/__tests__/errors.test.js.map +1 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/index.test.js +25 -0
- package/dist/__tests__/index.test.js.map +1 -0
- package/dist/__tests__/message-extraction.test.d.ts +5 -0
- package/dist/__tests__/message-extraction.test.d.ts.map +1 -0
- package/dist/__tests__/message-extraction.test.js +157 -0
- package/dist/__tests__/message-extraction.test.js.map +1 -0
- package/dist/__tests__/message-splitting.test.d.ts +5 -0
- package/dist/__tests__/message-splitting.test.d.ts.map +1 -0
- package/dist/__tests__/message-splitting.test.js +153 -0
- package/dist/__tests__/message-splitting.test.js.map +1 -0
- package/dist/__tests__/session-manager.test.d.ts +2 -0
- package/dist/__tests__/session-manager.test.d.ts.map +1 -0
- package/dist/__tests__/session-manager.test.js +779 -0
- package/dist/__tests__/session-manager.test.js.map +1 -0
- package/dist/__tests__/status-formatting.test.d.ts +5 -0
- package/dist/__tests__/status-formatting.test.d.ts.map +1 -0
- package/dist/__tests__/status-formatting.test.js +160 -0
- package/dist/__tests__/status-formatting.test.js.map +1 -0
- package/dist/__tests__/streaming-responder.test.d.ts +5 -0
- package/dist/__tests__/streaming-responder.test.d.ts.map +1 -0
- package/dist/__tests__/streaming-responder.test.js +154 -0
- package/dist/__tests__/streaming-responder.test.js.map +1 -0
- package/dist/dm-filter.d.ts +121 -0
- package/dist/dm-filter.d.ts.map +1 -0
- package/dist/dm-filter.js +162 -0
- package/dist/dm-filter.js.map +1 -0
- package/dist/error-handler.d.ts +217 -0
- package/dist/error-handler.d.ts.map +1 -0
- package/dist/error-handler.js +313 -0
- package/dist/error-handler.js.map +1 -0
- package/dist/errors.d.ts +118 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +157 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/message-extraction.d.ts +81 -0
- package/dist/message-extraction.d.ts.map +1 -0
- package/dist/message-extraction.js +90 -0
- package/dist/message-extraction.js.map +1 -0
- package/dist/message-splitting.d.ts +133 -0
- package/dist/message-splitting.d.ts.map +1 -0
- package/dist/message-splitting.js +188 -0
- package/dist/message-splitting.js.map +1 -0
- package/dist/session-manager/errors.d.ts +59 -0
- package/dist/session-manager/errors.d.ts.map +1 -0
- package/dist/session-manager/errors.js +71 -0
- package/dist/session-manager/errors.js.map +1 -0
- package/dist/session-manager/index.d.ts +10 -0
- package/dist/session-manager/index.d.ts.map +1 -0
- package/dist/session-manager/index.js +14 -0
- package/dist/session-manager/index.js.map +1 -0
- package/dist/session-manager/session-manager.d.ts +123 -0
- package/dist/session-manager/session-manager.d.ts.map +1 -0
- package/dist/session-manager/session-manager.js +394 -0
- package/dist/session-manager/session-manager.js.map +1 -0
- package/dist/session-manager/types.d.ts +205 -0
- package/dist/session-manager/types.d.ts.map +1 -0
- package/dist/session-manager/types.js +67 -0
- package/dist/session-manager/types.js.map +1 -0
- package/dist/status-formatting.d.ts +147 -0
- package/dist/status-formatting.d.ts.map +1 -0
- package/dist/status-formatting.js +234 -0
- package/dist/status-formatting.js.map +1 -0
- package/dist/streaming-responder.d.ts +130 -0
- package/dist/streaming-responder.d.ts.map +1 -0
- package/dist/streaming-responder.js +178 -0
- package/dist/streaming-responder.js.map +1 -0
- package/dist/types.d.ts +184 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- 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
|