@vibecheckai/cli 3.2.5 → 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.
Files changed (197) hide show
  1. package/bin/.generated +25 -25
  2. package/bin/dev/run-v2-torture.js +30 -30
  3. package/bin/registry.js +192 -5
  4. package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
  5. package/bin/runners/lib/agent-firewall/change-packet/builder.js +280 -6
  6. package/bin/runners/lib/agent-firewall/critic/index.js +151 -0
  7. package/bin/runners/lib/agent-firewall/critic/judge.js +432 -0
  8. package/bin/runners/lib/agent-firewall/critic/prompts.js +305 -0
  9. package/bin/runners/lib/agent-firewall/lawbook/distributor.js +465 -0
  10. package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +604 -0
  11. package/bin/runners/lib/agent-firewall/lawbook/index.js +304 -0
  12. package/bin/runners/lib/agent-firewall/lawbook/registry.js +514 -0
  13. package/bin/runners/lib/agent-firewall/lawbook/schema.js +420 -0
  14. package/bin/runners/lib/agent-firewall/logger.js +141 -0
  15. package/bin/runners/lib/agent-firewall/policy/loader.js +312 -4
  16. package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +113 -1
  17. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +133 -6
  18. package/bin/runners/lib/agent-firewall/proposal/extractor.js +394 -0
  19. package/bin/runners/lib/agent-firewall/proposal/index.js +212 -0
  20. package/bin/runners/lib/agent-firewall/proposal/schema.js +251 -0
  21. package/bin/runners/lib/agent-firewall/proposal/validator.js +386 -0
  22. package/bin/runners/lib/agent-firewall/reality/index.js +332 -0
  23. package/bin/runners/lib/agent-firewall/reality/state.js +625 -0
  24. package/bin/runners/lib/agent-firewall/reality/watcher.js +322 -0
  25. package/bin/runners/lib/agent-firewall/risk/index.js +173 -0
  26. package/bin/runners/lib/agent-firewall/risk/scorer.js +328 -0
  27. package/bin/runners/lib/agent-firewall/risk/thresholds.js +321 -0
  28. package/bin/runners/lib/agent-firewall/risk/vectors.js +421 -0
  29. package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +472 -0
  30. package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +346 -0
  31. package/bin/runners/lib/agent-firewall/simulator/index.js +181 -0
  32. package/bin/runners/lib/agent-firewall/simulator/route-validator.js +380 -0
  33. package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +661 -0
  34. package/bin/runners/lib/agent-firewall/time-machine/index.js +267 -0
  35. package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +436 -0
  36. package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +490 -0
  37. package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +530 -0
  38. package/bin/runners/lib/analyzers.js +81 -18
  39. package/bin/runners/lib/api-client.js +269 -0
  40. package/bin/runners/lib/auth-truth.js +193 -193
  41. package/bin/runners/lib/authority-badge.js +425 -0
  42. package/bin/runners/lib/backup.js +62 -62
  43. package/bin/runners/lib/billing.js +107 -107
  44. package/bin/runners/lib/claims.js +118 -118
  45. package/bin/runners/lib/cli-output.js +7 -1
  46. package/bin/runners/lib/cli-ui.js +540 -540
  47. package/bin/runners/lib/contracts/auth-contract.js +202 -202
  48. package/bin/runners/lib/contracts/env-contract.js +181 -181
  49. package/bin/runners/lib/contracts/external-contract.js +206 -206
  50. package/bin/runners/lib/contracts/guard.js +168 -168
  51. package/bin/runners/lib/contracts/index.js +89 -89
  52. package/bin/runners/lib/contracts/plan-validator.js +311 -311
  53. package/bin/runners/lib/contracts/route-contract.js +199 -199
  54. package/bin/runners/lib/contracts.js +804 -804
  55. package/bin/runners/lib/detect.js +89 -89
  56. package/bin/runners/lib/doctor/autofix.js +254 -254
  57. package/bin/runners/lib/doctor/index.js +37 -37
  58. package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
  59. package/bin/runners/lib/doctor/modules/index.js +46 -46
  60. package/bin/runners/lib/doctor/modules/network.js +250 -250
  61. package/bin/runners/lib/doctor/modules/project.js +312 -312
  62. package/bin/runners/lib/doctor/modules/runtime.js +224 -224
  63. package/bin/runners/lib/doctor/modules/security.js +348 -348
  64. package/bin/runners/lib/doctor/modules/system.js +213 -213
  65. package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
  66. package/bin/runners/lib/doctor/reporter.js +262 -262
  67. package/bin/runners/lib/doctor/service.js +262 -262
  68. package/bin/runners/lib/doctor/types.js +113 -113
  69. package/bin/runners/lib/doctor/ui.js +263 -263
  70. package/bin/runners/lib/doctor-v2.js +608 -608
  71. package/bin/runners/lib/drift.js +425 -425
  72. package/bin/runners/lib/enforcement.js +72 -72
  73. package/bin/runners/lib/enterprise-detect.js +603 -603
  74. package/bin/runners/lib/enterprise-init.js +942 -942
  75. package/bin/runners/lib/env-resolver.js +417 -417
  76. package/bin/runners/lib/env-template.js +66 -66
  77. package/bin/runners/lib/env.js +189 -189
  78. package/bin/runners/lib/error-handler.js +16 -9
  79. package/bin/runners/lib/exit-codes.js +275 -0
  80. package/bin/runners/lib/extractors/client-calls.js +990 -990
  81. package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
  82. package/bin/runners/lib/extractors/fastify-routes.js +426 -426
  83. package/bin/runners/lib/extractors/index.js +363 -363
  84. package/bin/runners/lib/extractors/next-routes.js +524 -524
  85. package/bin/runners/lib/extractors/proof-graph.js +431 -431
  86. package/bin/runners/lib/extractors/route-matcher.js +451 -451
  87. package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
  88. package/bin/runners/lib/extractors/ui-bindings.js +547 -547
  89. package/bin/runners/lib/findings-schema.js +281 -281
  90. package/bin/runners/lib/firewall-prompt.js +50 -50
  91. package/bin/runners/lib/global-flags.js +37 -0
  92. package/bin/runners/lib/graph/graph-builder.js +265 -265
  93. package/bin/runners/lib/graph/html-renderer.js +413 -413
  94. package/bin/runners/lib/graph/index.js +32 -32
  95. package/bin/runners/lib/graph/runtime-collector.js +215 -215
  96. package/bin/runners/lib/graph/static-extractor.js +518 -518
  97. package/bin/runners/lib/help-formatter.js +413 -0
  98. package/bin/runners/lib/html-report.js +650 -650
  99. package/bin/runners/lib/llm.js +75 -75
  100. package/bin/runners/lib/logger.js +38 -0
  101. package/bin/runners/lib/meter.js +61 -61
  102. package/bin/runners/lib/missions/evidence.js +126 -126
  103. package/bin/runners/lib/patch.js +40 -40
  104. package/bin/runners/lib/permissions/auth-model.js +213 -213
  105. package/bin/runners/lib/permissions/idor-prover.js +205 -205
  106. package/bin/runners/lib/permissions/index.js +45 -45
  107. package/bin/runners/lib/permissions/matrix-builder.js +198 -198
  108. package/bin/runners/lib/pkgjson.js +28 -28
  109. package/bin/runners/lib/policy.js +295 -295
  110. package/bin/runners/lib/preflight.js +142 -142
  111. package/bin/runners/lib/reality/correlation-detectors.js +359 -359
  112. package/bin/runners/lib/reality/index.js +318 -318
  113. package/bin/runners/lib/reality/request-hashing.js +416 -416
  114. package/bin/runners/lib/reality/request-mapper.js +453 -453
  115. package/bin/runners/lib/reality/safety-rails.js +463 -463
  116. package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
  117. package/bin/runners/lib/reality/toast-detector.js +393 -393
  118. package/bin/runners/lib/reality-findings.js +84 -84
  119. package/bin/runners/lib/receipts.js +179 -179
  120. package/bin/runners/lib/redact.js +29 -29
  121. package/bin/runners/lib/replay/capsule-manager.js +154 -154
  122. package/bin/runners/lib/replay/index.js +263 -263
  123. package/bin/runners/lib/replay/player.js +348 -348
  124. package/bin/runners/lib/replay/recorder.js +331 -331
  125. package/bin/runners/lib/report.js +135 -135
  126. package/bin/runners/lib/route-detection.js +1140 -1140
  127. package/bin/runners/lib/sandbox/index.js +59 -59
  128. package/bin/runners/lib/sandbox/proof-chain.js +399 -399
  129. package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
  130. package/bin/runners/lib/sandbox/worktree.js +174 -174
  131. package/bin/runners/lib/schema-validator.js +350 -350
  132. package/bin/runners/lib/schemas/contracts.schema.json +160 -160
  133. package/bin/runners/lib/schemas/finding.schema.json +100 -100
  134. package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
  135. package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
  136. package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
  137. package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
  138. package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
  139. package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
  140. package/bin/runners/lib/schemas/validator.js +438 -438
  141. package/bin/runners/lib/score-history.js +282 -282
  142. package/bin/runners/lib/share-pack.js +239 -239
  143. package/bin/runners/lib/snippets.js +67 -67
  144. package/bin/runners/lib/unified-cli-output.js +604 -0
  145. package/bin/runners/lib/upsell.js +658 -510
  146. package/bin/runners/lib/usage.js +153 -153
  147. package/bin/runners/lib/validate-patch.js +156 -156
  148. package/bin/runners/lib/verdict-engine.js +628 -628
  149. package/bin/runners/reality/engine.js +917 -917
  150. package/bin/runners/reality/flows.js +122 -122
  151. package/bin/runners/reality/report.js +378 -378
  152. package/bin/runners/reality/session.js +193 -193
  153. package/bin/runners/runAgent.d.ts +5 -0
  154. package/bin/runners/runApprove.js +1200 -0
  155. package/bin/runners/runAuth.js +324 -95
  156. package/bin/runners/runCheckpoint.js +39 -21
  157. package/bin/runners/runClassify.js +859 -0
  158. package/bin/runners/runContext.js +136 -24
  159. package/bin/runners/runDoctor.js +108 -68
  160. package/bin/runners/runFirewall.d.ts +5 -0
  161. package/bin/runners/runFirewallHook.d.ts +5 -0
  162. package/bin/runners/runFix.js +6 -5
  163. package/bin/runners/runGuard.js +262 -168
  164. package/bin/runners/runInit.js +3 -2
  165. package/bin/runners/runMcp.js +130 -52
  166. package/bin/runners/runPolish.js +43 -20
  167. package/bin/runners/runProve.js +1 -2
  168. package/bin/runners/runReport.js +3 -2
  169. package/bin/runners/runScan.js +145 -44
  170. package/bin/runners/runShip.js +3 -4
  171. package/bin/runners/runTruth.d.ts +5 -0
  172. package/bin/runners/runValidate.js +19 -2
  173. package/bin/runners/runWatch.js +104 -53
  174. package/bin/vibecheck.js +106 -19
  175. package/mcp-server/HARDENING_SUMMARY.md +299 -0
  176. package/mcp-server/agent-firewall-interceptor.js +367 -31
  177. package/mcp-server/authority-tools.js +569 -0
  178. package/mcp-server/conductor/conflict-resolver.js +588 -0
  179. package/mcp-server/conductor/execution-planner.js +544 -0
  180. package/mcp-server/conductor/index.js +377 -0
  181. package/mcp-server/conductor/lock-manager.js +615 -0
  182. package/mcp-server/conductor/request-queue.js +550 -0
  183. package/mcp-server/conductor/session-manager.js +500 -0
  184. package/mcp-server/conductor/tools.js +510 -0
  185. package/mcp-server/index.js +1199 -208
  186. package/mcp-server/lib/api-client.cjs +305 -0
  187. package/mcp-server/lib/logger.cjs +30 -0
  188. package/mcp-server/logger.js +173 -0
  189. package/mcp-server/package.json +2 -2
  190. package/mcp-server/premium-tools.js +2 -2
  191. package/mcp-server/tier-auth.js +351 -136
  192. package/mcp-server/tools/index.js +72 -72
  193. package/mcp-server/truth-firewall-tools.js +145 -15
  194. package/mcp-server/vibecheck-tools.js +2 -2
  195. package/package.json +2 -3
  196. package/mcp-server/index.old.js +0 -4137
  197. 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
+ };