fraim-hub 2.0.295 → 2.0.297

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.
@@ -35,11 +35,19 @@ const hub_runtime_file_1 = require("./hub-runtime-file");
35
35
  const project_fraim_paths_1 = require("../core/utils/project-fraim-paths");
36
36
  const version_utils_1 = require("../cli/utils/version-utils");
37
37
  const process_liveness_1 = require("./process-liveness");
38
+ // Issue #1415 (follow-up): confirmed live - a kill attempt can fail for a reason other than "the
39
+ // process is already gone." An orphan Hub started from an elevated (Administrator) context could
40
+ // not be terminated by this call running as a standard user - Windows refuses cross-privilege
41
+ // termination, taskkill exits non-zero with "Access is denied," and that failure used to vanish
42
+ // into this catch block with zero trace. There is nothing this function can do about that
43
+ // permission gap (escalating would mean a UAC prompt, defeating the "seamless" goal this whole
44
+ // fix exists for) - but the failure must be visible instead of silently indistinguishable from a
45
+ // successful kill, or a real remaining orphan reads as "the sweep just didn't run."
38
46
  function killPid(pid) {
39
47
  try {
40
48
  if (process.platform === 'win32') {
41
49
  // /T kills the entire process tree; /F forces termination without prompting.
42
- (0, child_process_1.execFileSync)('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' });
50
+ (0, child_process_1.execFileSync)('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'pipe' });
43
51
  }
44
52
  else {
45
53
  // tree-kill sends SIGTERM to the process group so child Electron helper processes
@@ -47,8 +55,11 @@ function killPid(pid) {
47
55
  (0, tree_kill_1.default)(pid, 'SIGTERM');
48
56
  }
49
57
  }
50
- catch {
51
- /* already gone */
58
+ catch (error) {
59
+ const detail = error instanceof Error && 'stderr' in error
60
+ ? String(error.stderr ?? error.message)
61
+ : String(error);
62
+ console.warn(`[fraim] could not kill pid ${pid} (may already be gone, or may lack permission - e.g. a process started elevated): ${detail.trim()}`);
52
63
  }
53
64
  }
54
65
  function isPortFree(port, timeoutMs = 500) {
@@ -3328,9 +3328,17 @@ class AiHubServer {
3328
3328
  conversationRecordFromRun(run) {
3329
3329
  const lastUpdatedAt = run.updatedAt || new Date().toISOString();
3330
3330
  const stages = deriveStages(run, run.projectPath);
3331
+ const rawScope = run.scope
3332
+ ?? run.invokedArea;
3333
+ const recordScope = rawScope === 'manager' || rawScope === 'company' ? rawScope : 'project';
3334
+ const recordProjectPath = recordScope === 'manager'
3335
+ ? conversation_store_1.MANAGER_SCOPE_KEY
3336
+ : recordScope === 'company'
3337
+ ? conversation_store_1.COMPANY_SCOPE_KEY
3338
+ : path_1.default.resolve(run.projectPath);
3331
3339
  const record = {
3332
3340
  id: run.conversationId || run.id,
3333
- projectPath: path_1.default.resolve(run.projectPath),
3341
+ projectPath: recordProjectPath,
3334
3342
  title: run.conversationTitle || run.jobTitle || run.jobId,
3335
3343
  jobId: run.jobId,
3336
3344
  jobTitle: run.jobTitle || run.jobId,
@@ -3392,9 +3400,7 @@ class AiHubServer {
3392
3400
  sourceTrigger: run.sourceTrigger,
3393
3401
  // Issue #708: carry the invocation scope so the record lands in (and is keyed to)
3394
3402
  // the right bucket. Falls back to the legacy client `invokedArea` when present.
3395
- scope: run.scope
3396
- ?? run.invokedArea
3397
- ?? 'project',
3403
+ scope: recordScope,
3398
3404
  run: {
3399
3405
  stages,
3400
3406
  currentPhase: run.currentPhase || null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim-hub",
3
- "version": "2.0.295",
3
+ "version": "2.0.297",
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.295",
214
+ "fraim": "2.0.297",
215
215
  "mongodb": "^7.0.0",
216
216
  "node-cron": "4.2.1",
217
217
  "node-edge-tts": "^1.2.10",
@@ -8515,6 +8515,12 @@ function convNeedsPolling(conv) {
8515
8515
  }
8516
8516
 
8517
8517
  function switchToConversation(id) {
8518
+ const conv = findConversation(id);
8519
+ const scope = convScope(conv);
8520
+ const targetArea = scope === 'manager' || scope === 'company' ? scope : 'projects';
8521
+ if (conv && tf.area !== targetArea) {
8522
+ tfShowArea(targetArea);
8523
+ }
8518
8524
  state.activeId = id;
8519
8525
  persistActiveConversationSelection(id);
8520
8526
  // Issue #1065: a find session belongs to one thread. srchOpenResult re-opens it with the search
@@ -8522,23 +8528,23 @@ function switchToConversation(id) {
8522
8528
  if (typeof convFindClose === 'function') convFindClose();
8523
8529
  // For Company/Manager: move the shared .page into this area's conv-host and
8524
8530
  // switch from the info view to the full coaching panel.
8525
- if (tf.area === 'company' || tf.area === 'manager') {
8526
- tfEnsurePageInArea(tf.area);
8527
- const host = document.getElementById(tf.area + '-conv-host');
8528
- const info = document.getElementById(tf.area + '-info-view');
8531
+ if (targetArea === 'company' || targetArea === 'manager') {
8532
+ tfEnsurePageInArea(targetArea);
8533
+ const host = document.getElementById(targetArea + '-conv-host');
8534
+ const info = document.getElementById(targetArea + '-info-view');
8529
8535
  if (host) host.hidden = false;
8530
8536
  if (info) info.hidden = true;
8531
8537
  // #1397: an area selection represents every explicit destination, not only
8532
8538
  // the Info view. Poll-driven renders must preserve this conversation until
8533
8539
  // the user chooses another destination or the conversation disappears.
8534
- if (tf.areaSelection) tf.areaSelection[tf.area] = id;
8540
+ if (tf.areaSelection) tf.areaSelection[targetArea] = id;
8535
8541
  }
8536
8542
  renderRail();
8537
8543
  renderActive();
8538
- hydrateConversationBody(state.projectPath, id).catch((error) =>
8544
+ hydrateConversationBody(conv ? convBucketKey(conv) : state.projectPath, id).catch((error) =>
8539
8545
  console.warn('Could not hydrate switched conversation body:', error));
8540
- const conv = activeConversation();
8541
- if (convNeedsPolling(conv)) {
8546
+ const active = activeConversation();
8547
+ if (convNeedsPolling(active)) {
8542
8548
  startPolling();
8543
8549
  } else if (state.pollHandle) {
8544
8550
  window.clearInterval(state.pollHandle);