fraim-framework 2.0.184 → 2.0.186

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.
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getManagerTeamFor = getManagerTeamFor;
4
+ exports.assignManagerSeatFor = assignManagerSeatFor;
5
+ exports.removeManagerSeatFor = removeManagerSeatFor;
6
+ exports.listManagerTeam = listManagerTeam;
7
+ exports.assignManagerTeam = assignManagerTeam;
8
+ exports.removeManagerTeam = removeManagerTeam;
9
+ const persona_entitlement_service_1 = require("../../services/persona-entitlement-service");
10
+ async function loadTeam(dbService, workspaceId, userKey) {
11
+ const rows = await dbService.getHubManagerAssignments(workspaceId, userKey);
12
+ return rows.map((r) => ({ personaKey: r.personaKey, assignedAt: r.assignedAt.toISOString() }));
13
+ }
14
+ /** Seats purchased for a persona in this workspace. Mirrors computePersonas' seatCountByKey:
15
+ * legacy (unsubscribed) workspaces have no assignable seats. */
16
+ function seatCountForPersona(state, personaKey) {
17
+ if (!state.subscriptionActive)
18
+ return 0;
19
+ return state.entitlements
20
+ .filter((e) => e.personaKey === personaKey && e.status === 'active')
21
+ .reduce((sum, e) => sum + (e.jobCreditsRemaining ?? 1), 0);
22
+ }
23
+ // ── Core operations (shared by hosted handlers and the Hub's local-DB gateway) ──────
24
+ async function getManagerTeamFor(dbService, userId, apiKey) {
25
+ const state = await (0, persona_entitlement_service_1.getWorkspacePersonaState)(dbService, userId, apiKey);
26
+ return loadTeam(dbService, state.workspaceId, state.userId);
27
+ }
28
+ async function assignManagerSeatFor(dbService, userId, apiKey, personaKey) {
29
+ const state = await (0, persona_entitlement_service_1.getWorkspacePersonaState)(dbService, userId, apiKey);
30
+ const seatCount = seatCountForPersona(state, personaKey);
31
+ if (seatCount === 0)
32
+ return { status: 404, body: { error: 'no_company_seat', personaKey } };
33
+ const seatsInUse = await dbService.countHubManagerAssignments(state.workspaceId, personaKey);
34
+ if (seatsInUse >= seatCount)
35
+ return { status: 409, body: { error: 'out_of_stock', personaKey, seatCount } };
36
+ await dbService.addHubManagerAssignment(state.workspaceId, state.userId, personaKey);
37
+ return { status: 200, body: { managerTeam: await loadTeam(dbService, state.workspaceId, state.userId) } };
38
+ }
39
+ async function removeManagerSeatFor(dbService, userId, apiKey, personaKey) {
40
+ const state = await (0, persona_entitlement_service_1.getWorkspacePersonaState)(dbService, userId, apiKey);
41
+ await dbService.removeHubManagerAssignment(state.workspaceId, state.userId, personaKey);
42
+ }
43
+ // ── Hosted HTTP handlers (thin wrappers over the core, authed via req.apiKeyData) ───
44
+ async function listManagerTeam(req, res, dbService) {
45
+ const apiKeyData = req.apiKeyData;
46
+ if (!apiKeyData?.userId) {
47
+ res.status(401).json({ error: 'Authentication required' });
48
+ return;
49
+ }
50
+ try {
51
+ res.json({ team: await getManagerTeamFor(dbService, apiKeyData.userId, apiKeyData.key) });
52
+ }
53
+ catch (error) {
54
+ console.error('[api] listManagerTeam failed:', error);
55
+ res.status(500).json({ error: 'internal_error', details: error?.message || String(error) });
56
+ }
57
+ }
58
+ async function assignManagerTeam(req, res, dbService) {
59
+ const apiKeyData = req.apiKeyData;
60
+ if (!apiKeyData?.userId) {
61
+ res.status(401).json({ error: 'Authentication required' });
62
+ return;
63
+ }
64
+ const { personaKey } = (req.body ?? {});
65
+ if (!personaKey) {
66
+ res.status(400).json({ error: 'personaKey required' });
67
+ return;
68
+ }
69
+ try {
70
+ const result = await assignManagerSeatFor(dbService, apiKeyData.userId, apiKeyData.key, personaKey);
71
+ res.status(result.status).json(result.body);
72
+ }
73
+ catch (error) {
74
+ console.error('[api] assignManagerTeam failed:', error);
75
+ res.status(500).json({ error: 'internal_error', details: error?.message || String(error) });
76
+ }
77
+ }
78
+ async function removeManagerTeam(req, res, dbService) {
79
+ const apiKeyData = req.apiKeyData;
80
+ if (!apiKeyData?.userId) {
81
+ res.status(401).json({ error: 'Authentication required' });
82
+ return;
83
+ }
84
+ const personaKey = String(req.params.personaKey ?? '');
85
+ try {
86
+ await removeManagerSeatFor(dbService, apiKeyData.userId, apiKeyData.key, personaKey);
87
+ res.status(204).end();
88
+ }
89
+ catch (error) {
90
+ console.error('[api] removeManagerTeam failed:', error);
91
+ res.status(500).json({ error: 'internal_error', details: error?.message || String(error) });
92
+ }
93
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim-framework",
3
- "version": "2.0.184",
3
+ "version": "2.0.186",
4
4
  "description": "FRAIM: AI Workforce Infrastructure — the organizational capability that turns AI agents into an accountable workforce, their operators into capable AI managers, and executives into leaders with clear optics on AI proficiency.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -8,7 +8,11 @@
8
8
  <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 48 48'%3E%3Crect width='48' height='48' rx='10' fill='%233d8a6e'/%3E%3Ctext x='24' y='32' text-anchor='middle' font-family='Helvetica,Arial,sans-serif' font-size='22' font-weight='700' fill='white'%3EH%3C/text%3E%3C/svg%3E">
9
9
  <!-- Detect Electron + host platform before CSS paints so html.electron / html.mac /
10
10
  html.win / html.linux rules apply on first render (no flash, correct native chrome). -->
11
- <script>(function(){var h=document.documentElement,ua=navigator.userAgent;if(/Electron/.test(ua))h.classList.add('electron');var p=(navigator.userAgentData&&navigator.userAgentData.platform)||navigator.platform||'';if(/mac/i.test(p)||/Mac|iPhone|iPad|iPod/.test(ua))h.classList.add('mac');else if(/win/i.test(p)||/Windows/.test(ua))h.classList.add('win');else if(/linux/i.test(p)||/Linux|X11/.test(ua))h.classList.add('linux');})();</script>
11
+ <script>(function(){var h=document.documentElement,ua=navigator.userAgent;if(/Electron/.test(ua))h.classList.add('electron');var p=(navigator.userAgentData&&navigator.userAgentData.platform)||navigator.platform||'';if(/mac/i.test(p)||/Mac|iPhone|iPad|iPod/.test(ua))h.classList.add('mac');else if(/win/i.test(p)||/Windows/.test(ua))h.classList.add('win');else if(/linux/i.test(p)||/Linux|X11/.test(ua))h.classList.add('linux');
12
+ // #700: set the theme pre-paint (no flash). Explicit choice in
13
+ // localStorage wins; otherwise follow the OS preference.
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
+ })();</script>
12
16
  <link rel="stylesheet" href="./styles.css?v=conv-panels-20260611b">
13
17
  <link rel="stylesheet" href="./review.css">
14
18
  </head>
@@ -41,9 +45,13 @@
41
45
  <span class="am-ico">📊</span>
42
46
  <div><div class="am-label">Analytics</div><div class="am-sub">Usage, cost, and ROI</div></div>
43
47
  </a>
48
+ <button class="am-item am-theme" id="am-theme-toggle" type="button" role="switch" aria-checked="false">
49
+ <span class="am-ico" id="am-theme-ico">🌙</span>
50
+ <div><div class="am-label">Dark mode</div><div class="am-sub" id="am-theme-sub">Off</div></div>
51
+ <span class="am-switch" aria-hidden="true"><span class="am-switch-knob"></span></span>
52
+ </button>
44
53
  <div class="am-div"></div>
45
- <a class="am-foot" id="am-settings" href="/account">Settings</a>
46
- <button class="am-foot" id="am-signout" type="button">Sign out</button>
54
+ <button class="am-foot" id="am-signout" type="button"><span class="am-ico">⏻</span><span>Sign out</span></button>
47
55
  </div>
48
56
  </div>
49
57
  </nav>
@@ -194,8 +202,7 @@
194
202
  <div class="ctx-acc-body">
195
203
  <div id="proj-deployments-list"></div>
196
204
  <div class="dep-actions">
197
- <button class="dep-add-btn" id="dep-add-schedule-btn" type="button">+ Schedule</button>
198
- <button class="dep-add-btn" id="dep-add-webhook-btn" type="button">+ Trigger</button>
205
+ <button class="dep-add-btn" id="dep-add-btn" type="button">+ New Assignment</button>
199
206
  </div>
200
207
  </div>
201
208
  </details>
@@ -265,7 +272,7 @@
265
272
  </section>
266
273
 
267
274
  <section class="rail-section rail-section--runs">
268
- <div class="rail-section-label">Runs</div>
275
+ <div class="rail-section-label">Employees</div>
269
276
  <div class="conv-list" id="conv-list"></div>
270
277
  </section>
271
278
  </aside>
@@ -684,110 +691,89 @@
684
691
  </div>
685
692
  </div>
686
693
 
687
- <!-- Issue #578: Add Schedule deployment modal -->
688
- <div id="dep-schedule-modal" class="modal-overlay" hidden>
694
+ <!-- Issue #693 R2: single consolidated "New Assignment" modal. Replaces the two
695
+ separate Schedule / Trigger modals. A segmented control switches between
696
+ Scheduled and Triggered, revealing only the type-specific fields. Shared
697
+ fields (Name, Job, AI Agent, Instructions) are common to both. Inner ids
698
+ (dep-sch-*, dep-wh-inbound-*) are preserved so the cron-preset and inbound-URL
699
+ helpers keep working. -->
700
+ <div id="dep-assignment-modal" class="modal-overlay" hidden>
689
701
  <div class="modal-card dep-modal-card">
690
702
  <div class="modal-hdr">
691
- <button class="modal-close" id="dep-schedule-close" type="button" aria-label="Close">×</button>
692
- <h2 id="dep-sch-modal-title">New Scheduled Assignment</h2>
693
- <p id="dep-sch-modal-sub">Run a job automatically on a recurring schedule.</p>
703
+ <button class="modal-close" id="dep-assign-close" type="button" aria-label="Close">×</button>
704
+ <h2 id="dep-assign-title">New assignment</h2>
705
+ <p>Run a job automatically - on a schedule, or when an external system calls in.</p>
694
706
  </div>
695
707
  <div class="modal-body">
708
+ <div class="dep-type-seg" id="dep-type-seg">
709
+ <button type="button" class="dep-type-btn on" data-dep-type="scheduled">&#9201; Scheduled</button>
710
+ <button type="button" class="dep-type-btn" data-dep-type="triggered">&#128279; Triggered</button>
711
+ </div>
696
712
  <div class="hm-field">
697
- <label for="dep-sch-label">Name</label>
698
- <input type="text" id="dep-sch-label" placeholder="e.g. Daily standup digest" />
713
+ <label for="dep-label">Name</label>
714
+ <input type="text" id="dep-label" placeholder="e.g. Daily standup digest" />
699
715
  </div>
700
716
  <div class="hm-fieldrow">
701
717
  <div class="hm-field">
702
- <label for="dep-sch-job">Job</label>
703
- <select id="dep-sch-job"></select>
704
- </div>
705
- <div class="hm-field">
706
- <label for="dep-sch-host">Employee</label>
707
- <select id="dep-sch-host"></select>
708
- </div>
709
- </div>
710
- <div class="hm-field">
711
- <label>Runs</label>
712
- <div class="dep-preset-chips" id="dep-sch-presets">
713
- <button type="button" class="dep-preset-chip" data-preset="daily">Daily</button>
714
- <button type="button" class="dep-preset-chip" data-preset="weekdays">Weekdays</button>
715
- <button type="button" class="dep-preset-chip" data-preset="weekly">Weekly</button>
716
- <button type="button" class="dep-preset-chip" data-preset="custom">Custom</button>
717
- </div>
718
- </div>
719
- <div id="dep-sch-time-row" class="hm-fieldrow" hidden>
720
- <div class="hm-field" id="dep-sch-day-field" hidden>
721
- <label for="dep-sch-day">Day</label>
722
- <select id="dep-sch-day">
723
- <option value="1">Monday</option>
724
- <option value="2">Tuesday</option>
725
- <option value="3">Wednesday</option>
726
- <option value="4">Thursday</option>
727
- <option value="5">Friday</option>
728
- <option value="6">Saturday</option>
729
- <option value="0">Sunday</option>
730
- </select>
718
+ <label for="dep-job">Job</label>
719
+ <select id="dep-job"></select>
731
720
  </div>
732
721
  <div class="hm-field">
733
- <label for="dep-sch-time">Time</label>
734
- <input type="time" id="dep-sch-time" value="09:00" />
722
+ <label for="dep-agent">AI Agent</label>
723
+ <select id="dep-agent"></select>
735
724
  </div>
736
725
  </div>
737
- <div id="dep-sch-custom-row" hidden>
726
+ <!-- Scheduled-only fields -->
727
+ <div id="dep-sched-block">
738
728
  <div class="hm-field">
739
- <label for="dep-sch-cron">Cron expression</label>
740
- <input type="text" id="dep-sch-cron" placeholder="e.g. 0 9 * * 1-5" />
741
- <p id="dep-sch-cron-preview" class="dep-cron-preview"></p>
729
+ <label>Runs</label>
730
+ <div class="dep-preset-chips" id="dep-sch-presets">
731
+ <button type="button" class="dep-preset-chip" data-preset="daily">Daily</button>
732
+ <button type="button" class="dep-preset-chip" data-preset="weekdays">Weekdays</button>
733
+ <button type="button" class="dep-preset-chip" data-preset="weekly">Weekly</button>
734
+ <button type="button" class="dep-preset-chip" data-preset="custom">Custom</button>
735
+ </div>
742
736
  </div>
743
- </div>
744
- <!-- hidden cron store for preset modes -->
745
- <input type="hidden" id="dep-sch-cron-preset" />
746
- <div class="hm-field">
747
- <label for="dep-sch-instructions">Instructions <span class="dep-optional">(optional)</span></label>
748
- <textarea id="dep-sch-instructions" rows="2" placeholder="Optional message sent to the employee at the start of each run"></textarea>
749
- </div>
750
- <div class="dep-modal-actions">
751
- <button type="button" id="dep-sch-save-btn" class="send-button">Add assignment</button>
752
- <button type="button" id="dep-sch-cancel-btn" class="cancel-button">Cancel</button>
753
- </div>
754
- <p id="dep-sch-error" class="dep-error" hidden></p>
755
- </div>
756
- </div>
757
- </div>
758
-
759
- <!-- Issue #578: Add Webhook deployment modal -->
760
- <div id="dep-webhook-modal" class="modal-overlay" hidden>
761
- <div class="modal-card dep-modal-card">
762
- <div class="modal-hdr">
763
- <button class="modal-close" id="dep-webhook-close" type="button" aria-label="Close">×</button>
764
- <h2 id="dep-wh-modal-title">New Triggered Assignment</h2>
765
- <p>Run a job when an external system calls the inbound URL.</p>
766
- </div>
767
- <div class="modal-body">
768
- <div class="hm-field">
769
- <label for="dep-wh-label">Name</label>
770
- <input type="text" id="dep-wh-label" placeholder="e.g. ServiceNow escalation handler" />
771
- </div>
772
- <div class="hm-fieldrow">
773
- <div class="hm-field">
774
- <label for="dep-wh-job">Job</label>
775
- <select id="dep-wh-job"></select>
737
+ <div id="dep-sch-time-row" class="hm-fieldrow" hidden>
738
+ <div class="hm-field" id="dep-sch-day-field" hidden>
739
+ <label for="dep-sch-day">Day</label>
740
+ <select id="dep-sch-day">
741
+ <option value="1">Monday</option>
742
+ <option value="2">Tuesday</option>
743
+ <option value="3">Wednesday</option>
744
+ <option value="4">Thursday</option>
745
+ <option value="5">Friday</option>
746
+ <option value="6">Saturday</option>
747
+ <option value="0">Sunday</option>
748
+ </select>
749
+ </div>
750
+ <div class="hm-field">
751
+ <label for="dep-sch-time">Time</label>
752
+ <input type="time" id="dep-sch-time" value="09:00" />
753
+ </div>
776
754
  </div>
777
- <div class="hm-field">
778
- <label for="dep-wh-host">Employee</label>
779
- <select id="dep-wh-host"></select>
755
+ <div id="dep-sch-custom-row" hidden>
756
+ <div class="hm-field">
757
+ <label for="dep-sch-cron">Cron expression</label>
758
+ <input type="text" id="dep-sch-cron" placeholder="e.g. 0 9 * * 1-5" />
759
+ <p id="dep-sch-cron-preview" class="dep-cron-preview"></p>
760
+ </div>
780
761
  </div>
762
+ <input type="hidden" id="dep-sch-cron-preset" />
763
+ </div>
764
+ <!-- Triggered-only note -->
765
+ <div id="dep-trig-block" hidden>
766
+ <p class="dep-trig-note">Runs when an external system POSTs to the inbound URL, generated after you add the assignment.</p>
781
767
  </div>
782
768
  <div class="hm-field">
783
- <label for="dep-wh-instructions">Instructions <span class="dep-optional">(optional)</span></label>
784
- <textarea id="dep-wh-instructions" rows="2" placeholder="Optional context prepended to each triggered run"></textarea>
769
+ <label for="dep-instructions">Instructions <span class="dep-optional">(optional)</span></label>
770
+ <textarea id="dep-instructions" rows="2" placeholder="Optional message sent to the agent at the start of each run"></textarea>
785
771
  </div>
786
772
  <div class="dep-modal-actions">
787
- <button type="button" id="dep-wh-save-btn" class="send-button">Add assignment</button>
788
- <button type="button" id="dep-wh-cancel-btn" class="cancel-button">Cancel</button>
773
+ <button type="button" id="dep-save-btn" class="send-button">Add assignment</button>
774
+ <button type="button" id="dep-cancel-btn" class="cancel-button">Cancel</button>
789
775
  </div>
790
- <p id="dep-wh-error" class="dep-error" hidden></p>
776
+ <p id="dep-error" class="dep-error" hidden></p>
791
777
  <div id="dep-wh-inbound-row" class="dep-wh-inbound-reveal" hidden>
792
778
  <div class="dep-wh-inbound-header">
793
779
  <span class="dep-wh-inbound-label">Inbound URL</span>
@@ -836,7 +822,8 @@
836
822
  <span class="cp-hint cp-hint--right"><kbd id="cp-rerun-kbd">&#x2318;&#x21E7;R</kbd> re-run last</span>
837
823
  </div>
838
824
  <div class="cp-employee-footer">
839
- Running as: <span id="cp-employee-name"></span>
825
+ <span class="cp-agent-label">Run with:</span>
826
+ <span id="cp-agent-picker" class="cp-agent-pills"></span>
840
827
  </div>
841
828
  </div>
842
829
  </div>
@@ -15,8 +15,10 @@
15
15
  gap: 8px;
16
16
  }
17
17
  .review-card {
18
- background: rgba(255, 255, 255, 0.86);
19
- border: 1px solid rgba(31, 67, 125, 0.14);
18
+ /* #700: token-driven so the card follows light/dark. Was a hardcoded
19
+ near-white that made var(--text) unreadable in dark mode. */
20
+ background: var(--surface);
21
+ border: 1px solid var(--line);
20
22
  border-radius: 10px;
21
23
  padding: 10px 14px;
22
24
  display: flex;