fraim 2.0.195 → 2.0.197
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/src/ai-hub/brand-store.js +232 -0
- package/dist/src/ai-hub/server.js +78 -45
- package/dist/src/api/personas/me.js +6 -6
- package/dist/src/cli/utils/org-pack-sync.js +35 -5
- package/dist/src/first-run/types.js +1 -1
- package/dist/src/local-mcp-server/learning-context-builder.js +41 -0
- package/dist/src/services/persona-entitlement-service.js +71 -7
- package/package.json +1 -1
- package/public/ai-hub/index.html +17 -1
- package/public/ai-hub/script.js +469 -22
- package/public/ai-hub/styles.css +67 -0
- package/public/first-run/script.js +98 -25
- package/public/first-run/styles.css +34 -8
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.computeSubscriptionActive = computeSubscriptionActive;
|
|
4
|
+
exports.resolvePersonaAccessStatuses = resolvePersonaAccessStatuses;
|
|
5
|
+
exports.buildUngatedPersonaWorkspaceState = buildUngatedPersonaWorkspaceState;
|
|
3
6
|
exports.buildPersonaHireUrl = buildPersonaHireUrl;
|
|
4
7
|
exports.syncPersonaEntitlementPurchase = syncPersonaEntitlementPurchase;
|
|
5
8
|
exports.syncPersonaEntitlementFromCheckoutSession = syncPersonaEntitlementFromCheckoutSession;
|
|
@@ -9,6 +12,66 @@ const persona_hiring_1 = require("../config/persona-hiring");
|
|
|
9
12
|
const persona_capability_bundles_1 = require("../config/persona-capability-bundles");
|
|
10
13
|
const workspace_identity_1 = require("./workspace-identity");
|
|
11
14
|
const DEFAULT_JOB_CREDITS = 1;
|
|
15
|
+
// The one place the "is the persona system gating this workspace?" question is
|
|
16
|
+
// answered. A workspace is subscribed if it explicitly opted in (personaSystemActive)
|
|
17
|
+
// or has ever held an active entitlement. Shared by getWorkspacePersonaState and
|
|
18
|
+
// evaluatePersonaAccess so display and enforcement can never diverge.
|
|
19
|
+
function computeSubscriptionActive(apiKeyData, entitlements) {
|
|
20
|
+
return apiKeyData?.personaSystemActive === true || entitlements.some((e) => e.status === 'active');
|
|
21
|
+
}
|
|
22
|
+
// Single source of truth for per-persona access, mirroring evaluatePersonaAccess's
|
|
23
|
+
// gating rules exactly:
|
|
24
|
+
// - `ungated` (feature flag OFF, or legacy/never-subscribed workspace) => every
|
|
25
|
+
// persona is accessible ('hired').
|
|
26
|
+
// - otherwise => 'hired' iff the workspace holds an active entitlement for it.
|
|
27
|
+
// Pure and DB-free so it is trivially testable and callable from either code path.
|
|
28
|
+
function resolvePersonaAccessStatuses(ungated, entitlements) {
|
|
29
|
+
const activeByKey = new Map();
|
|
30
|
+
for (const entitlement of entitlements) {
|
|
31
|
+
if (entitlement.status !== 'active')
|
|
32
|
+
continue;
|
|
33
|
+
const list = activeByKey.get(entitlement.personaKey) || [];
|
|
34
|
+
list.push(entitlement);
|
|
35
|
+
activeByKey.set(entitlement.personaKey, list);
|
|
36
|
+
}
|
|
37
|
+
return (0, persona_capability_bundles_1.listPersonaCapabilityBundles)().map((bundle) => {
|
|
38
|
+
const active = activeByKey.get(bundle.personaKey) || [];
|
|
39
|
+
const seatCount = active.reduce((sum, e) => sum + (e.jobCreditsRemaining ?? 1), 0);
|
|
40
|
+
return {
|
|
41
|
+
personaKey: bundle.personaKey,
|
|
42
|
+
personaName: bundle.catalogMetadata.displayName,
|
|
43
|
+
personaRole: bundle.catalogMetadata.role,
|
|
44
|
+
hireUrl: buildPersonaHireUrl(bundle.personaKey, bundle.defaultHireMode),
|
|
45
|
+
status: (ungated || active.length > 0) ? 'hired' : 'locked',
|
|
46
|
+
seatCount,
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// The persona-entitlements feature is OFF: there is no gating, so every persona is
|
|
51
|
+
// accessible. Returned by /api/personas/me instead of a 404 so downstream consumers
|
|
52
|
+
// (the Hub especially) inherit "flag off => all employees available" rather than
|
|
53
|
+
// inventing their own fallback. Matches maybeBuildPersonaLockResponse, which also
|
|
54
|
+
// short-circuits to "allowed" when the flag is off. No DB lookup is required.
|
|
55
|
+
function buildUngatedPersonaWorkspaceState(userId, apiKey) {
|
|
56
|
+
return {
|
|
57
|
+
featureFlagEnabled: false,
|
|
58
|
+
featureFlags: { personaEntitlementsEnabled: false },
|
|
59
|
+
subscriptionActive: false,
|
|
60
|
+
workspaceId: normalizeEmail(userId),
|
|
61
|
+
userId: normalizeEmail(userId),
|
|
62
|
+
apiKey: apiKey || null,
|
|
63
|
+
entitlements: [],
|
|
64
|
+
catalog: (0, persona_capability_bundles_1.listPersonaCapabilityBundles)().map((bundle) => ({
|
|
65
|
+
personaKey: bundle.personaKey,
|
|
66
|
+
personaName: bundle.catalogMetadata.displayName,
|
|
67
|
+
personaRole: bundle.catalogMetadata.role,
|
|
68
|
+
hireUrl: buildPersonaHireUrl(bundle.personaKey, bundle.defaultHireMode),
|
|
69
|
+
protectedJobs: bundle.protectedJobs,
|
|
70
|
+
})),
|
|
71
|
+
lockedPersonas: [],
|
|
72
|
+
personas: resolvePersonaAccessStatuses(true, []),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
12
75
|
function normalizeEmail(email) {
|
|
13
76
|
return email.trim().toLowerCase();
|
|
14
77
|
}
|
|
@@ -154,9 +217,7 @@ async function getWorkspacePersonaState(dbService, userId, apiKey) {
|
|
|
154
217
|
protectedJobs: bundle.protectedJobs
|
|
155
218
|
}));
|
|
156
219
|
const knownPersonaKeys = new Set(entitlements.map((entitlement) => entitlement.personaKey));
|
|
157
|
-
|
|
158
|
-
const subscriptionActive = apiKeyData?.personaSystemActive === true ||
|
|
159
|
-
entitlements.some((e) => e.status === 'active');
|
|
220
|
+
const subscriptionActive = computeSubscriptionActive(apiKeyData, entitlements);
|
|
160
221
|
return {
|
|
161
222
|
featureFlagEnabled: true,
|
|
162
223
|
featureFlags: {
|
|
@@ -185,7 +246,10 @@ async function getWorkspacePersonaState(dbService, userId, apiKey) {
|
|
|
185
246
|
.map((persona) => ({
|
|
186
247
|
...persona,
|
|
187
248
|
status: 'locked'
|
|
188
|
-
}))
|
|
249
|
+
})),
|
|
250
|
+
// Authoritative per-persona verdict: a non-subscribed (legacy) workspace is
|
|
251
|
+
// ungated, so all personas are accessible; a subscribed one gates to owned.
|
|
252
|
+
personas: resolvePersonaAccessStatuses(!subscriptionActive, entitlements),
|
|
189
253
|
};
|
|
190
254
|
}
|
|
191
255
|
async function evaluatePersonaAccess(dbService, userId, jobName, apiKey, returnTo) {
|
|
@@ -205,9 +269,9 @@ async function evaluatePersonaAccess(dbService, userId, jobName, apiKey, returnT
|
|
|
205
269
|
});
|
|
206
270
|
const workspaceEntitlements = await dbService.getPersonaEntitlementsByWorkspaceId(workspace.workspaceId, false);
|
|
207
271
|
// Legacy bypass: if the workspace has never subscribed to the persona system,
|
|
208
|
-
// all jobs are freely accessible — no lock enforcement.
|
|
209
|
-
|
|
210
|
-
|
|
272
|
+
// all jobs are freely accessible — no lock enforcement. Same rule the Hub
|
|
273
|
+
// display inherits via resolvePersonaAccessStatuses(!subscriptionActive, ...).
|
|
274
|
+
const subscriptionActive = computeSubscriptionActive(apiKeyData, workspaceEntitlements);
|
|
211
275
|
if (!subscriptionActive) {
|
|
212
276
|
return {
|
|
213
277
|
allowed: true,
|
package/package.json
CHANGED
package/public/ai-hub/index.html
CHANGED
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
// #700: set the theme pre-paint (no flash). Explicit choice in
|
|
13
13
|
// localStorage wins; otherwise follow the OS preference.
|
|
14
14
|
try{var t=localStorage.getItem('fraim-theme');if(t!=='light'&&t!=='dark'){t=(window.matchMedia&&window.matchMedia('(prefers-color-scheme: dark)').matches)?'dark':'light';}h.setAttribute('data-theme',t);}catch(e){h.setAttribute('data-theme','light');}
|
|
15
|
+
// #744: apply the cached org brand pre-paint (no flash of FRAIM identity
|
|
16
|
+
// before company branding). script.js refines the per-theme accent on load.
|
|
17
|
+
try{var b=JSON.parse(localStorage.getItem('fraim-org-brand')||'null');if(b){if(typeof b.color==='string'&&/^#[0-9a-f]{6}$/i.test(b.color)){h.style.setProperty('--accent',b.color);h.style.setProperty('--accent-strong',b.color);}if(typeof b.name==='string'&&b.name){document.title=b.name+' Hub';}}}catch(e){}
|
|
15
18
|
})();</script>
|
|
16
19
|
<link rel="stylesheet" href="./styles.css?v=conv-panels-20260611b">
|
|
17
20
|
<link rel="stylesheet" href="./review.css">
|
|
@@ -20,10 +23,15 @@
|
|
|
20
23
|
|
|
21
24
|
<!-- Issue #512: three-area top nav + account menu (surface=hub only). -->
|
|
22
25
|
<nav class="hub-tabs" id="hub-tabs" hidden>
|
|
26
|
+
<!-- #744: company brand lockup (logo + name), populated from bootstrap.orgBrand. -->
|
|
27
|
+
<span class="hub-brand" id="hub-brand" hidden></span>
|
|
28
|
+
<span class="hub-brand-divider" id="hub-brand-divider" hidden></span>
|
|
23
29
|
<button class="hub-tab" type="button" data-area="company">Company</button>
|
|
24
30
|
<button class="hub-tab" type="button" data-area="manager">Manager</button>
|
|
25
31
|
<button class="hub-tab on" type="button" data-area="projects">Projects</button>
|
|
26
32
|
<div class="nav-right">
|
|
33
|
+
<!-- #744: FRAIM co-mark (co-branding, not white-label) shown when a brand is set. -->
|
|
34
|
+
<span class="hub-cobrand" id="hub-cobrand" hidden></span>
|
|
27
35
|
<button class="avatar-btn" id="avatar-btn" type="button" title="Account & settings">SM</button>
|
|
28
36
|
<div id="account-menu" class="account-menu">
|
|
29
37
|
<div class="am-header">
|
|
@@ -68,6 +76,12 @@
|
|
|
68
76
|
<div class="area-h1">Company</div>
|
|
69
77
|
<p class="area-lede">Your organization's identity — what you do, how you operate, and the guardrails every employee follows on every job.</p>
|
|
70
78
|
<div id="company-push-banner"></div>
|
|
79
|
+
<!-- #744: Brand editor — company identity across the Hub. -->
|
|
80
|
+
<details class="ctx-acc" id="company-brand-acc">
|
|
81
|
+
<summary><span class="ca-chev">▸</span> <span>🎨 Brand</span>
|
|
82
|
+
<span class="ca-note">— your company's identity across the Hub, applied for every teammate</span></summary>
|
|
83
|
+
<div class="ctx-acc-body"><div class="card area-profile" id="company-brand-editor"></div></div>
|
|
84
|
+
</details>
|
|
71
85
|
<details class="ctx-acc" id="company-ctx-acc">
|
|
72
86
|
<summary><span class="ca-chev">▸</span> <span>🏢 Context & rules</span></summary>
|
|
73
87
|
<div class="ctx-acc-body"><div class="card area-profile" id="company-profile"></div></div>
|
|
@@ -199,6 +213,7 @@
|
|
|
199
213
|
<!-- Onboarding state banner lives ABOVE the accordions so it shows even
|
|
200
214
|
when the Brief is collapsed (so the conversation keeps its height). -->
|
|
201
215
|
<div id="proj-onboarding-banner"></div>
|
|
216
|
+
<div id="hub-agent-setup-panel" class="hub-agent-setup-panel" data-testid="hub-cli-setup-panel" hidden></div>
|
|
202
217
|
<details class="ctx-acc" id="proj-brief-acc">
|
|
203
218
|
<summary><span class="ca-chev">▸</span> <span>📋 Brief</span>
|
|
204
219
|
<span class="ca-note">— this project's context and rules, captured by Project Onboarding</span></summary>
|
|
@@ -839,6 +854,7 @@
|
|
|
839
854
|
<span class="cp-agent-label">Run with:</span>
|
|
840
855
|
<span id="cp-agent-picker" class="cp-agent-pills"></span>
|
|
841
856
|
</div>
|
|
857
|
+
<div id="cp-agent-install-panel" class="cp-agent-install-panel" data-testid="cp-cli-setup-panel" hidden></div>
|
|
842
858
|
</div>
|
|
843
859
|
</div>
|
|
844
860
|
|
|
@@ -868,6 +884,6 @@
|
|
|
868
884
|
</div>
|
|
869
885
|
</div>
|
|
870
886
|
|
|
871
|
-
<script src="./script.js?v=
|
|
887
|
+
<script src="./script.js?v=persona-mgr-del-20260707"></script>
|
|
872
888
|
</body>
|
|
873
889
|
</html>
|