fraim-hub 2.0.292 → 2.0.294

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.
@@ -65,6 +65,20 @@ function parseArgs(argv) {
65
65
  }
66
66
  return { projectPath, preferredPort, runtimeId };
67
67
  }
68
+ // Issue #1415 (follow-up): the orphan sweep inside reconcileRunningHub hits real system ports
69
+ // directly (not scoped to FRAIM_USER_DIR), so a test proving it fires must not point it at the
70
+ // real 43091-43200 range - that could kill whatever Hub is genuinely running on the machine the
71
+ // test happens to execute on. Not used in any real launch path; mirrors the existing
72
+ // FRAIM_HUB_SKIP_MATERIALIZE / FRAIM_INSTALLER_LIFECYCLE_TEST kill-switch pattern.
73
+ function resolveOrphanScanPortRangeOverride() {
74
+ const raw = process.env.FRAIM_HUB_ORPHAN_SCAN_PORT_RANGE;
75
+ if (!raw)
76
+ return hub_instance_reconciliation_1.DEFAULT_HUB_ORPHAN_SCAN_PORT_RANGE;
77
+ const [start, end] = raw.split('-').map(Number);
78
+ if (!Number.isFinite(start) || !Number.isFinite(end) || start > end)
79
+ return hub_instance_reconciliation_1.DEFAULT_HUB_ORPHAN_SCAN_PORT_RANGE;
80
+ return { start, end };
81
+ }
68
82
  function applyUserDataOverride() {
69
83
  const userDataDir = process.env.FRAIM_AI_HUB_USER_DATA_DIR;
70
84
  if (!userDataDir)
@@ -570,7 +584,7 @@ async function bootstrap() {
570
584
  // regardless of FRAIM_USER_DIR isolation, so a test double (FAKE_HOST=1) skips it for the same
571
585
  // reason it skips the lock — it must never disturb a real Hub already running on this machine.
572
586
  if (!skipSingleInstance) {
573
- await (0, hub_instance_reconciliation_1.reconcileRunningHub)({ restart: true, keepRunning: false }, options.runtimeId);
587
+ await (0, hub_instance_reconciliation_1.reconcileRunningHub)({ restart: true, keepRunning: false }, options.runtimeId, resolveOrphanScanPortRangeOverride());
574
588
  }
575
589
  // macOS reads the dock icon from the app bundle once packaged, but an
576
590
  // unpackaged `npm run hub:desktop` / `npx fraim-hub` run shows Electron's
@@ -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.DEFAULT_HUB_ORPHAN_SCAN_PORT_RANGE = void 0;
6
7
  exports.killPid = killPid;
7
8
  exports.isPortFree = isPortFree;
8
9
  exports.waitForPortFree = waitForPortFree;
@@ -109,10 +110,13 @@ function fetchHubPid(port) {
109
110
  req.on('timeout', () => { req.destroy(); resolve(null); });
110
111
  });
111
112
  }
112
- async function scanAndKillOrphanHubs(excludePid) {
113
- const HUB_PORT_SCAN_START = 43091;
114
- const HUB_PORT_SCAN_END = 43200;
115
- for (let port = HUB_PORT_SCAN_START; port <= HUB_PORT_SCAN_END; port++) {
113
+ exports.DEFAULT_HUB_ORPHAN_SCAN_PORT_RANGE = { start: 43091, end: 43200 };
114
+ // `portRange` is injectable (default: the real Hub port range) so a test can point this at an
115
+ // isolated range instead - the scan hits real system ports directly, independent of any
116
+ // FRAIM_USER_DIR isolation, so exercising it against the real range in a test would risk killing
117
+ // whatever Hub is genuinely running on this machine at the time.
118
+ async function scanAndKillOrphanHubs(excludePid, portRange = exports.DEFAULT_HUB_ORPHAN_SCAN_PORT_RANGE) {
119
+ for (let port = portRange.start; port <= portRange.end; port++) {
116
120
  const version = await fetchRunningHubVersion(port);
117
121
  if (!version)
118
122
  continue; // port not a Hub
@@ -127,8 +131,8 @@ async function scanAndKillOrphanHubs(excludePid) {
127
131
  // The one entry point both launch paths call before starting their own server: reads
128
132
  // hub-runtime.json for `runtimeId`, confirms the recorded pid is actually alive and answering
129
133
  // (not just present in a stale file), and - when the decision is `replace` - kills it, waits for
130
- // its port to free, then sweeps for any unregistered orphan on the standard Hub port range.
131
- async function reconcileRunningHub(flags, runtimeId = 'hub') {
134
+ // its port to free. The orphan sweep (#921) then runs independently of that decision - see below.
135
+ async function reconcileRunningHub(flags, runtimeId = 'hub', orphanScanPortRange = exports.DEFAULT_HUB_ORPHAN_SCAN_PORT_RANGE) {
132
136
  const running = (0, hub_runtime_file_1.readHubRuntimeFile)((0, project_fraim_paths_1.getUserFraimDirPath)(), runtimeId);
133
137
  const confirmedVersion = running ? await fetchRunningHubVersion(running.port) : null;
134
138
  const live = !!(running && confirmedVersion && (0, process_liveness_1.isPidAlive)(running.pid));
@@ -140,17 +144,27 @@ async function reconcileRunningHub(flags, runtimeId = 'hub') {
140
144
  restart: flags.restart,
141
145
  noRestart: flags.keepRunning,
142
146
  });
147
+ let replacedPid = null;
143
148
  if (decision.action === 'replace' && effective) {
144
149
  console.log(`Replacing running Hub (v${effective.version}, pid ${effective.pid}) - ${decision.reason}`);
145
150
  killPid(effective.pid);
146
151
  await waitForPortFree(effective.port);
147
- // #921: after killing the registered pid, scan for orphan Hub processes that were
148
- // never registered in hub-runtime.json (e.g. older versions started via bare npx).
149
- if (runtimeId === 'hub') {
150
- await scanAndKillOrphanHubs(effective.pid);
151
- }
152
+ replacedPid = effective.pid;
152
153
  }
153
154
  else if (decision.action === 'focus-existing' && effective) {
154
155
  console.log(`A Hub (v${effective.version}) is already running - focusing it. Use --restart to replace it.`);
156
+ return;
157
+ }
158
+ // Issue #1415 (follow-up): confirmed live - a Hub started via a different launch path (so it
159
+ // was never registered in hub-runtime.json at all, e.g. an older `npx fraim-hub` build) is only
160
+ // ever caught here. The bug this closes: the *registered* Hub can perfectly well have already
161
+ // quit on its own (a clean shutdown, or - observed live - electron-updater's own native update
162
+ // silently restarting it) by the time this reconciliation runs. That collapses the decision to
163
+ // 'launch-fresh', and the sweep used to live *inside* the 'replace' branch above, so a real,
164
+ // still-running orphan on the standard port range was never even looked for. The sweep must run
165
+ // whenever there is no live-and-focusable Hub for this runtimeId, not only when one was found
166
+ // and replaced - "no other launch path registered a Hub" is not evidence "no Hub is running."
167
+ if (runtimeId === 'hub') {
168
+ await scanAndKillOrphanHubs(replacedPid, orphanScanPortRange);
155
169
  }
156
170
  }
@@ -99,7 +99,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
99
99
  personaKey: 'cela',
100
100
  bundleId: 'persona-cela-core',
101
101
  catalogMetadata: buildCatalogMetadata('cela', ['contract-review-analysis', 'nda-creation', 'saas-contract-package-creation']),
102
- protectedJobs: ['contract-review-analysis', 'nda-creation', 'saas-contract-package-creation', 'trademark-registration-management', 'provisional-patent-application-creation', 'opensign-cloud-esign-dispatch', 'w9-creation', 'entity-type-selection', 'state-incorporation-filing', 'business-tax-registration', 'ein-application', 'cap-table-construction', 'founder-and-equity-agreements', 'ip-assignment-agreement-creation', 'employment-structure-decision'],
102
+ protectedJobs: ['contract-review-analysis', 'nda-creation', 'saas-contract-package-creation', 'trademark-registration-management', 'provisional-patent-application-creation', 'opensign-cloud-esign-dispatch', 'w9-creation', 'entity-type-selection', 'state-incorporation-filing', 'business-annual-report-filing', 'business-tax-registration', 'ein-application', 'cap-table-construction', 'founder-and-equity-agreements', 'ip-assignment-agreement-creation', 'employment-structure-decision'],
103
103
  protectedAliases: ['legal', 'counsel'],
104
104
  defaultHireMode: 'job',
105
105
  lockCopy: 'Hire CELiA to unlock legal workflow support for this request.'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim-hub",
3
- "version": "2.0.292",
3
+ "version": "2.0.294",
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.292",
214
+ "fraim": "2.0.294",
215
215
  "mongodb": "^7.0.0",
216
216
  "node-cron": "4.2.1",
217
217
  "node-edge-tts": "^1.2.10",