fraim-hub 2.0.308 → 2.0.310

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.
@@ -630,6 +630,17 @@ function microEventFromToolResult(candidate) {
630
630
  const text = resultTextFromValue(candidate.result ?? candidate.output ?? candidate.content ?? candidate.message);
631
631
  return text ? [{ kind: 'mentor_reply', text }] : undefined;
632
632
  }
633
+ function microEventFromNonMentorToolCall(candidate) {
634
+ const rawName = readToolName(candidate);
635
+ const name = typeof rawName === 'string' ? rawName : null;
636
+ if (!name)
637
+ return undefined;
638
+ const rawArgs = candidate.input || candidate.arguments || candidate.parameters;
639
+ const args = normalizeToolArgs(rawArgs);
640
+ const argSummary = args ? JSON.stringify(args) : '';
641
+ const text = argSummary ? `${name}(${argSummary})` : name;
642
+ return { kind: 'tool_call', text };
643
+ }
633
644
  function readToolUseId(candidate) {
634
645
  return stringValue(candidate.id) || stringValue(candidate.tool_use_id) || stringValue(candidate.toolUseId);
635
646
  }
@@ -685,6 +696,12 @@ function parseMicroEventsSignal(line) {
685
696
  if (id)
686
697
  mentorToolUseIds.push(id);
687
698
  }
699
+ else {
700
+ // Non-seekMentoring tool calls: emit so the manager sees all agent activity.
701
+ const nonMentorCall = microEventFromNonMentorToolCall(content);
702
+ if (nonMentorCall)
703
+ events.push(nonMentorCall);
704
+ }
688
705
  }
689
706
  if (content.type === 'tool_result') {
690
707
  const reply = microEventFromToolResult(content);
@@ -698,6 +715,24 @@ function parseMicroEventsSignal(line) {
698
715
  mentorToolResults.push({ toolUseId, text });
699
716
  }
700
717
  }
718
+ // Thinking blocks are suppressed from the main conversation panel but belong
719
+ // in micro-manage, where hidden agent reasoning is the primary signal of interest.
720
+ if (content.type === 'thinking' && typeof content.thinking === 'string' && content.thinking.length > 0) {
721
+ events.push({ kind: 'thinking', text: content.thinking });
722
+ }
723
+ // Opt-out: any content type not already handled above is emitted so new agent
724
+ // capabilities are visible without requiring code changes. Exclusions: types already
725
+ // handled (tool_use, function_call, tool_result, thinking) and 'text' (shown in the
726
+ // main conversation panel, not the micro-manage panel).
727
+ if (content.type !== 'text' &&
728
+ content.type !== 'tool_use' &&
729
+ content.type !== 'function_call' &&
730
+ content.type !== 'tool_result' &&
731
+ content.type !== 'thinking') {
732
+ const typeStr = typeof content.type === 'string' ? content.type : 'unknown';
733
+ if (typeStr)
734
+ events.push({ kind: 'tool_call', text: typeStr });
735
+ }
701
736
  }
702
737
  return {
703
738
  ...(events.length > 0 ? { microEvents: events } : {}),
@@ -5814,7 +5814,11 @@ class AiHubServer {
5814
5814
  (0, hosts_1.createHubEvent)('system', switchEventText),
5815
5815
  ...(fallbackReason ? [(0, hosts_1.createHubEvent)('system', `Same-host resume fell back to handoff: ${fallbackReason}`)] : []),
5816
5816
  ],
5817
- microEvents: persistedMicroEventsForRun(conversation),
5817
+ microEvents: [
5818
+ ...persistedMicroEventsForRun(conversation),
5819
+ createHubMicroEvent('diagnostic', 'system', switchEventText),
5820
+ ...(fallbackReason ? [createHubMicroEvent('diagnostic', 'system', `Same-host resume fell back to handoff: ${fallbackReason}`)] : []),
5821
+ ],
5818
5822
  eventLogRefs: conversation.eventLogRefs || [],
5819
5823
  currentPhase: persistedRun?.currentPhase || null,
5820
5824
  phaseHistory: persistedRun?.phaseHistory || [],
@@ -5,7 +5,94 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.installManagedAgent = installManagedAgent;
7
7
  const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
8
9
  const managed_agent_paths_1 = require("./managed-agent-paths");
10
+ function errorMessage(error) {
11
+ return error instanceof Error ? error.message : 'Unknown error';
12
+ }
13
+ function isStaleNpmInstallError(error) {
14
+ const code = typeof error === 'object' && error !== null && 'code' in error
15
+ ? String(error.code || '')
16
+ : '';
17
+ const message = errorMessage(error).toLowerCase();
18
+ return code === 'EEXIST'
19
+ || message.includes('eexist')
20
+ || message.includes('already exists')
21
+ || message.includes('already in use');
22
+ }
23
+ function packagePathSegments(installPackage) {
24
+ let packageName = installPackage.trim();
25
+ if (!packageName)
26
+ return [];
27
+ if (packageName.startsWith('@')) {
28
+ const slashIndex = packageName.indexOf('/');
29
+ if (slashIndex === -1)
30
+ return [];
31
+ const versionAt = packageName.indexOf('@', slashIndex);
32
+ if (versionAt !== -1)
33
+ packageName = packageName.slice(0, versionAt);
34
+ }
35
+ else {
36
+ const versionAt = packageName.lastIndexOf('@');
37
+ if (versionAt > 0)
38
+ packageName = packageName.slice(0, versionAt);
39
+ }
40
+ const segments = packageName.split('/');
41
+ return segments.every((segment) => segment && segment !== '.' && segment !== '..' && !segment.includes('\\'))
42
+ ? segments
43
+ : [];
44
+ }
45
+ function isInsideDirectory(parentDir, candidate) {
46
+ const relative = path_1.default.relative(path_1.default.resolve(parentDir), path_1.default.resolve(candidate));
47
+ return !!relative && !relative.startsWith('..') && !path_1.default.isAbsolute(relative);
48
+ }
49
+ function managedShimNames(command) {
50
+ return process.platform === 'win32'
51
+ ? [command, `${command}.cmd`, `${command}.ps1`]
52
+ : [command];
53
+ }
54
+ function cleanupManagedInstallArtifacts(option, prefix) {
55
+ const resolvedPrefix = path_1.default.resolve(prefix);
56
+ const packageSegments = packagePathSegments(option.installPackage);
57
+ const candidates = new Set();
58
+ if (packageSegments.length > 0) {
59
+ candidates.add(path_1.default.join(resolvedPrefix, 'node_modules', ...packageSegments));
60
+ candidates.add(path_1.default.join(resolvedPrefix, 'lib', 'node_modules', ...packageSegments));
61
+ }
62
+ for (const shimName of managedShimNames(option.launchCommand)) {
63
+ candidates.add(path_1.default.join(resolvedPrefix, shimName));
64
+ candidates.add(path_1.default.join(resolvedPrefix, 'bin', shimName));
65
+ }
66
+ const removed = [];
67
+ for (const candidate of candidates) {
68
+ if (!isInsideDirectory(resolvedPrefix, candidate) || !fs_1.default.existsSync(candidate))
69
+ continue;
70
+ fs_1.default.rmSync(candidate, { recursive: true, force: true });
71
+ removed.push(candidate);
72
+ }
73
+ return removed;
74
+ }
75
+ async function runInstallWithStaleCleanupRetry(option, prefix, runInstall) {
76
+ try {
77
+ await runInstall();
78
+ return;
79
+ }
80
+ catch (error) {
81
+ if (!isStaleNpmInstallError(error))
82
+ throw error;
83
+ const removed = cleanupManagedInstallArtifacts(option, prefix);
84
+ try {
85
+ await runInstall();
86
+ }
87
+ catch (retryError) {
88
+ const cleanupSummary = removed.length > 0
89
+ ? ` Removed stale managed artifacts: ${removed.join(', ')}.`
90
+ : ' No matching stale managed artifacts were found to remove.';
91
+ throw new Error(`${option.label} install hit a stale npm artifact (${errorMessage(error)}).`
92
+ + `${cleanupSummary} Retry failed: ${errorMessage(retryError)}`);
93
+ }
94
+ }
95
+ }
9
96
  // Issue #1284/#1285 (Implementation Strategy §2): the exact "standard npm
10
97
  // global install, then fall back to FRAIM's managed prefix" sequence used to
11
98
  // be duplicated verbatim in `server.ts` (Hub's install-agent route) and
@@ -17,6 +104,7 @@ const managed_agent_paths_1 = require("./managed-agent-paths");
17
104
  // check before invoking this — it only covers the install-then-fallback path.
18
105
  async function installManagedAgent(option, systemPath, deps) {
19
106
  let standardInstallError = null;
107
+ const prefix = (0, managed_agent_paths_1.getPortableNodeBinPath)();
20
108
  try {
21
109
  await deps.runProcess('npm', ['install', '-g', option.installPackage], {
22
110
  PATH: systemPath,
@@ -40,13 +128,14 @@ async function installManagedAgent(option, systemPath, deps) {
40
128
  standardInstallError = `${option.label} standard install completed, but the CLI is not runnable from the user PATH.`;
41
129
  }
42
130
  catch (error) {
43
- standardInstallError = error instanceof Error ? error.message : 'Unknown error';
131
+ if (isStaleNpmInstallError(error))
132
+ cleanupManagedInstallArtifacts(option, prefix);
133
+ standardInstallError = errorMessage(error);
44
134
  }
45
135
  // Defect A fix: co-locate npm-global shims with the node.exe/npm.cmd that
46
136
  // actually runs the install, instead of the flat legacy directory.
47
- const prefix = (0, managed_agent_paths_1.getPortableNodeBinPath)();
48
137
  fs_1.default.mkdirSync(prefix, { recursive: true });
49
- await deps.runProcess('npm', ['install', '-g', option.installPackage], { npm_config_prefix: prefix });
138
+ await runInstallWithStaleCleanupRetry(option, prefix, () => deps.runProcess('npm', ['install', '-g', option.installPackage], { npm_config_prefix: prefix }).then(() => undefined));
50
139
  const ver = deps.commandVersion(option.launchCommand, (0, managed_agent_paths_1.getManagedAgentBinDirs)());
51
140
  if (!ver) {
52
141
  throw new Error(`${option.label} install completed, but the CLI is not runnable from FRAIM's managed PATH. Standard install failure: ${standardInstallError}`);
@@ -647,6 +647,7 @@ class FirstRunSessionService {
647
647
  async runNodeRow() {
648
648
  const row = this.getRow('node');
649
649
  if (this.embeddedDesktop) {
650
+ persistShellPath(this.embeddedDesktop);
650
651
  row.status = 'ok';
651
652
  row.verb = `${process.version} bundled with FRAIM`;
652
653
  this.persist();
@@ -656,6 +657,7 @@ class FirstRunSessionService {
656
657
  // If node is missing here something is severely wrong; fall through to error.
657
658
  const ver = commandVersion('node');
658
659
  if (ver) {
660
+ persistShellPath(this.embeddedDesktop);
659
661
  row.status = 'ok';
660
662
  row.verb = `${ver} installed`;
661
663
  this.persist();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim-hub",
3
- "version": "2.0.308",
3
+ "version": "2.0.310",
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.308",
214
+ "fraim": "2.0.310",
215
215
  "mongodb": "^7.0.0",
216
216
  "node-cron": "4.2.1",
217
217
  "node-edge-tts": "^1.2.10",
@@ -3632,7 +3632,10 @@ function formatMicroAuditKind(event) {
3632
3632
  if (kind === 'manager_input') return 'manager';
3633
3633
  if (kind === 'mentor_call') return 'seekMentoring call';
3634
3634
  if (kind === 'mentor_reply') return 'mentor reply';
3635
+ if (kind === 'diagnostic' && event && event.channel === 'system') return 'system';
3635
3636
  if (kind === 'diagnostic') return 'diagnostic';
3637
+ if (kind === 'thinking') return 'thinking';
3638
+ if (kind === 'tool_call') return 'tool call';
3636
3639
  return event && event.channel ? event.channel : 'system';
3637
3640
  }
3638
3641