fraim-hub 2.0.305 → 2.0.306

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._internal = void 0;
6
7
  exports.certPaths = certPaths;
7
8
  exports.loadOrCreateCert = loadOrCreateCert;
8
9
  exports.trustCert = trustCert;
@@ -49,6 +50,15 @@ async function loadOrCreateCert() {
49
50
  // HTTPS loopback certificate to be trusted before it can render the task pane.
50
51
  return { key: pem.private, cert: pem.cert };
51
52
  }
53
+ /**
54
+ * Test seam (issue #1589): the real OS command invocation goes through this mutable,
55
+ * module-owned indirection point instead of a bare `spawnSync` call, so a test can swap in
56
+ * a fake implementation and assert on the exact command/arguments `trustCert()`/`untrustCert()`
57
+ * would invoke, without spawning a process, touching PATH, or mutating the real trust store.
58
+ * Swapping the export on Node's built-in `child_process` module itself is not reliable for
59
+ * this (confirmed empirically while fixing this issue) - this repo-owned seam is.
60
+ */
61
+ exports._internal = { spawnSync: child_process_1.spawnSync };
52
62
  /**
53
63
  * Installs the cert as a trusted root CA in the user's OS certificate store.
54
64
  * ONLY call this for explicit Word-Online support — it prompts a Windows security
@@ -59,7 +69,7 @@ async function loadOrCreateCert() {
59
69
  */
60
70
  function trustCert(certPath) {
61
71
  if (process.platform === 'win32') {
62
- const result = (0, child_process_1.spawnSync)('certutil', ['-addstore', '-user', 'Root', certPath], { encoding: 'utf8' });
72
+ const result = exports._internal.spawnSync('certutil', ['-addstore', '-user', 'Root', certPath], { encoding: 'utf8' });
63
73
  if (result.status !== 0) {
64
74
  console.warn('[fraim] could not trust localhost certificate:', result.stderr || result.stdout);
65
75
  }
@@ -67,17 +77,17 @@ function trustCert(certPath) {
67
77
  }
68
78
  if (process.platform === 'darwin') {
69
79
  const keychain = path_1.default.join(os_1.default.homedir(), 'Library', 'Keychains', 'login.keychain-db');
70
- (0, child_process_1.spawnSync)('security', ['add-trusted-cert', '-r', 'trustRoot', '-k', keychain, certPath], { stdio: 'ignore' });
80
+ exports._internal.spawnSync('security', ['add-trusted-cert', '-r', 'trustRoot', '-k', keychain, certPath], { stdio: 'ignore' });
71
81
  }
72
82
  }
73
83
  /** Removes a specific cert from the trust store, by fingerprint. Test-only helper. */
74
84
  function untrustCert(fingerprint) {
75
85
  if (process.platform === 'win32') {
76
- (0, child_process_1.spawnSync)('certutil', ['-delstore', '-user', 'Root', fingerprint], { encoding: 'utf8' });
86
+ exports._internal.spawnSync('certutil', ['-delstore', '-user', 'Root', fingerprint], { encoding: 'utf8' });
77
87
  return;
78
88
  }
79
89
  if (process.platform === 'darwin') {
80
- (0, child_process_1.spawnSync)('security', ['delete-certificate', '-Z', fingerprint], { stdio: 'ignore' });
90
+ exports._internal.spawnSync('security', ['delete-certificate', '-Z', fingerprint], { stdio: 'ignore' });
81
91
  }
82
92
  }
83
93
  /**
@@ -22,6 +22,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
22
22
  return (mod && mod.__esModule) ? mod : { "default": mod };
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports._internal = exports.WEF_DEVELOPER_KEY = void 0;
25
26
  exports.manifestXmlForPort = manifestXmlForPort;
26
27
  exports.shouldSkipSideloadForFlag = shouldSkipSideloadForFlag;
27
28
  exports.winDeveloperManifestSubkey = winDeveloperManifestSubkey;
@@ -32,7 +33,7 @@ const fs_1 = __importDefault(require("fs"));
32
33
  const path_1 = __importDefault(require("path"));
33
34
  const os_1 = __importDefault(require("os"));
34
35
  const child_process_1 = require("child_process");
35
- const WEF_DEVELOPER_KEY = 'HKCU\\SOFTWARE\\Microsoft\\Office\\16.0\\WEF\\Developer';
36
+ exports.WEF_DEVELOPER_KEY = 'HKCU\\SOFTWARE\\Microsoft\\Office\\16.0\\WEF\\Developer';
36
37
  const MANIFESTS = [
37
38
  {
38
39
  guid: 'd1090951-50cf-4cf2-9d12-b0f8541d265c',
@@ -93,7 +94,7 @@ function prepareManifestForSideload(entry, sourcePath, options) {
93
94
  // Windows registry helpers
94
95
  // ---------------------------------------------------------------------------
95
96
  function winDeveloperManifestSubkey(guid) {
96
- return `${WEF_DEVELOPER_KEY}\\{${guid}}`;
97
+ return `${exports.WEF_DEVELOPER_KEY}\\{${guid}}`;
97
98
  }
98
99
  function parseRegSzValue(stdout) {
99
100
  const line = stdout.split(/\r?\n/).find(l => l.includes('REG_SZ'));
@@ -103,7 +104,7 @@ function parseRegSzValue(stdout) {
103
104
  return line.slice(idx + 'REG_SZ'.length).trim() || null;
104
105
  }
105
106
  function winRegisteredRootValue(guid) {
106
- const r = (0, child_process_1.spawnSync)('reg', ['query', WEF_DEVELOPER_KEY, '/v', guid], { encoding: 'utf8' });
107
+ const r = (0, child_process_1.spawnSync)('reg', ['query', exports.WEF_DEVELOPER_KEY, '/v', guid], { encoding: 'utf8' });
107
108
  if (r.status !== 0 || !r.stdout.includes('REG_SZ'))
108
109
  return null;
109
110
  // Output line looks like: " <guid> REG_SZ C:\path\to\manifest.xml"
@@ -122,14 +123,21 @@ function winRegisteredValue(guid) {
122
123
  // but treat the root value as the effective discovery registration.
123
124
  return winRegisteredRootValue(guid) ?? winRegisteredDefaultValue(guid);
124
125
  }
126
+ /**
127
+ * Test seam (issue #1589): the registry-write invocations go through this mutable,
128
+ * module-owned indirection point instead of a bare `spawnSync` call, so a test can swap in a
129
+ * fake implementation and assert on the exact `reg` command/arguments `sideloadManifest()`
130
+ * would invoke, without spawning a process, touching PATH, or mutating the real registry.
131
+ */
132
+ exports._internal = { spawnSync: child_process_1.spawnSync };
125
133
  function winWriteRegisteredValue(guid, manifestPath) {
126
- const root = (0, child_process_1.spawnSync)('reg', [
127
- 'add', WEF_DEVELOPER_KEY,
134
+ const root = exports._internal.spawnSync('reg', [
135
+ 'add', exports.WEF_DEVELOPER_KEY,
128
136
  '/v', guid, '/t', 'REG_SZ', '/d', manifestPath, '/f',
129
137
  ], { encoding: 'utf8' });
130
138
  if (root.status !== 0)
131
139
  return { ok: false, reason: root.stderr || `reg add failed for ${guid}` };
132
- const r = (0, child_process_1.spawnSync)('reg', [
140
+ const r = exports._internal.spawnSync('reg', [
133
141
  'add', winDeveloperManifestSubkey(guid),
134
142
  '/ve', '/t', 'REG_SZ', '/d', manifestPath, '/f',
135
143
  ], { encoding: 'utf8' });
@@ -137,7 +145,7 @@ function winWriteRegisteredValue(guid, manifestPath) {
137
145
  return { ok: false, reason: r.stderr || `reg add failed for ${guid}` };
138
146
  // Remove an accidental unbraced subkey left by older debugger settings. This
139
147
  // is different from the root named value above, which Word needs.
140
- (0, child_process_1.spawnSync)('reg', ['delete', `${WEF_DEVELOPER_KEY}\\${guid}`, '/f'], { encoding: 'utf8' });
148
+ exports._internal.spawnSync('reg', ['delete', `${exports.WEF_DEVELOPER_KEY}\\${guid}`, '/f'], { encoding: 'utf8' });
141
149
  return { ok: true };
142
150
  }
143
151
  // ---------------------------------------------------------------------------
@@ -195,8 +203,8 @@ function sideloadManifest(projectPath, options = {}) {
195
203
  function removeSideload() {
196
204
  for (const entry of MANIFESTS) {
197
205
  if (process.platform === 'win32') {
198
- (0, child_process_1.spawnSync)('reg', ['delete', WEF_DEVELOPER_KEY, '/v', entry.guid, '/f'], { encoding: 'utf8' });
199
- (0, child_process_1.spawnSync)('reg', ['delete', `${WEF_DEVELOPER_KEY}\\${entry.guid}`, '/f'], { encoding: 'utf8' });
206
+ (0, child_process_1.spawnSync)('reg', ['delete', exports.WEF_DEVELOPER_KEY, '/v', entry.guid, '/f'], { encoding: 'utf8' });
207
+ (0, child_process_1.spawnSync)('reg', ['delete', `${exports.WEF_DEVELOPER_KEY}\\${entry.guid}`, '/f'], { encoding: 'utf8' });
200
208
  (0, child_process_1.spawnSync)('reg', ['delete', winDeveloperManifestSubkey(entry.guid), '/f'], { encoding: 'utf8' });
201
209
  }
202
210
  else if (process.platform === 'darwin') {
@@ -69,6 +69,7 @@ const script_sync_utils_1 = require("../utils/script-sync-utils");
69
69
  */
70
70
  const USER_LEVEL_RUNTIME_DEPS = {
71
71
  'node-edge-tts': '*', // used by scripts/author-audio.js
72
+ 'adm-zip': '^0.6.0', // used by scripts/communication/*.js (DOCX review layer) — matches root/packages/* manifests
72
73
  };
73
74
  /**
74
75
  * Ensure the user-level FRAIM directory structure exists.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim-hub",
3
- "version": "2.0.305",
3
+ "version": "2.0.306",
4
4
  "description": "FRAIM Hub local companion package.",
5
5
  "author": "Sid Mathur <sid.mathur@gmail.com>",
6
6
  "homepage": "https://github.com/mathursrus/FRAIM#readme",
@@ -211,7 +211,7 @@
211
211
  "electron-updater": "^6.8.9",
212
212
  "express": "^5.2.1",
213
213
  "extract-zip": "^2.0.1",
214
- "fraim": "2.0.305",
214
+ "fraim": "2.0.306",
215
215
  "mongodb": "^7.0.0",
216
216
  "node-cron": "4.2.1",
217
217
  "node-edge-tts": "^1.2.10",
@@ -13560,41 +13560,38 @@ function tfMostRecentMatch(convs, predicate) {
13560
13560
  return convs.filter(predicate).sort((a, b) => (b.lastUpdatedAt || 0) - (a.lastUpdatedAt || 0))[0] || null;
13561
13561
  }
13562
13562
 
13563
- // Issue #1124: only look in the @company bucket. Scanning all buckets caused project-scoped
13564
- // org-learning-synthesis runs to hijack the Company tab.
13565
- function tfActiveOrgConv() {
13566
- const orgJobs = new Set(['organization-onboarding', 'organizational-learning-synthesis']);
13567
- const convs = ((state.conversations || {})[COMPANY_CONV_KEY] || []).filter((c) => orgJobs.has(c.jobId));
13568
- // Prefer running > waiting (needs coaching) > most recently updated completed.
13563
+ // #1601: tfActiveMgrConv() (#1408) and tfActiveOrgConv() (#1124/#1601) both resolve
13564
+ // "the one most-active conversation for this scope" running > waiting > has real
13565
+ // content — differing only in which scope they select. Shared here instead of two
13566
+ // near-identical copies. convScope() reads an explicit scope/invokedArea tag rather
13567
+ // than jobId or bucket location, so a project-scoped run (no scope field, e.g. a
13568
+ // legacy manager-agreements or organizational-learning-synthesis record) always
13569
+ // resolves to 'project' and is correctly excluded here regardless of which bucket
13570
+ // it lives in — scanning all buckets by scope is safe for both callers.
13571
+ function tfActiveScopedConv(scope) {
13572
+ const convs = Object.values(state.conversations || {}).flat().filter((c) => c && convScope(c) === scope);
13569
13573
  return (
13570
13574
  tfMostRecentMatch(convs, (c) => c.status === 'running') ||
13571
13575
  tfMostRecentMatch(convs, (c) => c.status === 'waiting') ||
13572
- tfMostRecentMatch(convs, () => true) ||
13576
+ // #782/#1408/#1601: only a conversation with real content should replace the
13577
+ // area's info view. A message-less completed conv (e.g. a provisional freeform
13578
+ // run that emitted nothing) is a phantom, not a conversation to surface.
13579
+ tfMostRecentMatch(convs, (c) => (c.messages || []).length > 0) ||
13573
13580
  null
13574
13581
  );
13575
13582
  }
13576
13583
 
13584
+ // Issue #1124: return the company-scoped conversation to show in the Company area.
13585
+ function tfActiveOrgConv() {
13586
+ return tfActiveScopedConv('company');
13587
+ }
13588
+
13577
13589
  // #594 R2: return the manager-scoped conversation to show in Manager area.
13578
13590
  // #702/#1408: manager-tab runs are those invoked from the manager area (any persona job,
13579
- // including manager-agreements), decided by scope alone. A pre-scope-tracking legacy
13580
- // manager-agreements record with no scope field falls back to convScope()'s 'project'
13581
- // default and won't be picked here; it's still reachable, just not as this tab's
13582
- // single "most active" spotlight. An active manager-invoked run surfaces in the
13583
- // Manager conversation host (Coach panel included).
13591
+ // including manager-agreements), decided by scope alone. An active manager-invoked run
13592
+ // surfaces in the Manager conversation host (Coach panel included).
13584
13593
  function tfActiveMgrConv() {
13585
- // Issue #1408: no job-id exception — a manager-agreements run only counts here if it
13586
- // was genuinely invoked from the Manager tab (convScope(c) === 'manager').
13587
- const convs = Object.values(state.conversations || {}).flat().filter((c) => c && convScope(c) === 'manager');
13588
- return (
13589
- tfMostRecentMatch(convs, (c) => c.status === 'running') ||
13590
- tfMostRecentMatch(convs, (c) => c.status === 'waiting') ||
13591
- // #782: only a conversation with real content should replace the Manager info
13592
- // view (Context / Rules / Learnings). A message-less completed conv (e.g. a
13593
- // provisional __freeform__ run that emitted nothing) is a phantom, not a
13594
- // conversation to surface — ignoring it keeps the info view as the tab's home.
13595
- tfMostRecentMatch(convs, (c) => (c.messages || []).length > 0) ||
13596
- null
13597
- );
13594
+ return tfActiveScopedConv('manager');
13598
13595
  }
13599
13596
 
13600
13597
  // #1397: resolve one stable destination for an area. Polling may refresh the