fraim-hub 2.0.250 → 2.0.252

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.
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.certPaths = certPaths;
6
7
  exports.loadOrCreateCert = loadOrCreateCert;
7
8
  exports.trustCert = trustCert;
8
9
  const fs_1 = __importDefault(require("fs"));
@@ -40,27 +41,24 @@ async function loadOrCreateCert() {
40
41
  fs_1.default.mkdirSync(path_1.default.dirname(keyPath), { recursive: true });
41
42
  fs_1.default.writeFileSync(keyPath, pem.private, { mode: 0o600 });
42
43
  fs_1.default.writeFileSync(certPath, pem.cert, { mode: 0o644 });
43
- // NOTE: we deliberately do NOT install this into the OS trusted-root store here.
44
- // Desktop Word/PowerPoint reach the pane over plain HTTP loopback and need no cert.
45
- // Trusting a root CA triggers a Windows security popup on every add/remove and is
46
- // invasive, so it is reserved for explicit Word-Online opt-in via trustCert() below.
44
+ // The caller decides when to trust this certificate. Word Online requires the
45
+ // HTTPS loopback certificate to be trusted before it can render the task pane.
47
46
  return { key: pem.private, cert: pem.cert };
48
47
  }
49
48
  /**
50
49
  * Installs the cert as a trusted root CA in the user's OS certificate store.
51
50
  * ONLY call this for explicit Word-Online support — it prompts a Windows security
52
- * dialog. It is idempotent: it no-ops if a matching localhost cert is already
53
- * trusted, so it never produces the delete/add popup churn.
54
- * Windows: certutil -addstore -user Root (skipped if already present)
51
+ * dialog. It never deletes certs; importing the same cert is idempotent in the
52
+ * user root store and also repairs stale installs when the cached cert changed.
53
+ * Windows: certutil -addstore -user Root
55
54
  * macOS: security add-trusted-cert -r trustRoot -k login.keychain
56
55
  */
57
56
  function trustCert(certPath) {
58
57
  if (process.platform === 'win32') {
59
- // Skip if a localhost cert is already trusted avoids the confirmation popup.
60
- const check = (0, child_process_1.spawnSync)('certutil', ['-user', '-store', 'Root', 'localhost'], { encoding: 'utf8' });
61
- if (check.status === 0 && /localhost/i.test(check.stdout))
62
- return;
63
- (0, child_process_1.spawnSync)('certutil', ['-addstore', '-user', 'Root', certPath], { stdio: 'ignore' });
58
+ const result = (0, child_process_1.spawnSync)('certutil', ['-addstore', '-user', 'Root', certPath], { encoding: 'utf8' });
59
+ if (result.status !== 0) {
60
+ console.warn('[fraim] could not trust localhost certificate:', result.stderr || result.stdout);
61
+ }
64
62
  return;
65
63
  }
66
64
  if (process.platform === 'darwin') {
@@ -326,6 +326,12 @@ async function launchDesktopShell(options) {
326
326
  // Generate (or load cached) self-signed cert for HTTPS.
327
327
  // Fast on subsequent launches (file read); ~200ms on first launch (key gen).
328
328
  const certBundle = await (0, cert_store_1.loadOrCreateCert)();
329
+ try {
330
+ (0, cert_store_1.trustCert)((0, cert_store_1.certPaths)().certPath);
331
+ }
332
+ catch (err) {
333
+ console.warn('[fraim] could not trust localhost certificate:', err);
334
+ }
329
335
  server = new server_1.AiHubServer({
330
336
  ...(options.projectPath ? { projectPath: options.projectPath } : {}),
331
337
  // Issue #701: no local DB. Persona/manager-team state resolves through the hosted
@@ -63,7 +63,7 @@ function generatedManifestPath(entry, userDataDir) {
63
63
  return path_1.default.join(userDataDir, 'office-manifests', entry.guid, 'manifest.xml');
64
64
  }
65
65
  function manifestXmlForPort(xml, httpPort) {
66
- return xml.replace(/https?:\/\/(?:localhost|127\.0\.0\.1):43091/g, `http://127.0.0.1:${httpPort}`);
66
+ return xml.replace(/https?:\/\/(?:localhost|127\.0\.0\.1):(43091|43092)/g, `http://127.0.0.1:${httpPort}`);
67
67
  }
68
68
  function shouldSkipSideloadForFlag(existingFlag, expectedFlag, sideloaded) {
69
69
  return existingFlag?.trim() === expectedFlag && sideloaded;
@@ -9,8 +9,8 @@
9
9
  <DefaultLocale>en-US</DefaultLocale>
10
10
  <DisplayName DefaultValue="FRAIM"/>
11
11
  <Description DefaultValue="AI Hub for your document - legal, product, engineering, and more in one task pane."/>
12
- <IconUrl DefaultValue="http://127.0.0.1:43091/word-taskpane/icon-64.png"/>
13
- <HighResolutionIconUrl DefaultValue="http://127.0.0.1:43091/word-taskpane/icon-64.png"/>
12
+ <IconUrl DefaultValue="https://localhost:43092/word-taskpane/icon-64.png"/>
13
+ <HighResolutionIconUrl DefaultValue="https://localhost:43092/word-taskpane/icon-64.png"/>
14
14
  <AppDomains>
15
15
  <AppDomain>https://appsforoffice.microsoft.com</AppDomain>
16
16
  </AppDomains>
@@ -26,7 +26,7 @@
26
26
  </Requirements>
27
27
 
28
28
  <DefaultSettings>
29
- <SourceLocation DefaultValue="http://127.0.0.1:43091/word-taskpane/"/>
29
+ <SourceLocation DefaultValue="https://localhost:43092/word-taskpane/"/>
30
30
  </DefaultSettings>
31
31
 
32
32
  <Permissions>ReadWriteDocument</Permissions>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim-hub",
3
- "version": "2.0.250",
3
+ "version": "2.0.252",
4
4
  "description": "FRAIM Hub local companion package.",
5
5
  "bin": {
6
6
  "fraim-hub": "bin/fraim-hub.js",
@@ -163,7 +163,7 @@
163
163
  "electron": "^41.2.2",
164
164
  "electron-updater": "^6.8.9",
165
165
  "express": "^5.2.1",
166
- "fraim": "2.0.250",
166
+ "fraim": "2.0.252",
167
167
  "mongodb": "^7.0.0",
168
168
  "node-cron": "4.2.1",
169
169
  "node-edge-tts": "^1.2.10",
@@ -500,7 +500,7 @@
500
500
  </div>
501
501
  </div>
502
502
  <div class="coach-input">
503
- <textarea id="coach-text" placeholder="Tell the employee what to do next..."></textarea>
503
+ <textarea id="coach-text" aria-label="Coach the employee" placeholder="Tell the employee what to do next..."></textarea>
504
504
  <button class="send-button" type="button" id="send" disabled>↑</button>
505
505
  </div>
506
506
  <div class="coach-note" id="coach-note"></div>
@@ -529,12 +529,40 @@ function renderWordContextInModal() {
529
529
  body.textContent = buildWordContextBlock(wc);
530
530
  }
531
531
 
532
- function isTaskPaneSurface() {
533
- return document.body.dataset.surface === 'task-pane' || document.body.dataset.surface === 'extension';
534
- }
535
-
536
- function taskPaneProjectEntries() {
537
- const byPath = new Map();
532
+ function isTaskPaneSurface() {
533
+ return document.body.dataset.surface === 'task-pane' || document.body.dataset.surface === 'extension';
534
+ }
535
+
536
+ function syncTaskPaneSurfaceMode() {
537
+ if (!isTaskPaneSurface()) return;
538
+ const conv = typeof activeConversation === 'function' ? activeConversation() : null;
539
+ const hasActiveConversation = !!conv;
540
+ document.body.classList.toggle('task-pane-run-active', hasActiveConversation);
541
+ if (!hasActiveConversation || typeof tf === 'undefined' || typeof tfSelectProjectView !== 'function') return;
542
+ const projectPath = (conv && conv.projectPath) || state.projectPath;
543
+ let project = typeof tfProjectForPath === 'function' ? tfProjectForPath(projectPath) : null;
544
+ if (!project && projectPath && typeof tfNormalizeProject === 'function' && typeof tfApplyUniqueProjectIds === 'function') {
545
+ tf.projects = tfApplyUniqueProjectIds([...(tf.projects || []), tfNormalizeProject({
546
+ name: friendlyProjectShortName(projectPath),
547
+ folderPath: projectPath,
548
+ }, projectPath)]);
549
+ project = typeof tfProjectForPath === 'function' ? tfProjectForPath(projectPath) : null;
550
+ }
551
+ const projectId = (project && project.id) || tf.activeProjectId;
552
+ if (!projectId) return;
553
+ if (tf.projectView === 'workspace' && tf.activeProjectId === projectId) {
554
+ const overview = document.getElementById('proj-overview');
555
+ const workspace = document.getElementById('proj-workspace');
556
+ if (overview) overview.hidden = true;
557
+ if (workspace) workspace.hidden = false;
558
+ return;
559
+ }
560
+ tfSelectProjectView('workspace', projectId).catch((error) =>
561
+ console.warn('[ai-hub] could not show task-pane run workspace:', error));
562
+ }
563
+
564
+ function taskPaneProjectEntries() {
565
+ const byPath = new Map();
538
566
  const add = (project) => {
539
567
  if (!project) return;
540
568
  const folderPath = project.folderPath || project.path || project.folder || '';
@@ -654,10 +682,11 @@ function ensureTaskPaneLauncher() {
654
682
  return root;
655
683
  }
656
684
 
657
- function renderTaskPaneLauncher() {
658
- if (!isTaskPaneSurface() || !state.bootstrap) return;
659
- ensureTaskPaneLauncher();
660
- const projects = taskPaneProjectEntries();
685
+ function renderTaskPaneLauncher() {
686
+ if (!isTaskPaneSurface() || !state.bootstrap) return;
687
+ syncTaskPaneSurfaceMode();
688
+ ensureTaskPaneLauncher();
689
+ const projects = taskPaneProjectEntries();
661
690
  const jobs = taskPaneJobEntries();
662
691
  const agents = hubConfiguredAgents().filter((agent) => agent.available !== false && agent.enabled !== false);
663
692
  const projectSelect = document.getElementById('task-pane-project-select');
@@ -3152,9 +3181,10 @@ function handleConversationFocusEscape(e) {
3152
3181
  closeConversationFocus();
3153
3182
  }
3154
3183
 
3155
- function renderActive() {
3156
- const conv = activeConversation();
3157
- if (typeof tfApplyWorkspaceMode === 'function') tfApplyWorkspaceMode();
3184
+ function renderActive() {
3185
+ const conv = activeConversation();
3186
+ if (typeof syncTaskPaneSurfaceMode === 'function') syncTaskPaneSurfaceMode();
3187
+ if (typeof tfApplyWorkspaceMode === 'function') tfApplyWorkspaceMode();
3158
3188
  if (!conv) {
3159
3189
  closeConversationFocus();
3160
3190
  els['empty'].hidden = false;
@@ -2866,24 +2866,52 @@ button.small { padding: 4px 10px; font-size: 12px; }
2866
2866
  body:is([data-surface="task-pane"],[data-surface="extension"]). No media queries — this is a surface
2867
2867
  flag, not a viewport breakpoint. */
2868
2868
 
2869
- body:is([data-surface="task-pane"],[data-surface="extension"]) {
2870
- font-size: 13px;
2871
- overflow: auto;
2872
- }
2873
- body:is([data-surface="task-pane"],[data-surface="extension"]) .hub-tabs,
2874
- body:is([data-surface="task-pane"],[data-surface="extension"]) .hub-area,
2875
- body:is([data-surface="task-pane"],[data-surface="extension"]) .proj-tabs {
2876
- display: none !important;
2877
- }
2878
- .task-pane-launcher { display: none; }
2879
- body:is([data-surface="task-pane"],[data-surface="extension"]) .task-pane-launcher {
2880
- display: flex;
2881
- flex-direction: column;
2882
- gap: 10px;
2883
- padding: 12px;
2884
- border-bottom: 1px solid var(--border, rgba(0,0,0,0.07));
2885
- background: var(--surface, #fff);
2886
- }
2869
+ body:is([data-surface="task-pane"],[data-surface="extension"]) {
2870
+ font-size: 13px;
2871
+ overflow: auto;
2872
+ }
2873
+ body:is([data-surface="task-pane"],[data-surface="extension"]) .hub-tabs,
2874
+ body:is([data-surface="task-pane"],[data-surface="extension"]):not(.task-pane-run-active) .hub-area,
2875
+ body:is([data-surface="task-pane"],[data-surface="extension"]) .proj-tabs {
2876
+ display: none !important;
2877
+ }
2878
+ .task-pane-launcher { display: none; }
2879
+ body:is([data-surface="task-pane"],[data-surface="extension"]):not(.task-pane-run-active) .task-pane-launcher {
2880
+ display: flex;
2881
+ flex-direction: column;
2882
+ gap: 10px;
2883
+ padding: 12px;
2884
+ border-bottom: 1px solid var(--border, rgba(0,0,0,0.07));
2885
+ background: var(--surface, #fff);
2886
+ }
2887
+ body:is([data-surface="task-pane"],[data-surface="extension"]).task-pane-run-active #area-projects {
2888
+ display: flex !important;
2889
+ min-height: 100vh;
2890
+ }
2891
+ body:is([data-surface="task-pane"],[data-surface="extension"]).task-pane-run-active #proj-workspace {
2892
+ display: flex;
2893
+ flex: 1 1 auto;
2894
+ min-width: 0;
2895
+ overflow: visible;
2896
+ }
2897
+ body:is([data-surface="task-pane"],[data-surface="extension"]).task-pane-run-active .tree-sidebar,
2898
+ body:is([data-surface="task-pane"],[data-surface="extension"]).task-pane-run-active .tree-collapse-btn,
2899
+ body:is([data-surface="task-pane"],[data-surface="extension"]).task-pane-run-active .tree-resizer {
2900
+ display: none !important;
2901
+ }
2902
+ body:is([data-surface="task-pane"],[data-surface="extension"]).task-pane-run-active .workspace-conv {
2903
+ width: 100%;
2904
+ min-width: 0;
2905
+ }
2906
+ body:is([data-surface="task-pane"],[data-surface="extension"]).task-pane-run-active .workspace-conv .layout {
2907
+ display: flex;
2908
+ flex-direction: column;
2909
+ min-width: 0;
2910
+ overflow: visible;
2911
+ }
2912
+ body:is([data-surface="task-pane"],[data-surface="extension"]).task-pane-run-active .word-context-bar {
2913
+ flex: 0 0 auto;
2914
+ }
2887
2915
  .tp-launcher-header {
2888
2916
  display: flex;
2889
2917
  flex-direction: column;