@herdctl/discord 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/auto-mode-handler.test.d.ts +2 -0
- package/dist/__tests__/auto-mode-handler.test.d.ts.map +1 -0
- package/dist/__tests__/auto-mode-handler.test.js +362 -0
- package/dist/__tests__/auto-mode-handler.test.js.map +1 -0
- package/dist/__tests__/discord-connector.test.d.ts +2 -0
- package/dist/__tests__/discord-connector.test.d.ts.map +1 -0
- package/dist/__tests__/discord-connector.test.js +958 -0
- package/dist/__tests__/discord-connector.test.js.map +1 -0
- package/dist/__tests__/error-handler.test.d.ts +2 -0
- package/dist/__tests__/error-handler.test.d.ts.map +1 -0
- package/dist/__tests__/error-handler.test.js +509 -0
- package/dist/__tests__/error-handler.test.js.map +1 -0
- package/dist/__tests__/errors.test.d.ts +2 -0
- package/dist/__tests__/errors.test.d.ts.map +1 -0
- package/dist/__tests__/errors.test.js +152 -0
- package/dist/__tests__/errors.test.js.map +1 -0
- package/dist/__tests__/logger.test.d.ts +2 -0
- package/dist/__tests__/logger.test.d.ts.map +1 -0
- package/dist/__tests__/logger.test.js +282 -0
- package/dist/__tests__/logger.test.js.map +1 -0
- package/dist/__tests__/mention-handler.test.d.ts +2 -0
- package/dist/__tests__/mention-handler.test.d.ts.map +1 -0
- package/dist/__tests__/mention-handler.test.js +547 -0
- package/dist/__tests__/mention-handler.test.js.map +1 -0
- package/dist/auto-mode-handler.d.ts +145 -0
- package/dist/auto-mode-handler.d.ts.map +1 -0
- package/dist/auto-mode-handler.js +211 -0
- package/dist/auto-mode-handler.js.map +1 -0
- package/dist/commands/__tests__/command-manager.test.d.ts +2 -0
- package/dist/commands/__tests__/command-manager.test.d.ts.map +1 -0
- package/dist/commands/__tests__/command-manager.test.js +307 -0
- package/dist/commands/__tests__/command-manager.test.js.map +1 -0
- package/dist/commands/__tests__/help.test.d.ts +2 -0
- package/dist/commands/__tests__/help.test.d.ts.map +1 -0
- package/dist/commands/__tests__/help.test.js +105 -0
- package/dist/commands/__tests__/help.test.js.map +1 -0
- package/dist/commands/__tests__/reset.test.d.ts +2 -0
- package/dist/commands/__tests__/reset.test.d.ts.map +1 -0
- package/dist/commands/__tests__/reset.test.js +140 -0
- package/dist/commands/__tests__/reset.test.js.map +1 -0
- package/dist/commands/__tests__/status.test.d.ts +2 -0
- package/dist/commands/__tests__/status.test.d.ts.map +1 -0
- package/dist/commands/__tests__/status.test.js +205 -0
- package/dist/commands/__tests__/status.test.js.map +1 -0
- package/dist/commands/command-manager.d.ts +66 -0
- package/dist/commands/command-manager.d.ts.map +1 -0
- package/dist/commands/command-manager.js +191 -0
- package/dist/commands/command-manager.js.map +1 -0
- package/dist/commands/help.d.ts +8 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +27 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/index.d.ts +12 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +13 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/reset.d.ts +9 -0
- package/dist/commands/reset.d.ts.map +1 -0
- package/dist/commands/reset.js +28 -0
- package/dist/commands/reset.js.map +1 -0
- package/dist/commands/status.d.ts +9 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +102 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/types.d.ts +87 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +8 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/discord-connector.d.ts +154 -0
- package/dist/discord-connector.d.ts.map +1 -0
- package/dist/discord-connector.js +638 -0
- package/dist/discord-connector.js.map +1 -0
- package/dist/error-handler.d.ts +237 -0
- package/dist/error-handler.d.ts.map +1 -0
- package/dist/error-handler.js +433 -0
- package/dist/error-handler.js.map +1 -0
- package/dist/errors.d.ts +61 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +77 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +119 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +198 -0
- package/dist/logger.js.map +1 -0
- package/dist/mention-handler.d.ts +176 -0
- package/dist/mention-handler.d.ts.map +1 -0
- package/dist/mention-handler.js +236 -0
- package/dist/mention-handler.js.map +1 -0
- package/dist/session-manager/__tests__/errors.test.d.ts +2 -0
- package/dist/session-manager/__tests__/errors.test.d.ts.map +1 -0
- package/dist/session-manager/__tests__/errors.test.js +124 -0
- package/dist/session-manager/__tests__/errors.test.js.map +1 -0
- package/dist/session-manager/__tests__/session-manager.test.d.ts +2 -0
- package/dist/session-manager/__tests__/session-manager.test.d.ts.map +1 -0
- package/dist/session-manager/__tests__/session-manager.test.js +517 -0
- package/dist/session-manager/__tests__/session-manager.test.js.map +1 -0
- package/dist/session-manager/__tests__/types.test.d.ts +2 -0
- package/dist/session-manager/__tests__/types.test.d.ts.map +1 -0
- package/dist/session-manager/__tests__/types.test.js +169 -0
- package/dist/session-manager/__tests__/types.test.js.map +1 -0
- package/dist/session-manager/errors.d.ts +58 -0
- package/dist/session-manager/errors.d.ts.map +1 -0
- package/dist/session-manager/errors.js +70 -0
- package/dist/session-manager/errors.js.map +1 -0
- package/dist/session-manager/index.d.ts +11 -0
- package/dist/session-manager/index.d.ts.map +1 -0
- package/dist/session-manager/index.js +12 -0
- package/dist/session-manager/index.js.map +1 -0
- package/dist/session-manager/session-manager.d.ts +107 -0
- package/dist/session-manager/session-manager.d.ts.map +1 -0
- package/dist/session-manager/session-manager.js +347 -0
- package/dist/session-manager/session-manager.js.map +1 -0
- package/dist/session-manager/types.d.ts +167 -0
- package/dist/session-manager/types.d.ts.map +1 -0
- package/dist/session-manager/types.js +57 -0
- package/dist/session-manager/types.js.map +1 -0
- package/dist/types.d.ts +323 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/__tests__/formatting.test.d.ts +2 -0
- package/dist/utils/__tests__/formatting.test.d.ts.map +1 -0
- package/dist/utils/__tests__/formatting.test.js +571 -0
- package/dist/utils/__tests__/formatting.test.js.map +1 -0
- package/dist/utils/formatting.d.ts +211 -0
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/utils/formatting.js +305 -0
- package/dist/utils/formatting.js.map +1 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session manager for Discord channel conversations
|
|
3
|
+
*
|
|
4
|
+
* Provides per-channel session management for Claude conversations,
|
|
5
|
+
* enabling conversation context preservation across Discord channels.
|
|
6
|
+
*
|
|
7
|
+
* Sessions are stored at .herdctl/discord-sessions/<agent-name>.yaml
|
|
8
|
+
*/
|
|
9
|
+
import { mkdir } from "node:fs/promises";
|
|
10
|
+
import { join, dirname } from "node:path";
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
12
|
+
import { stringify as stringifyYaml, parse as parseYaml } from "yaml";
|
|
13
|
+
import { readFile, writeFile, rename, unlink } from "node:fs/promises";
|
|
14
|
+
import { randomBytes } from "node:crypto";
|
|
15
|
+
import { DiscordSessionStateSchema, createInitialSessionState, } from "./types.js";
|
|
16
|
+
import { SessionStateReadError, SessionStateWriteError, SessionDirectoryCreateError, } from "./errors.js";
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// Default Logger
|
|
19
|
+
// =============================================================================
|
|
20
|
+
function createDefaultLogger(agentName) {
|
|
21
|
+
const prefix = `[session:${agentName}]`;
|
|
22
|
+
return {
|
|
23
|
+
debug: (msg, data) => console.debug(prefix, msg, data ? JSON.stringify(data) : ""),
|
|
24
|
+
info: (msg, data) => console.info(prefix, msg, data ? JSON.stringify(data) : ""),
|
|
25
|
+
warn: (msg, data) => console.warn(prefix, msg, data ? JSON.stringify(data) : ""),
|
|
26
|
+
error: (msg, data) => console.error(prefix, msg, data ? JSON.stringify(data) : ""),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// Session Manager Implementation
|
|
31
|
+
// =============================================================================
|
|
32
|
+
/**
|
|
33
|
+
* SessionManager manages per-channel Claude sessions for a Discord agent.
|
|
34
|
+
*
|
|
35
|
+
* Each agent has its own SessionManager instance, storing session mappings
|
|
36
|
+
* in a YAML file at .herdctl/discord-sessions/<agent-name>.yaml
|
|
37
|
+
*
|
|
38
|
+
* Features:
|
|
39
|
+
* - Create new sessions for channels/DMs
|
|
40
|
+
* - Resume existing sessions when user sends messages
|
|
41
|
+
* - Automatic session expiry based on configurable timeout
|
|
42
|
+
* - Cleanup expired sessions on startup
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* const sessionManager = new SessionManager({
|
|
47
|
+
* agentName: 'my-agent',
|
|
48
|
+
* stateDir: '.herdctl',
|
|
49
|
+
* sessionExpiryHours: 24,
|
|
50
|
+
* });
|
|
51
|
+
*
|
|
52
|
+
* // Get or create a session for a channel
|
|
53
|
+
* const { sessionId, isNew } = await sessionManager.getOrCreateSession('channel-123');
|
|
54
|
+
*
|
|
55
|
+
* // After sending/receiving a message
|
|
56
|
+
* await sessionManager.touchSession('channel-123');
|
|
57
|
+
*
|
|
58
|
+
* // Cleanup expired sessions on startup
|
|
59
|
+
* const cleanedUp = await sessionManager.cleanupExpiredSessions();
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export class SessionManager {
|
|
63
|
+
agentName;
|
|
64
|
+
stateDir;
|
|
65
|
+
sessionExpiryHours;
|
|
66
|
+
logger;
|
|
67
|
+
stateFilePath;
|
|
68
|
+
// In-memory cache of session state
|
|
69
|
+
state = null;
|
|
70
|
+
constructor(options) {
|
|
71
|
+
this.agentName = options.agentName;
|
|
72
|
+
this.stateDir = options.stateDir;
|
|
73
|
+
this.sessionExpiryHours = options.sessionExpiryHours ?? 24;
|
|
74
|
+
this.logger =
|
|
75
|
+
options.logger ?? createDefaultLogger(options.agentName);
|
|
76
|
+
// Compute state file path
|
|
77
|
+
this.stateFilePath = join(this.stateDir, "discord-sessions", `${this.agentName}.yaml`);
|
|
78
|
+
}
|
|
79
|
+
// ===========================================================================
|
|
80
|
+
// Public API
|
|
81
|
+
// ===========================================================================
|
|
82
|
+
/**
|
|
83
|
+
* Get or create a session for a channel/DM
|
|
84
|
+
*
|
|
85
|
+
* If an active (non-expired) session exists, returns it.
|
|
86
|
+
* Otherwise, creates a new session with a generated session ID.
|
|
87
|
+
*/
|
|
88
|
+
async getOrCreateSession(channelId) {
|
|
89
|
+
const existingSession = await this.getSession(channelId);
|
|
90
|
+
if (existingSession) {
|
|
91
|
+
this.logger.info("Resuming existing session", {
|
|
92
|
+
channelId,
|
|
93
|
+
sessionId: existingSession.sessionId,
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
sessionId: existingSession.sessionId,
|
|
97
|
+
isNew: false,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
// Create new session
|
|
101
|
+
const sessionId = this.generateSessionId();
|
|
102
|
+
const state = await this.loadState();
|
|
103
|
+
const now = new Date().toISOString();
|
|
104
|
+
state.channels[channelId] = {
|
|
105
|
+
sessionId,
|
|
106
|
+
lastMessageAt: now,
|
|
107
|
+
};
|
|
108
|
+
await this.saveState(state);
|
|
109
|
+
this.logger.info("Created new session", { channelId, sessionId });
|
|
110
|
+
return {
|
|
111
|
+
sessionId,
|
|
112
|
+
isNew: true,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Update the last message timestamp for a session
|
|
117
|
+
*/
|
|
118
|
+
async touchSession(channelId) {
|
|
119
|
+
const state = await this.loadState();
|
|
120
|
+
const session = state.channels[channelId];
|
|
121
|
+
if (!session) {
|
|
122
|
+
this.logger.warn("Attempted to touch non-existent session", {
|
|
123
|
+
channelId,
|
|
124
|
+
});
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
session.lastMessageAt = new Date().toISOString();
|
|
128
|
+
await this.saveState(state);
|
|
129
|
+
this.logger.debug("Touched session", {
|
|
130
|
+
channelId,
|
|
131
|
+
sessionId: session.sessionId,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Get an existing session without creating one
|
|
136
|
+
*
|
|
137
|
+
* Returns null if no session exists or if the session is expired.
|
|
138
|
+
*/
|
|
139
|
+
async getSession(channelId) {
|
|
140
|
+
const state = await this.loadState();
|
|
141
|
+
const session = state.channels[channelId];
|
|
142
|
+
if (!session) {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
// Check if session is expired
|
|
146
|
+
if (this.isSessionExpired(session)) {
|
|
147
|
+
this.logger.info("Session expired", {
|
|
148
|
+
channelId,
|
|
149
|
+
sessionId: session.sessionId,
|
|
150
|
+
lastMessageAt: session.lastMessageAt,
|
|
151
|
+
expiryHours: this.sessionExpiryHours,
|
|
152
|
+
});
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
return session;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Clear a specific session
|
|
159
|
+
*/
|
|
160
|
+
async clearSession(channelId) {
|
|
161
|
+
const state = await this.loadState();
|
|
162
|
+
if (!state.channels[channelId]) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
const sessionId = state.channels[channelId].sessionId;
|
|
166
|
+
delete state.channels[channelId];
|
|
167
|
+
await this.saveState(state);
|
|
168
|
+
this.logger.info("Cleared session", { channelId, sessionId });
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Clean up all expired sessions
|
|
173
|
+
*
|
|
174
|
+
* Should be called on connector startup and periodically.
|
|
175
|
+
*/
|
|
176
|
+
async cleanupExpiredSessions() {
|
|
177
|
+
const state = await this.loadState();
|
|
178
|
+
const channelIds = Object.keys(state.channels);
|
|
179
|
+
let cleanedUp = 0;
|
|
180
|
+
for (const channelId of channelIds) {
|
|
181
|
+
const session = state.channels[channelId];
|
|
182
|
+
if (this.isSessionExpired(session)) {
|
|
183
|
+
this.logger.debug("Cleaning up expired session", {
|
|
184
|
+
channelId,
|
|
185
|
+
sessionId: session.sessionId,
|
|
186
|
+
lastMessageAt: session.lastMessageAt,
|
|
187
|
+
});
|
|
188
|
+
delete state.channels[channelId];
|
|
189
|
+
cleanedUp++;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (cleanedUp > 0) {
|
|
193
|
+
await this.saveState(state);
|
|
194
|
+
this.logger.info("Cleaned up expired sessions", { count: cleanedUp });
|
|
195
|
+
}
|
|
196
|
+
return cleanedUp;
|
|
197
|
+
}
|
|
198
|
+
// ===========================================================================
|
|
199
|
+
// Private Helpers
|
|
200
|
+
// ===========================================================================
|
|
201
|
+
/**
|
|
202
|
+
* Generate a unique session ID
|
|
203
|
+
*/
|
|
204
|
+
generateSessionId() {
|
|
205
|
+
return `discord-${this.agentName}-${randomUUID()}`;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Check if a session is expired
|
|
209
|
+
*/
|
|
210
|
+
isSessionExpired(session) {
|
|
211
|
+
const lastMessageAt = new Date(session.lastMessageAt);
|
|
212
|
+
const now = new Date();
|
|
213
|
+
const expiryMs = this.sessionExpiryHours * 60 * 60 * 1000;
|
|
214
|
+
return now.getTime() - lastMessageAt.getTime() > expiryMs;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Load session state from disk
|
|
218
|
+
*
|
|
219
|
+
* Returns cached state if available, otherwise loads from file.
|
|
220
|
+
* Creates initial state if file doesn't exist.
|
|
221
|
+
*/
|
|
222
|
+
async loadState() {
|
|
223
|
+
if (this.state) {
|
|
224
|
+
return this.state;
|
|
225
|
+
}
|
|
226
|
+
let content;
|
|
227
|
+
try {
|
|
228
|
+
content = await readFile(this.stateFilePath, "utf-8");
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
const code = error.code;
|
|
232
|
+
// File doesn't exist - create initial state
|
|
233
|
+
if (code === "ENOENT") {
|
|
234
|
+
this.state = createInitialSessionState(this.agentName);
|
|
235
|
+
return this.state;
|
|
236
|
+
}
|
|
237
|
+
// Other read errors are fatal
|
|
238
|
+
throw new SessionStateReadError(this.agentName, this.stateFilePath, { cause: error });
|
|
239
|
+
}
|
|
240
|
+
// Handle empty file
|
|
241
|
+
if (content.trim() === "") {
|
|
242
|
+
this.state = createInitialSessionState(this.agentName);
|
|
243
|
+
return this.state;
|
|
244
|
+
}
|
|
245
|
+
// Parse YAML - treat parse errors as corrupted state
|
|
246
|
+
let parsed;
|
|
247
|
+
try {
|
|
248
|
+
parsed = parseYaml(content);
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
this.logger.warn("Corrupted session state file, creating fresh state", {
|
|
252
|
+
error: error.message,
|
|
253
|
+
});
|
|
254
|
+
this.state = createInitialSessionState(this.agentName);
|
|
255
|
+
return this.state;
|
|
256
|
+
}
|
|
257
|
+
// Validate against schema - treat validation errors as corrupted state
|
|
258
|
+
const validated = DiscordSessionStateSchema.safeParse(parsed);
|
|
259
|
+
if (!validated.success) {
|
|
260
|
+
this.logger.warn("Corrupted session state file, creating fresh state", {
|
|
261
|
+
error: validated.error.message,
|
|
262
|
+
});
|
|
263
|
+
this.state = createInitialSessionState(this.agentName);
|
|
264
|
+
return this.state;
|
|
265
|
+
}
|
|
266
|
+
this.state = validated.data;
|
|
267
|
+
return this.state;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Save session state to disk atomically
|
|
271
|
+
*/
|
|
272
|
+
async saveState(state) {
|
|
273
|
+
// Ensure directory exists
|
|
274
|
+
await this.ensureDirectoryExists();
|
|
275
|
+
// Update in-memory cache
|
|
276
|
+
this.state = state;
|
|
277
|
+
// Write atomically
|
|
278
|
+
const yamlContent = stringifyYaml(state, { indent: 2, lineWidth: 120 });
|
|
279
|
+
const tempPath = this.generateTempPath(this.stateFilePath);
|
|
280
|
+
try {
|
|
281
|
+
await writeFile(tempPath, yamlContent, "utf-8");
|
|
282
|
+
await this.renameWithRetry(tempPath, this.stateFilePath);
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
// Clean up temp file on failure
|
|
286
|
+
try {
|
|
287
|
+
await unlink(tempPath);
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
// Ignore cleanup errors
|
|
291
|
+
}
|
|
292
|
+
throw new SessionStateWriteError(this.agentName, this.stateFilePath, { cause: error });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Ensure the discord-sessions directory exists
|
|
297
|
+
*/
|
|
298
|
+
async ensureDirectoryExists() {
|
|
299
|
+
const dir = dirname(this.stateFilePath);
|
|
300
|
+
try {
|
|
301
|
+
await mkdir(dir, { recursive: true });
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
const code = error.code;
|
|
305
|
+
// EEXIST is fine - directory already exists
|
|
306
|
+
if (code !== "EEXIST") {
|
|
307
|
+
throw new SessionDirectoryCreateError(this.agentName, dir, { cause: error });
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Generate a temp file path for atomic writes
|
|
313
|
+
*/
|
|
314
|
+
generateTempPath(targetPath) {
|
|
315
|
+
const dir = dirname(targetPath);
|
|
316
|
+
const random = randomBytes(8).toString("hex");
|
|
317
|
+
const filename = `${this.agentName}.yaml`;
|
|
318
|
+
return join(dir, `.${filename}.tmp.${random}`);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Rename with retry logic for Windows compatibility
|
|
322
|
+
*/
|
|
323
|
+
async renameWithRetry(oldPath, newPath, maxRetries = 3, baseDelayMs = 50) {
|
|
324
|
+
let lastError;
|
|
325
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
326
|
+
try {
|
|
327
|
+
await rename(oldPath, newPath);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
lastError = error;
|
|
332
|
+
const code = error.code;
|
|
333
|
+
// Only retry on Windows-specific errors
|
|
334
|
+
if (code !== "EACCES" && code !== "EPERM") {
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
// Don't delay on the last attempt
|
|
338
|
+
if (attempt < maxRetries) {
|
|
339
|
+
const delay = baseDelayMs * Math.pow(2, attempt);
|
|
340
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
throw lastError;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
//# 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,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for Discord session management
|
|
3
|
+
*
|
|
4
|
+
* Provides interfaces for per-channel session state tracking,
|
|
5
|
+
* enabling conversation context preservation across Discord channels.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
/**
|
|
9
|
+
* Schema for individual channel/DM session mapping
|
|
10
|
+
*/
|
|
11
|
+
export declare const ChannelSessionSchema: z.ZodObject<{
|
|
12
|
+
/** Claude session ID for resuming conversations */
|
|
13
|
+
sessionId: z.ZodString;
|
|
14
|
+
/** ISO timestamp when last message was sent/received */
|
|
15
|
+
lastMessageAt: z.ZodString;
|
|
16
|
+
}, "strip", z.ZodTypeAny, {
|
|
17
|
+
sessionId: string;
|
|
18
|
+
lastMessageAt: string;
|
|
19
|
+
}, {
|
|
20
|
+
sessionId: string;
|
|
21
|
+
lastMessageAt: string;
|
|
22
|
+
}>;
|
|
23
|
+
/**
|
|
24
|
+
* Schema for the entire agent's Discord session state file
|
|
25
|
+
*
|
|
26
|
+
* Stored at .herdctl/discord-sessions/<agent-name>.yaml
|
|
27
|
+
*/
|
|
28
|
+
export declare const DiscordSessionStateSchema: z.ZodObject<{
|
|
29
|
+
/** Version for future schema migrations */
|
|
30
|
+
version: z.ZodLiteral<1>;
|
|
31
|
+
/** Agent name this session state belongs to */
|
|
32
|
+
agentName: z.ZodString;
|
|
33
|
+
/** Map of channel/DM ID to session info */
|
|
34
|
+
channels: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
35
|
+
/** Claude session ID for resuming conversations */
|
|
36
|
+
sessionId: z.ZodString;
|
|
37
|
+
/** ISO timestamp when last message was sent/received */
|
|
38
|
+
lastMessageAt: z.ZodString;
|
|
39
|
+
}, "strip", z.ZodTypeAny, {
|
|
40
|
+
sessionId: string;
|
|
41
|
+
lastMessageAt: string;
|
|
42
|
+
}, {
|
|
43
|
+
sessionId: string;
|
|
44
|
+
lastMessageAt: string;
|
|
45
|
+
}>>;
|
|
46
|
+
}, "strip", z.ZodTypeAny, {
|
|
47
|
+
channels: Record<string, {
|
|
48
|
+
sessionId: string;
|
|
49
|
+
lastMessageAt: string;
|
|
50
|
+
}>;
|
|
51
|
+
version: 1;
|
|
52
|
+
agentName: string;
|
|
53
|
+
}, {
|
|
54
|
+
channels: Record<string, {
|
|
55
|
+
sessionId: string;
|
|
56
|
+
lastMessageAt: string;
|
|
57
|
+
}>;
|
|
58
|
+
version: 1;
|
|
59
|
+
agentName: string;
|
|
60
|
+
}>;
|
|
61
|
+
export type ChannelSession = z.infer<typeof ChannelSessionSchema>;
|
|
62
|
+
export type DiscordSessionState = z.infer<typeof DiscordSessionStateSchema>;
|
|
63
|
+
/**
|
|
64
|
+
* Logger interface for session manager operations
|
|
65
|
+
*/
|
|
66
|
+
export interface SessionManagerLogger {
|
|
67
|
+
debug(message: string, data?: Record<string, unknown>): void;
|
|
68
|
+
info(message: string, data?: Record<string, unknown>): void;
|
|
69
|
+
warn(message: string, data?: Record<string, unknown>): void;
|
|
70
|
+
error(message: string, data?: Record<string, unknown>): void;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Options for configuring the SessionManager
|
|
74
|
+
*/
|
|
75
|
+
export interface SessionManagerOptions {
|
|
76
|
+
/**
|
|
77
|
+
* Name of the agent this session manager is for
|
|
78
|
+
*/
|
|
79
|
+
agentName: string;
|
|
80
|
+
/**
|
|
81
|
+
* Root path for state storage (e.g., .herdctl)
|
|
82
|
+
* Sessions will be stored at <stateDir>/discord-sessions/<agent-name>.yaml
|
|
83
|
+
*/
|
|
84
|
+
stateDir: string;
|
|
85
|
+
/**
|
|
86
|
+
* Session expiry timeout in hours
|
|
87
|
+
* Sessions inactive for longer than this will be considered expired
|
|
88
|
+
*
|
|
89
|
+
* @default 24
|
|
90
|
+
*/
|
|
91
|
+
sessionExpiryHours?: number;
|
|
92
|
+
/**
|
|
93
|
+
* Logger for session manager operations
|
|
94
|
+
*
|
|
95
|
+
* @default console-based logger
|
|
96
|
+
*/
|
|
97
|
+
logger?: SessionManagerLogger;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Result of getting or creating a session
|
|
101
|
+
*/
|
|
102
|
+
export interface SessionResult {
|
|
103
|
+
/** Claude session ID */
|
|
104
|
+
sessionId: string;
|
|
105
|
+
/** Whether this is a newly created session */
|
|
106
|
+
isNew: boolean;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Interface that all session managers must implement
|
|
110
|
+
*/
|
|
111
|
+
export interface ISessionManager {
|
|
112
|
+
/**
|
|
113
|
+
* Get or create a session for a channel/DM
|
|
114
|
+
*
|
|
115
|
+
* If an active (non-expired) session exists, returns it.
|
|
116
|
+
* Otherwise, creates a new session.
|
|
117
|
+
*
|
|
118
|
+
* @param channelId - Discord channel or DM ID
|
|
119
|
+
* @returns Session info with sessionId and isNew flag
|
|
120
|
+
*/
|
|
121
|
+
getOrCreateSession(channelId: string): Promise<SessionResult>;
|
|
122
|
+
/**
|
|
123
|
+
* Update the last message timestamp for a session
|
|
124
|
+
*
|
|
125
|
+
* Called after each message to keep the session active.
|
|
126
|
+
*
|
|
127
|
+
* @param channelId - Discord channel or DM ID
|
|
128
|
+
*/
|
|
129
|
+
touchSession(channelId: string): Promise<void>;
|
|
130
|
+
/**
|
|
131
|
+
* Get an existing session without creating one
|
|
132
|
+
*
|
|
133
|
+
* Returns null if no session exists or if the session is expired.
|
|
134
|
+
*
|
|
135
|
+
* @param channelId - Discord channel or DM ID
|
|
136
|
+
* @returns The session if it exists and is not expired, null otherwise
|
|
137
|
+
*/
|
|
138
|
+
getSession(channelId: string): Promise<ChannelSession | null>;
|
|
139
|
+
/**
|
|
140
|
+
* Clear a specific session
|
|
141
|
+
*
|
|
142
|
+
* @param channelId - Discord channel or DM ID
|
|
143
|
+
* @returns true if the session was cleared, false if it didn't exist
|
|
144
|
+
*/
|
|
145
|
+
clearSession(channelId: string): Promise<boolean>;
|
|
146
|
+
/**
|
|
147
|
+
* Clean up all expired sessions
|
|
148
|
+
*
|
|
149
|
+
* Should be called on connector startup and periodically.
|
|
150
|
+
*
|
|
151
|
+
* @returns Number of sessions that were cleaned up
|
|
152
|
+
*/
|
|
153
|
+
cleanupExpiredSessions(): Promise<number>;
|
|
154
|
+
/**
|
|
155
|
+
* Name of the agent this session manager is for
|
|
156
|
+
*/
|
|
157
|
+
readonly agentName: string;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Create initial session state for a new agent
|
|
161
|
+
*/
|
|
162
|
+
export declare function createInitialSessionState(agentName: string): DiscordSessionState;
|
|
163
|
+
/**
|
|
164
|
+
* Create a new channel session
|
|
165
|
+
*/
|
|
166
|
+
export declare function createChannelSession(sessionId: string): ChannelSession;
|
|
167
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/session-manager/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB;;GAEG;AACH,eAAO,MAAM,oBAAoB;IAC/B,mDAAmD;;IAGnD,wDAAwD;;;;;;;;EAIxD,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB;IACpC,2CAA2C;;IAG3C,+CAA+C;;IAG/C,2CAA2C;;QArB3C,mDAAmD;;QAGnD,wDAAwD;;;;;;;;;;;;;;;;;;;;;;;EAoBxD,CAAC;AAMH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAClE,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAM5E;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC7D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5D,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC9D;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;;;OAIG;IACH,MAAM,CAAC,EAAE,oBAAoB,CAAC;CAC/B;AAMD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,wBAAwB;IACxB,SAAS,EAAE,MAAM,CAAC;IAElB,8CAA8C;IAC9C,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;;;;OAQG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAE9D;;;;;;OAMG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/C;;;;;;;OAOG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAE9D;;;;;OAKG;IACH,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAElD;;;;;;OAMG;IACH,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAE1C;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAMD;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,MAAM,GAChB,mBAAmB,CAMrB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,CAKtE"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for Discord session management
|
|
3
|
+
*
|
|
4
|
+
* Provides interfaces for per-channel session state tracking,
|
|
5
|
+
* enabling conversation context preservation across Discord channels.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Session Schema
|
|
10
|
+
// =============================================================================
|
|
11
|
+
/**
|
|
12
|
+
* Schema for individual channel/DM session mapping
|
|
13
|
+
*/
|
|
14
|
+
export const ChannelSessionSchema = z.object({
|
|
15
|
+
/** Claude session ID for resuming conversations */
|
|
16
|
+
sessionId: z.string().min(1, "Session ID cannot be empty"),
|
|
17
|
+
/** ISO timestamp when last message was sent/received */
|
|
18
|
+
lastMessageAt: z.string().datetime({
|
|
19
|
+
message: "lastMessageAt must be a valid ISO datetime string",
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Schema for the entire agent's Discord session state file
|
|
24
|
+
*
|
|
25
|
+
* Stored at .herdctl/discord-sessions/<agent-name>.yaml
|
|
26
|
+
*/
|
|
27
|
+
export const DiscordSessionStateSchema = z.object({
|
|
28
|
+
/** Version for future schema migrations */
|
|
29
|
+
version: z.literal(1),
|
|
30
|
+
/** Agent name this session state belongs to */
|
|
31
|
+
agentName: z.string().min(1, "Agent name cannot be empty"),
|
|
32
|
+
/** Map of channel/DM ID to session info */
|
|
33
|
+
channels: z.record(z.string(), ChannelSessionSchema),
|
|
34
|
+
});
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// Factory Functions
|
|
37
|
+
// =============================================================================
|
|
38
|
+
/**
|
|
39
|
+
* Create initial session state for a new agent
|
|
40
|
+
*/
|
|
41
|
+
export function createInitialSessionState(agentName) {
|
|
42
|
+
return {
|
|
43
|
+
version: 1,
|
|
44
|
+
agentName,
|
|
45
|
+
channels: {},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create a new channel session
|
|
50
|
+
*/
|
|
51
|
+
export function createChannelSession(sessionId) {
|
|
52
|
+
return {
|
|
53
|
+
sessionId,
|
|
54
|
+
lastMessageAt: new Date().toISOString(),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/session-manager/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,gFAAgF;AAChF,iBAAiB;AACjB,gFAAgF;AAEhF;;GAEG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,mDAAmD;IACnD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,4BAA4B,CAAC;IAE1D,wDAAwD;IACxD,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC;QACjC,OAAO,EAAE,mDAAmD;KAC7D,CAAC;CACH,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,2CAA2C;IAC3C,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAErB,+CAA+C;IAC/C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,4BAA4B,CAAC;IAE1D,2CAA2C;IAC3C,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,oBAAoB,CAAC;CACrD,CAAC,CAAC;AA8HH,gFAAgF;AAChF,oBAAoB;AACpB,gFAAgF;AAEhF;;GAEG;AACH,MAAM,UAAU,yBAAyB,CACvC,SAAiB;IAEjB,OAAO;QACL,OAAO,EAAE,CAAC;QACV,SAAS;QACT,QAAQ,EAAE,EAAE;KACb,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,OAAO;QACL,SAAS;QACT,aAAa,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACxC,CAAC;AACJ,CAAC"}
|