fraim 2.0.353 → 2.0.354
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.
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getFeatureFlags = getFeatureFlags;
|
|
4
4
|
exports.isPersonaEntitlementsEnabled = isPersonaEntitlementsEnabled;
|
|
5
|
+
exports.isShowTokenCostEnabled = isShowTokenCostEnabled;
|
|
5
6
|
exports.getPublicFeatureFlags = getPublicFeatureFlags;
|
|
6
7
|
function parseBooleanFlag(value) {
|
|
7
8
|
if (!value)
|
|
@@ -18,6 +19,9 @@ function getFeatureFlags() {
|
|
|
18
19
|
function isPersonaEntitlementsEnabled() {
|
|
19
20
|
return getFeatureFlags().personaEntitlements.enabled;
|
|
20
21
|
}
|
|
22
|
+
function isShowTokenCostEnabled() {
|
|
23
|
+
return parseBooleanFlag(process.env.FRAIM_FF_SHOW_TOKEN_COST);
|
|
24
|
+
}
|
|
21
25
|
function getPublicFeatureFlags() {
|
|
22
26
|
return {
|
|
23
27
|
personaEntitlementsEnabled: isPersonaEntitlementsEnabled()
|
|
@@ -188,6 +188,10 @@ class FraimDbService {
|
|
|
188
188
|
await this.sessionsCollection.createIndex({ sessionId: 1 }, { unique: true });
|
|
189
189
|
await this.sessionsCollection.createIndex({ userId: 1 });
|
|
190
190
|
await this.sessionsCollection.createIndex({ lastActive: -1 });
|
|
191
|
+
// Issue #2093 — getActiveSessionByApiKey no longer bounds its match by recency, so a
|
|
192
|
+
// long-lived user's full session history is a candidate. This compound index keeps that
|
|
193
|
+
// lookup an indexed scan with an ordered result instead of a fetch-then-sort in memory.
|
|
194
|
+
await this.sessionsCollection.createIndex({ userId: 1, lastActive: -1 });
|
|
191
195
|
await this.signupsCollection.createIndex({ email: 1 }, { unique: true });
|
|
192
196
|
await this.signupsCollection.createIndex({ timestamp: -1 });
|
|
193
197
|
await this.salesCollection.createIndex({ email: 1 });
|
|
@@ -344,17 +348,21 @@ class FraimDbService {
|
|
|
344
348
|
// Preserve the stored credential and its history; auth checks the current entitlement.
|
|
345
349
|
return paid || credential;
|
|
346
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Most recently active session for the key's owner, at any age.
|
|
353
|
+
*
|
|
354
|
+
* Issue #2093 — a FRAIM work session has no lifetime. Elapsed time must never make an
|
|
355
|
+
* existing, owned session unresolvable, so this lookup filters on ownership only and
|
|
356
|
+
* leaves recency to the sort. Access is still bounded by `verifyApiKey`: an unknown,
|
|
357
|
+
* revoked or suspended credential resolves nothing.
|
|
358
|
+
*/
|
|
347
359
|
async getActiveSessionByApiKey(key) {
|
|
348
360
|
if (!this.keysCollection || !this.sessionsCollection)
|
|
349
361
|
throw new Error('DB not connected');
|
|
350
|
-
// 1. Get user for this key
|
|
351
362
|
const apiKeyData = await this.verifyApiKey(key);
|
|
352
363
|
if (!apiKeyData)
|
|
353
364
|
return null;
|
|
354
|
-
|
|
355
|
-
const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
356
|
-
const session = await this.sessionsCollection.findOne({ userId: apiKeyData.userId, lastActive: { $gt: dayAgo } }, { sort: { lastActive: -1 } });
|
|
357
|
-
return session;
|
|
365
|
+
return this.sessionsCollection.findOne({ userId: apiKeyData.userId }, { sort: { lastActive: -1 } });
|
|
358
366
|
}
|
|
359
367
|
async getSessionByApiKeyAndSessionId(key, sessionId) {
|
|
360
368
|
if (!this.keysCollection || !this.sessionsCollection)
|
|
@@ -364,11 +372,17 @@ class FraimDbService {
|
|
|
364
372
|
return null;
|
|
365
373
|
return this.getSessionByUserIdAndSessionId(apiKeyData.userId, sessionId);
|
|
366
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Exact session lookup scoped to its owner, at any age.
|
|
377
|
+
*
|
|
378
|
+
* Issue #2093 — resuming a session after days, months or years must return the original
|
|
379
|
+
* record so the run keeps its job, phase and review state. `userId` remains the only
|
|
380
|
+
* eligibility condition, so another account still cannot reach this session.
|
|
381
|
+
*/
|
|
367
382
|
async getSessionByUserIdAndSessionId(userId, sessionId) {
|
|
368
383
|
if (!this.sessionsCollection)
|
|
369
384
|
throw new Error('DB not connected');
|
|
370
|
-
|
|
371
|
-
return this.sessionsCollection.findOne({ sessionId, userId, lastActive: { $gt: dayAgo } });
|
|
385
|
+
return this.sessionsCollection.findOne({ sessionId, userId });
|
|
372
386
|
}
|
|
373
387
|
async createAuthSession(input) {
|
|
374
388
|
if (!this.authSessionsCollection)
|
|
@@ -137,7 +137,9 @@ class McpService {
|
|
|
137
137
|
else if (method === 'fraim/session/validate') {
|
|
138
138
|
const sessionId = typeof params?.sessionId === 'string' ? params.sessionId.trim() : '';
|
|
139
139
|
if (!sessionId || !context.userId || !(await this.dbService.getSessionByUserIdAndSessionId(context.userId, sessionId))) {
|
|
140
|
-
|
|
140
|
+
// Issue #2093 — sessions do not expire, so the rejection names the two
|
|
141
|
+
// causes that remain: the session is unknown, or it belongs elsewhere.
|
|
142
|
+
const error = new Error('FRAIM session is unknown or not accessible with this account. Call fraim_connect again.');
|
|
141
143
|
error.code = -32001;
|
|
142
144
|
throw error;
|
|
143
145
|
}
|
|
@@ -306,19 +306,21 @@ class UsageAnalyticsService {
|
|
|
306
306
|
await this.logUsage(mentoringEvent);
|
|
307
307
|
}
|
|
308
308
|
/**
|
|
309
|
-
* Validate that
|
|
309
|
+
* Validate that this user started this jobId, regardless of how long ago.
|
|
310
310
|
*/
|
|
311
311
|
async validateJobId(jobId, userId) {
|
|
312
312
|
try {
|
|
313
|
-
//
|
|
314
|
-
|
|
315
|
-
|
|
313
|
+
// Issue #2093 — a run resumed days or months later is still the same job, so
|
|
314
|
+
// this match is on job identity and ownership only. Adding an age condition
|
|
315
|
+
// here would silently drop analytics for exactly the long-gap resumes that
|
|
316
|
+
// the session fix is meant to support.
|
|
317
|
+
const events = await this.dbService.getDb();
|
|
318
|
+
const collection = events.collection('fraim_usage_events');
|
|
316
319
|
const jobStartEvent = await collection.findOne({
|
|
317
320
|
jobId,
|
|
318
321
|
userId,
|
|
319
322
|
type: 'job',
|
|
320
|
-
'args.action': 'start'
|
|
321
|
-
createdAt: { $gte: new Date(Date.now() - 24 * 60 * 60 * 1000) } // Within last 24 hours
|
|
323
|
+
'args.action': 'start'
|
|
322
324
|
});
|
|
323
325
|
return jobStartEvent !== null;
|
|
324
326
|
}
|