@vibecheckai/cli 3.2.6 → 3.3.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/bin/registry.js +192 -5
- package/bin/runners/lib/agent-firewall/change-packet/builder.js +280 -6
- package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
- package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
- package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
- package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
- package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
- package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
- package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
- package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
- package/bin/runners/lib/agent-firewall/logger.js +141 -0
- package/bin/runners/lib/agent-firewall/policy/loader.js +312 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +113 -1
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +133 -6
- package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
- package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
- package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
- package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
- package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
- package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
- package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
- package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
- package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
- package/bin/runners/lib/agent-firewall/risk/thresholds.js +321 -0
- package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
- package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
- package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
- package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
- package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
- package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
- package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
- package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
- package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
- package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
- package/bin/runners/lib/analyzers.js +81 -18
- package/bin/runners/lib/authority-badge.js +425 -0
- package/bin/runners/lib/cli-output.js +7 -1
- package/bin/runners/lib/error-handler.js +16 -9
- package/bin/runners/lib/exit-codes.js +275 -0
- package/bin/runners/lib/global-flags.js +37 -0
- package/bin/runners/lib/help-formatter.js +413 -0
- package/bin/runners/lib/logger.js +38 -0
- package/bin/runners/lib/unified-cli-output.js +604 -0
- package/bin/runners/lib/upsell.js +148 -0
- package/bin/runners/runApprove.js +1200 -0
- package/bin/runners/runAuth.js +324 -95
- package/bin/runners/runCheckpoint.js +39 -21
- package/bin/runners/runClassify.js +859 -0
- package/bin/runners/runContext.js +136 -24
- package/bin/runners/runDoctor.js +108 -68
- package/bin/runners/runFix.js +6 -5
- package/bin/runners/runGuard.js +212 -118
- package/bin/runners/runInit.js +3 -2
- package/bin/runners/runMcp.js +130 -52
- package/bin/runners/runPolish.js +43 -20
- package/bin/runners/runProve.js +1 -2
- package/bin/runners/runReport.js +3 -2
- package/bin/runners/runScan.js +63 -44
- package/bin/runners/runShip.js +3 -4
- package/bin/runners/runValidate.js +19 -2
- package/bin/runners/runWatch.js +104 -53
- package/bin/vibecheck.js +106 -19
- package/mcp-server/HARDENING_SUMMARY.md +299 -0
- package/mcp-server/agent-firewall-interceptor.js +367 -31
- package/mcp-server/authority-tools.js +569 -0
- package/mcp-server/conductor/conflict-resolver.js +588 -0
- package/mcp-server/conductor/execution-planner.js +544 -0
- package/mcp-server/conductor/index.js +377 -0
- package/mcp-server/conductor/lock-manager.js +615 -0
- package/mcp-server/conductor/request-queue.js +550 -0
- package/mcp-server/conductor/session-manager.js +500 -0
- package/mcp-server/conductor/tools.js +510 -0
- package/mcp-server/index.js +1149 -243
- package/mcp-server/lib/{api-client.js → api-client.cjs} +40 -4
- package/mcp-server/lib/logger.cjs +30 -0
- package/mcp-server/logger.js +173 -0
- package/mcp-server/package.json +2 -2
- package/mcp-server/premium-tools.js +2 -2
- package/mcp-server/tier-auth.js +245 -35
- package/mcp-server/truth-firewall-tools.js +145 -15
- package/mcp-server/vibecheck-tools.js +2 -2
- package/package.json +2 -3
- package/mcp-server/index.old.js +0 -4137
- package/mcp-server/package-lock.json +0 -165
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conductor Session Manager
|
|
3
|
+
*
|
|
4
|
+
* Tracks active AI agent sessions per project.
|
|
5
|
+
* Enables multi-agent coordination by knowing which agents are active.
|
|
6
|
+
*
|
|
7
|
+
* Codename: Conductor
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
"use strict";
|
|
11
|
+
|
|
12
|
+
import fs from "fs";
|
|
13
|
+
import path from "path";
|
|
14
|
+
import crypto from "crypto";
|
|
15
|
+
import { conductorLogger as log, getErrorMessage } from "../logger.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {Object} AgentSession
|
|
19
|
+
* @property {string} agentId - Agent identifier (cursor, copilot, etc.)
|
|
20
|
+
* @property {string} sessionId - Unique session ID
|
|
21
|
+
* @property {string} tier - User tier (FREE, STARTER, PRO, ENTERPRISE)
|
|
22
|
+
* @property {string} trust - Trust level (low, medium, high)
|
|
23
|
+
* @property {Date} startTime - Session start timestamp
|
|
24
|
+
* @property {Date} lastActivity - Last activity timestamp
|
|
25
|
+
* @property {string[]} workingFiles - Files this agent is working on
|
|
26
|
+
* @property {string[]} pendingProposals - Pending proposal IDs
|
|
27
|
+
* @property {string} projectRoot - Project root directory
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Default session timeout (30 minutes)
|
|
32
|
+
*/
|
|
33
|
+
const DEFAULT_SESSION_TIMEOUT_MS = 30 * 60 * 1000;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Trust level mapping based on tier
|
|
37
|
+
*/
|
|
38
|
+
const TIER_TRUST_MAP = {
|
|
39
|
+
FREE: "low",
|
|
40
|
+
STARTER: "medium",
|
|
41
|
+
PRO: "high",
|
|
42
|
+
ENTERPRISE: "high",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Session Manager class
|
|
47
|
+
*/
|
|
48
|
+
class SessionManager {
|
|
49
|
+
constructor(options = {}) {
|
|
50
|
+
this.sessions = new Map(); // sessionId -> AgentSession
|
|
51
|
+
this.agentSessions = new Map(); // agentId -> Set<sessionId>
|
|
52
|
+
this.projectSessions = new Map(); // projectRoot -> Set<sessionId>
|
|
53
|
+
this.sessionTimeout = options.sessionTimeout || DEFAULT_SESSION_TIMEOUT_MS;
|
|
54
|
+
this.persistPath = options.persistPath || null;
|
|
55
|
+
|
|
56
|
+
// Start cleanup interval
|
|
57
|
+
this.cleanupInterval = setInterval(() => {
|
|
58
|
+
this.cleanupExpiredSessions();
|
|
59
|
+
}, 60000); // Check every minute
|
|
60
|
+
|
|
61
|
+
// Load persisted state if available
|
|
62
|
+
if (this.persistPath) {
|
|
63
|
+
this.loadState();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Generate a unique session ID
|
|
69
|
+
* @returns {string} Session ID
|
|
70
|
+
*/
|
|
71
|
+
generateSessionId() {
|
|
72
|
+
return `ses_${crypto.randomBytes(12).toString("hex")}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Register a new agent session
|
|
77
|
+
* @param {Object} params - Session parameters
|
|
78
|
+
* @returns {AgentSession} Created session
|
|
79
|
+
*/
|
|
80
|
+
registerSession({
|
|
81
|
+
agentId,
|
|
82
|
+
tier = "FREE",
|
|
83
|
+
projectRoot,
|
|
84
|
+
workingFiles = [],
|
|
85
|
+
}) {
|
|
86
|
+
const sessionId = this.generateSessionId();
|
|
87
|
+
const now = new Date();
|
|
88
|
+
|
|
89
|
+
const session = {
|
|
90
|
+
agentId,
|
|
91
|
+
sessionId,
|
|
92
|
+
tier,
|
|
93
|
+
trust: TIER_TRUST_MAP[tier] || "low",
|
|
94
|
+
startTime: now,
|
|
95
|
+
lastActivity: now,
|
|
96
|
+
workingFiles,
|
|
97
|
+
pendingProposals: [],
|
|
98
|
+
projectRoot: path.resolve(projectRoot),
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// Store session
|
|
102
|
+
this.sessions.set(sessionId, session);
|
|
103
|
+
|
|
104
|
+
// Index by agent
|
|
105
|
+
if (!this.agentSessions.has(agentId)) {
|
|
106
|
+
this.agentSessions.set(agentId, new Set());
|
|
107
|
+
}
|
|
108
|
+
this.agentSessions.get(agentId).add(sessionId);
|
|
109
|
+
|
|
110
|
+
// Index by project
|
|
111
|
+
const normalizedRoot = path.resolve(projectRoot);
|
|
112
|
+
if (!this.projectSessions.has(normalizedRoot)) {
|
|
113
|
+
this.projectSessions.set(normalizedRoot, new Set());
|
|
114
|
+
}
|
|
115
|
+
this.projectSessions.get(normalizedRoot).add(sessionId);
|
|
116
|
+
|
|
117
|
+
// Persist state
|
|
118
|
+
this.saveState();
|
|
119
|
+
|
|
120
|
+
return session;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get a session by ID
|
|
125
|
+
* @param {string} sessionId - Session ID
|
|
126
|
+
* @returns {AgentSession|null} Session or null
|
|
127
|
+
*/
|
|
128
|
+
getSession(sessionId) {
|
|
129
|
+
const session = this.sessions.get(sessionId);
|
|
130
|
+
if (!session) return null;
|
|
131
|
+
|
|
132
|
+
// Check if expired
|
|
133
|
+
if (this.isSessionExpired(session)) {
|
|
134
|
+
this.terminateSession(sessionId);
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return session;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get all sessions for an agent
|
|
143
|
+
* @param {string} agentId - Agent ID
|
|
144
|
+
* @returns {AgentSession[]} Sessions
|
|
145
|
+
*/
|
|
146
|
+
getAgentSessions(agentId) {
|
|
147
|
+
const sessionIds = this.agentSessions.get(agentId);
|
|
148
|
+
if (!sessionIds) return [];
|
|
149
|
+
|
|
150
|
+
const sessions = [];
|
|
151
|
+
for (const sessionId of sessionIds) {
|
|
152
|
+
const session = this.getSession(sessionId);
|
|
153
|
+
if (session) {
|
|
154
|
+
sessions.push(session);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return sessions;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get all sessions for a project
|
|
162
|
+
* @param {string} projectRoot - Project root
|
|
163
|
+
* @returns {AgentSession[]} Sessions
|
|
164
|
+
*/
|
|
165
|
+
getProjectSessions(projectRoot) {
|
|
166
|
+
const normalizedRoot = path.resolve(projectRoot);
|
|
167
|
+
const sessionIds = this.projectSessions.get(normalizedRoot);
|
|
168
|
+
if (!sessionIds) return [];
|
|
169
|
+
|
|
170
|
+
const sessions = [];
|
|
171
|
+
for (const sessionId of sessionIds) {
|
|
172
|
+
const session = this.getSession(sessionId);
|
|
173
|
+
if (session) {
|
|
174
|
+
sessions.push(session);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return sessions;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get all active agents for a project
|
|
182
|
+
* @param {string} projectRoot - Project root
|
|
183
|
+
* @returns {Object[]} Active agents with their sessions
|
|
184
|
+
*/
|
|
185
|
+
getActiveAgents(projectRoot) {
|
|
186
|
+
const sessions = this.getProjectSessions(projectRoot);
|
|
187
|
+
const agentMap = new Map();
|
|
188
|
+
|
|
189
|
+
for (const session of sessions) {
|
|
190
|
+
if (!agentMap.has(session.agentId)) {
|
|
191
|
+
agentMap.set(session.agentId, {
|
|
192
|
+
agentId: session.agentId,
|
|
193
|
+
tier: session.tier,
|
|
194
|
+
trust: session.trust,
|
|
195
|
+
sessions: [],
|
|
196
|
+
workingFiles: new Set(),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const agent = agentMap.get(session.agentId);
|
|
201
|
+
agent.sessions.push(session);
|
|
202
|
+
session.workingFiles.forEach(f => agent.workingFiles.add(f));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Convert sets to arrays
|
|
206
|
+
return Array.from(agentMap.values()).map(agent => ({
|
|
207
|
+
...agent,
|
|
208
|
+
workingFiles: Array.from(agent.workingFiles),
|
|
209
|
+
sessionCount: agent.sessions.length,
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Update session activity
|
|
215
|
+
* @param {string} sessionId - Session ID
|
|
216
|
+
* @param {Object} updates - Updates to apply
|
|
217
|
+
* @returns {AgentSession|null} Updated session
|
|
218
|
+
*/
|
|
219
|
+
updateSession(sessionId, updates = {}) {
|
|
220
|
+
const session = this.sessions.get(sessionId);
|
|
221
|
+
if (!session) return null;
|
|
222
|
+
|
|
223
|
+
// Update last activity
|
|
224
|
+
session.lastActivity = new Date();
|
|
225
|
+
|
|
226
|
+
// Apply updates
|
|
227
|
+
if (updates.workingFiles) {
|
|
228
|
+
session.workingFiles = updates.workingFiles;
|
|
229
|
+
}
|
|
230
|
+
if (updates.pendingProposals) {
|
|
231
|
+
session.pendingProposals = updates.pendingProposals;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Persist state
|
|
235
|
+
this.saveState();
|
|
236
|
+
|
|
237
|
+
return session;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Add working file to session
|
|
242
|
+
* @param {string} sessionId - Session ID
|
|
243
|
+
* @param {string} filePath - File path
|
|
244
|
+
*/
|
|
245
|
+
addWorkingFile(sessionId, filePath) {
|
|
246
|
+
const session = this.sessions.get(sessionId);
|
|
247
|
+
if (!session) return;
|
|
248
|
+
|
|
249
|
+
if (!session.workingFiles.includes(filePath)) {
|
|
250
|
+
session.workingFiles.push(filePath);
|
|
251
|
+
session.lastActivity = new Date();
|
|
252
|
+
this.saveState();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Remove working file from session
|
|
258
|
+
* @param {string} sessionId - Session ID
|
|
259
|
+
* @param {string} filePath - File path
|
|
260
|
+
*/
|
|
261
|
+
removeWorkingFile(sessionId, filePath) {
|
|
262
|
+
const session = this.sessions.get(sessionId);
|
|
263
|
+
if (!session) return;
|
|
264
|
+
|
|
265
|
+
const index = session.workingFiles.indexOf(filePath);
|
|
266
|
+
if (index !== -1) {
|
|
267
|
+
session.workingFiles.splice(index, 1);
|
|
268
|
+
session.lastActivity = new Date();
|
|
269
|
+
this.saveState();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Add pending proposal to session
|
|
275
|
+
* @param {string} sessionId - Session ID
|
|
276
|
+
* @param {string} proposalId - Proposal ID
|
|
277
|
+
*/
|
|
278
|
+
addPendingProposal(sessionId, proposalId) {
|
|
279
|
+
const session = this.sessions.get(sessionId);
|
|
280
|
+
if (!session) return;
|
|
281
|
+
|
|
282
|
+
if (!session.pendingProposals.includes(proposalId)) {
|
|
283
|
+
session.pendingProposals.push(proposalId);
|
|
284
|
+
session.lastActivity = new Date();
|
|
285
|
+
this.saveState();
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Remove pending proposal from session
|
|
291
|
+
* @param {string} sessionId - Session ID
|
|
292
|
+
* @param {string} proposalId - Proposal ID
|
|
293
|
+
*/
|
|
294
|
+
removePendingProposal(sessionId, proposalId) {
|
|
295
|
+
const session = this.sessions.get(sessionId);
|
|
296
|
+
if (!session) return;
|
|
297
|
+
|
|
298
|
+
const index = session.pendingProposals.indexOf(proposalId);
|
|
299
|
+
if (index !== -1) {
|
|
300
|
+
session.pendingProposals.splice(index, 1);
|
|
301
|
+
session.lastActivity = new Date();
|
|
302
|
+
this.saveState();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Check if session is expired
|
|
308
|
+
* @param {AgentSession} session - Session to check
|
|
309
|
+
* @returns {boolean} Is expired
|
|
310
|
+
*/
|
|
311
|
+
isSessionExpired(session) {
|
|
312
|
+
const elapsed = Date.now() - new Date(session.lastActivity).getTime();
|
|
313
|
+
return elapsed > this.sessionTimeout;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Terminate a session
|
|
318
|
+
* @param {string} sessionId - Session ID to terminate
|
|
319
|
+
* @returns {boolean} Success
|
|
320
|
+
*/
|
|
321
|
+
terminateSession(sessionId) {
|
|
322
|
+
const session = this.sessions.get(sessionId);
|
|
323
|
+
if (!session) return false;
|
|
324
|
+
|
|
325
|
+
// Remove from sessions
|
|
326
|
+
this.sessions.delete(sessionId);
|
|
327
|
+
|
|
328
|
+
// Remove from agent index
|
|
329
|
+
const agentSessions = this.agentSessions.get(session.agentId);
|
|
330
|
+
if (agentSessions) {
|
|
331
|
+
agentSessions.delete(sessionId);
|
|
332
|
+
if (agentSessions.size === 0) {
|
|
333
|
+
this.agentSessions.delete(session.agentId);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Remove from project index
|
|
338
|
+
const projectSessions = this.projectSessions.get(session.projectRoot);
|
|
339
|
+
if (projectSessions) {
|
|
340
|
+
projectSessions.delete(sessionId);
|
|
341
|
+
if (projectSessions.size === 0) {
|
|
342
|
+
this.projectSessions.delete(session.projectRoot);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Persist state
|
|
347
|
+
this.saveState();
|
|
348
|
+
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Cleanup expired sessions
|
|
354
|
+
*/
|
|
355
|
+
cleanupExpiredSessions() {
|
|
356
|
+
const expiredIds = [];
|
|
357
|
+
|
|
358
|
+
for (const [sessionId, session] of this.sessions) {
|
|
359
|
+
if (this.isSessionExpired(session)) {
|
|
360
|
+
expiredIds.push(sessionId);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
for (const sessionId of expiredIds) {
|
|
365
|
+
this.terminateSession(sessionId);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (expiredIds.length > 0) {
|
|
369
|
+
log.info(`Cleaned up ${expiredIds.length} expired sessions`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Get session statistics
|
|
375
|
+
* @returns {Object} Statistics
|
|
376
|
+
*/
|
|
377
|
+
getStatistics() {
|
|
378
|
+
const stats = {
|
|
379
|
+
totalSessions: this.sessions.size,
|
|
380
|
+
activeAgents: this.agentSessions.size,
|
|
381
|
+
activeProjects: this.projectSessions.size,
|
|
382
|
+
byTier: { FREE: 0, STARTER: 0, PRO: 0, ENTERPRISE: 0 },
|
|
383
|
+
byTrust: { low: 0, medium: 0, high: 0 },
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
for (const session of this.sessions.values()) {
|
|
387
|
+
stats.byTier[session.tier] = (stats.byTier[session.tier] || 0) + 1;
|
|
388
|
+
stats.byTrust[session.trust] = (stats.byTrust[session.trust] || 0) + 1;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return stats;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Save state to disk
|
|
396
|
+
*/
|
|
397
|
+
saveState() {
|
|
398
|
+
if (!this.persistPath) return;
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
const dir = path.dirname(this.persistPath);
|
|
402
|
+
if (!fs.existsSync(dir)) {
|
|
403
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const state = {
|
|
407
|
+
sessions: Array.from(this.sessions.entries()),
|
|
408
|
+
timestamp: new Date().toISOString(),
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
fs.writeFileSync(this.persistPath, JSON.stringify(state, null, 2));
|
|
412
|
+
} catch (error) {
|
|
413
|
+
log.warn(`Failed to save state: ${getErrorMessage(error)}`);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Load state from disk
|
|
419
|
+
*/
|
|
420
|
+
loadState() {
|
|
421
|
+
if (!this.persistPath || !fs.existsSync(this.persistPath)) return;
|
|
422
|
+
|
|
423
|
+
try {
|
|
424
|
+
const content = fs.readFileSync(this.persistPath, "utf-8");
|
|
425
|
+
const state = JSON.parse(content);
|
|
426
|
+
|
|
427
|
+
// Restore sessions
|
|
428
|
+
for (const [sessionId, session] of state.sessions || []) {
|
|
429
|
+
// Convert date strings back to dates
|
|
430
|
+
session.startTime = new Date(session.startTime);
|
|
431
|
+
session.lastActivity = new Date(session.lastActivity);
|
|
432
|
+
|
|
433
|
+
// Only restore non-expired sessions
|
|
434
|
+
if (!this.isSessionExpired(session)) {
|
|
435
|
+
this.sessions.set(sessionId, session);
|
|
436
|
+
|
|
437
|
+
// Rebuild indexes
|
|
438
|
+
if (!this.agentSessions.has(session.agentId)) {
|
|
439
|
+
this.agentSessions.set(session.agentId, new Set());
|
|
440
|
+
}
|
|
441
|
+
this.agentSessions.get(session.agentId).add(sessionId);
|
|
442
|
+
|
|
443
|
+
if (!this.projectSessions.has(session.projectRoot)) {
|
|
444
|
+
this.projectSessions.set(session.projectRoot, new Set());
|
|
445
|
+
}
|
|
446
|
+
this.projectSessions.get(session.projectRoot).add(sessionId);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
log.info(`Restored ${this.sessions.size} sessions from disk`);
|
|
451
|
+
} catch (error) {
|
|
452
|
+
log.warn(`Failed to load state: ${getErrorMessage(error)}`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Shutdown the session manager
|
|
458
|
+
*/
|
|
459
|
+
shutdown() {
|
|
460
|
+
if (this.cleanupInterval) {
|
|
461
|
+
clearInterval(this.cleanupInterval);
|
|
462
|
+
}
|
|
463
|
+
this.saveState();
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Create a session manager instance
|
|
469
|
+
* @param {Object} options - Options
|
|
470
|
+
* @returns {SessionManager} Manager instance
|
|
471
|
+
*/
|
|
472
|
+
function createSessionManager(options = {}) {
|
|
473
|
+
return new SessionManager(options);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Default instance
|
|
477
|
+
let defaultManager = null;
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Get the default session manager
|
|
481
|
+
* @param {string} projectRoot - Project root for persist path
|
|
482
|
+
* @returns {SessionManager} Default manager
|
|
483
|
+
*/
|
|
484
|
+
function getSessionManager(projectRoot) {
|
|
485
|
+
if (!defaultManager) {
|
|
486
|
+
const persistPath = projectRoot
|
|
487
|
+
? path.join(projectRoot, ".vibecheck", "conductor", "sessions.json")
|
|
488
|
+
: null;
|
|
489
|
+
defaultManager = createSessionManager({ persistPath });
|
|
490
|
+
}
|
|
491
|
+
return defaultManager;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export {
|
|
495
|
+
SessionManager,
|
|
496
|
+
createSessionManager,
|
|
497
|
+
getSessionManager,
|
|
498
|
+
TIER_TRUST_MAP,
|
|
499
|
+
DEFAULT_SESSION_TIMEOUT_MS,
|
|
500
|
+
};
|