fraim-hub 2.0.350 → 2.0.351
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.
|
@@ -16,7 +16,6 @@ const auto_mcp_setup_1 = require("../cli/setup/auto-mcp-setup");
|
|
|
16
16
|
const fraim_mcp_latest_launcher_1 = require("../cli/mcp/fraim-mcp-latest-launcher");
|
|
17
17
|
const managed_node_runtime_1 = require("../cli/utils/managed-node-runtime");
|
|
18
18
|
const managed_agent_paths_1 = require("../cli/utils/managed-agent-paths");
|
|
19
|
-
const hub_app_materializer_1 = require("./hub-app-materializer");
|
|
20
19
|
function isDesktopRuntimeComplete() {
|
|
21
20
|
if (![(0, fraim_mcp_latest_launcher_1.getFraimCliShimPath)(), (0, fraim_mcp_latest_launcher_1.getFraimMcpShimPath)(), (0, fraim_mcp_latest_launcher_1.getFraimNpxShimPath)(), (0, fraim_mcp_latest_launcher_1.getFraimMcpLatestLauncherPath)()]
|
|
22
21
|
.every(file => fs_1.default.existsSync(file)))
|
|
@@ -68,19 +67,11 @@ async function repairInstallerIdeMcpEntries(options = {}) {
|
|
|
68
67
|
process.env.FRAIM_NON_INTERACTIVE = previousNonInteractive;
|
|
69
68
|
}
|
|
70
69
|
}
|
|
71
|
-
async function warmInstallerCliCache() {
|
|
72
|
-
const cliShim = (0, fraim_mcp_latest_launcher_1.getFraimCliShimPath)();
|
|
73
|
-
const result = await (0, hub_app_materializer_1.runNpmInstall)(cliShim, ['--version']);
|
|
74
|
-
if (result.status !== 0) {
|
|
75
|
-
throw new Error(`FRAIM CLI bootstrap exited with code ${result.status ?? 'unknown'}.`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
70
|
async function runDesktopRuntimeSetup(logger = console) {
|
|
79
71
|
await (0, managed_node_runtime_1.ensureManagedNpm)({
|
|
80
72
|
onProgress: (message) => logger.log(`[fraim-installer-setup] ${message}`),
|
|
81
73
|
});
|
|
82
74
|
(0, fraim_mcp_latest_launcher_1.ensureFraimMcpLatestLauncher)();
|
|
83
|
-
await warmInstallerCliCache();
|
|
84
75
|
}
|
|
85
76
|
async function runDesktopInstallerSetup(logger = console) {
|
|
86
77
|
await runDesktopRuntimeSetup(logger);
|
|
@@ -232,9 +232,26 @@ function runPackage(plan, cacheDir) {
|
|
|
232
232
|
const plan = resolvePackage();
|
|
233
233
|
const versionCacheDir = path.join(CACHE_ROOT, plan.packageName + '-' + plan.version.replace(/[^a-zA-Z0-9._-]/g, '_'));
|
|
234
234
|
pruneCaches(versionCacheDir, Date.now());
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* True when npx materialized the package into this cache. A non-zero exit from a package
|
|
238
|
+
* that DID install is the CLI's own result, not a cache problem, and must be returned verbatim.
|
|
239
|
+
*/
|
|
240
|
+
function packageMaterialized(cacheDir) {
|
|
241
|
+
try {
|
|
242
|
+
const npxDir = path.join(cacheDir, '_npx');
|
|
243
|
+
return fs.readdirSync(npxDir).some(function (entry) {
|
|
244
|
+
return fs.existsSync(path.join(npxDir, entry, 'node_modules', plan.packageName));
|
|
245
|
+
});
|
|
246
|
+
} catch (error) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
235
251
|
let status = runPackage(plan, versionCacheDir);
|
|
236
252
|
|
|
237
|
-
|
|
253
|
+
// Retry ONLY when npx could not materialize the package: the corrupt-cache case this exists for.
|
|
254
|
+
if (status !== 0 && process.platform === 'win32' && !packageMaterialized(versionCacheDir)) {
|
|
238
255
|
const retryCache = path.join(CACHE_ROOT, 'retry-' + Date.now() + '-' + process.pid);
|
|
239
256
|
console.error('[fraim-mcp-launcher] Package execution failed; retrying once with a fresh npm cache.');
|
|
240
257
|
status = runPackage(plan, retryCache);
|
|
@@ -70,6 +70,8 @@ const script_sync_utils_1 = require("../utils/script-sync-utils");
|
|
|
70
70
|
const USER_LEVEL_RUNTIME_DEPS = {
|
|
71
71
|
'node-edge-tts': '*', // used by scripts/author-audio.js
|
|
72
72
|
'adm-zip': '^0.6.0', // used by scripts/communication/*.js (DOCX review layer) — matches root/packages/* manifests
|
|
73
|
+
'fast-glob': '^3.3.3', // used by scripts/exec-with-timeout.ts (Issue #1839)
|
|
74
|
+
'tree-kill': '^1.2.2', // used by scripts/exec-with-timeout.ts (Issue #1839)
|
|
73
75
|
};
|
|
74
76
|
/**
|
|
75
77
|
* Ensure the user-level FRAIM directory structure exists.
|
|
@@ -47,7 +47,6 @@ const REPO_LEARNINGS_REL = (0, project_fraim_paths_1.getWorkspaceFraimDisplayPat
|
|
|
47
47
|
* would keep passing if this default ever changed.
|
|
48
48
|
*/
|
|
49
49
|
exports.DEFAULT_THRESHOLD = 3.0;
|
|
50
|
-
const AGING_HORIZON_DAYS = 7;
|
|
51
50
|
const MAX_ENTRIES_SCANNED = 200;
|
|
52
51
|
const L0_SLEEP_ON_LEARNINGS_PROMPT_MIN = 5;
|
|
53
52
|
// ── Single source of truth for the learning-entry format contract (#533) ──────
|
|
@@ -306,11 +305,10 @@ function getScoreThreshold(workspaceRoot) {
|
|
|
306
305
|
return exports.DEFAULT_THRESHOLD;
|
|
307
306
|
}
|
|
308
307
|
/**
|
|
309
|
-
* Effective score for an L1 learning entry.
|
|
310
|
-
*
|
|
308
|
+
* Effective score for an L1 learning entry. Score is display-only and does
|
|
309
|
+
* not gate delivery; the tier file is the authoritative delivery gate.
|
|
311
310
|
*
|
|
312
|
-
* @param now Optional override for "now"
|
|
313
|
-
* calculations). Defaults to the current wall clock.
|
|
311
|
+
* @param now Optional override for "now". Defaults to the current wall clock.
|
|
314
312
|
*/
|
|
315
313
|
function computeEffectiveScore(severity, lastSeenDate, recurrences, fileType, now = new Date()) {
|
|
316
314
|
const baseScore = severity === 'P-CRITICAL' ? 10 : severity === 'P-HIGH' ? 8 : severity === 'P-MED' ? 5 : 3;
|
|
@@ -330,72 +328,6 @@ function computeEffectiveScore(severity, lastSeenDate, recurrences, fileType, no
|
|
|
330
328
|
const recurrenceBoost = Math.log2(Math.max(1, recurrences) + 1);
|
|
331
329
|
return baseScore * decay * recurrenceBoost;
|
|
332
330
|
}
|
|
333
|
-
function scanMistakePatternFile(filePath, threshold, fileType = 'mistake-patterns') {
|
|
334
|
-
const empty = { active: 0, dormant: 0, agingRisk: 0 };
|
|
335
|
-
if (!(0, fs_1.existsSync)(filePath))
|
|
336
|
-
return empty;
|
|
337
|
-
let content;
|
|
338
|
-
try {
|
|
339
|
-
content = (0, fs_1.readFileSync)(filePath, 'utf8');
|
|
340
|
-
}
|
|
341
|
-
catch {
|
|
342
|
-
return empty;
|
|
343
|
-
}
|
|
344
|
-
const lines = content.split(/\r?\n/);
|
|
345
|
-
const now = new Date();
|
|
346
|
-
const horizon = new Date(now.getTime() + AGING_HORIZON_DAYS * 86_400_000);
|
|
347
|
-
let active = 0;
|
|
348
|
-
let dormant = 0;
|
|
349
|
-
let agingRisk = 0;
|
|
350
|
-
let scanned = 0;
|
|
351
|
-
let inEntry = false;
|
|
352
|
-
let severity = null;
|
|
353
|
-
let lastSeen = '';
|
|
354
|
-
let recurrences = 1;
|
|
355
|
-
const flush = () => {
|
|
356
|
-
if (!severity)
|
|
357
|
-
return;
|
|
358
|
-
scanned++;
|
|
359
|
-
const today = computeEffectiveScore(severity, lastSeen, recurrences, fileType, now);
|
|
360
|
-
if (today >= threshold) {
|
|
361
|
-
active++;
|
|
362
|
-
if (lastSeen) {
|
|
363
|
-
const future = computeEffectiveScore(severity, lastSeen, recurrences, fileType, horizon);
|
|
364
|
-
if (future < threshold)
|
|
365
|
-
agingRisk++;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
else {
|
|
369
|
-
dormant++;
|
|
370
|
-
}
|
|
371
|
-
};
|
|
372
|
-
for (const line of lines) {
|
|
373
|
-
if (scanned >= MAX_ENTRIES_SCANNED)
|
|
374
|
-
break;
|
|
375
|
-
const headerMatch = line.match((0, learning_entry_format_1.learningEntryHeadingRegex)());
|
|
376
|
-
if (headerMatch) {
|
|
377
|
-
flush();
|
|
378
|
-
inEntry = true;
|
|
379
|
-
severity = headerMatch[1];
|
|
380
|
-
lastSeen = '';
|
|
381
|
-
recurrences = 1;
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
if (!inEntry)
|
|
385
|
-
continue;
|
|
386
|
-
const lastSeenMatch = line.match(/^\*\*Last seen\*\*:\s*(.+)/);
|
|
387
|
-
if (lastSeenMatch) {
|
|
388
|
-
lastSeen = lastSeenMatch[1].trim();
|
|
389
|
-
continue;
|
|
390
|
-
}
|
|
391
|
-
const recurrenceMatch = line.match(/^\*\*Recurrences\*\*:\s*(\d+)/);
|
|
392
|
-
if (recurrenceMatch) {
|
|
393
|
-
recurrences = parseInt(recurrenceMatch[1], 10);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
flush();
|
|
397
|
-
return { active, dormant, agingRisk };
|
|
398
|
-
}
|
|
399
331
|
/** Historical counters are only read for the human library view. */
|
|
400
332
|
function resolveUsageStats() {
|
|
401
333
|
try {
|
|
@@ -597,10 +529,10 @@ function mergeLearningEntriesAcrossTiers(tiers) {
|
|
|
597
529
|
return tiers.flatMap(tier => tier.entries);
|
|
598
530
|
}
|
|
599
531
|
const CONTEXT_CATEGORIES = {
|
|
600
|
-
mistake: { ft: 'mistake-patterns',
|
|
601
|
-
pref: { ft: 'preferences',
|
|
602
|
-
coach: { ft: 'manager-coaching',
|
|
603
|
-
validated: { ft: 'validated-patterns',
|
|
532
|
+
mistake: { ft: 'mistake-patterns', label: '(all entries)' },
|
|
533
|
+
pref: { ft: 'preferences', label: '(all entries)' },
|
|
534
|
+
coach: { ft: 'manager-coaching', label: '(manager-facing; all entries)' },
|
|
535
|
+
validated: { ft: 'validated-patterns', label: '(all entries)' },
|
|
604
536
|
};
|
|
605
537
|
/** Resolve the files listed in injected context, in delivery order. */
|
|
606
538
|
function resolveContextLearningFiles(workspaceRoot, resolvedUserId, roots, forJob, domain) {
|
|
@@ -646,7 +578,6 @@ function resolveContextLearningFiles(workspaceRoot, resolvedUserId, roots, forJo
|
|
|
646
578
|
path: tier.path,
|
|
647
579
|
displayPath: tier.displayPath,
|
|
648
580
|
fileType: meta.ft,
|
|
649
|
-
gated: meta.gated,
|
|
650
581
|
label: meta.label,
|
|
651
582
|
isCoach: key === 'coach',
|
|
652
583
|
level: tier.level ?? fallbackLevel,
|
|
@@ -669,42 +600,14 @@ function resolveContextLearningFiles(workspaceRoot, resolvedUserId, roots, forJo
|
|
|
669
600
|
const l1 = collect({ global: l1GlobalCats, domain: l1DomainCats }, (ft, dom) => resolvePersonalLearningFileTiers(roots.repoLearningsBase, roots.managerCacheBase, roots.managerCacheDisplayBase, roots.globalPersonalBase, roots.globalPersonalDisplayBase, dom ? `${resolvedUserId}-${dom}-${ft}.md` : `${resolvedUserId}-${ft}.md`), 'manager');
|
|
670
601
|
return { l2, l1, multiTier, coachPresent };
|
|
671
602
|
}
|
|
672
|
-
/**
|
|
673
|
-
* Report how many entries have been demoted, without naming the file they live in.
|
|
674
|
-
*
|
|
675
|
-
* Issue #861 keeps demoted entries out of the injected context, and #1103 keeps it
|
|
676
|
-
* that way after trying the alternative. Naming the `.cold.md` path here was
|
|
677
|
-
* considered and reverted: the path states a family (`…-mistake-patterns.cold.md`)
|
|
678
|
-
* and not a subject, so an instruction to "read it if this job touches its
|
|
679
|
-
* subject" is one the agent has no basis to follow. It degrades to reading the
|
|
680
|
-
* file every job, which is the token cost #861 removed, or never, which makes the
|
|
681
|
-
* line noise.
|
|
682
|
-
*
|
|
683
|
-
* Recovery for a demoted entry is a synthesis-time concern, not a runtime one.
|
|
684
|
-
* `sleep-on-learnings` reads both tiers when deciding whether a signal is a
|
|
685
|
-
* recurrence, so an entry that becomes relevant again is found, updated and
|
|
686
|
-
* promoted there. See that job's gather-signals and analyze phases.
|
|
687
|
-
*/
|
|
688
|
-
function renderDemotedLine(listed, label) {
|
|
689
|
-
const total = listed.reduce((sum, f) => sum + f.dormant, 0);
|
|
690
|
-
if (total === 0)
|
|
691
|
-
return '';
|
|
692
|
-
return total === 1
|
|
693
|
-
? `Demoted: 1 ${label} pattern has gone quiet and is not listed above.\n`
|
|
694
|
-
: `Demoted: ${total} ${label} patterns have gone quiet and are not listed above.\n`;
|
|
695
|
-
}
|
|
696
603
|
function buildLearningContextSection(workspaceRoot, userId, forJob, domain) {
|
|
697
604
|
const roots = getLearningRoots(workspaceRoot);
|
|
698
605
|
const resolvedUserId = resolveLearningUserId(workspaceRoot, userId, roots);
|
|
699
|
-
const threshold = getScoreThreshold(workspaceRoot);
|
|
700
606
|
const resolved = resolveContextLearningFiles(workspaceRoot, resolvedUserId, roots, forJob, domain);
|
|
701
607
|
const multiTier = resolved.multiTier;
|
|
702
608
|
const toListed = (f) => ({
|
|
703
609
|
displayPath: f.displayPath,
|
|
704
610
|
label: f.label,
|
|
705
|
-
dormant: f.gated
|
|
706
|
-
? scanMistakePatternFile(f.path, threshold, f.fileType).dormant
|
|
707
|
-
: 0,
|
|
708
611
|
isCoach: f.isCoach,
|
|
709
612
|
});
|
|
710
613
|
const l2Files = resolved.l2.map(toListed);
|
|
@@ -726,14 +629,12 @@ function buildLearningContextSection(workspaceRoot, userId, forJob, domain) {
|
|
|
726
629
|
section += '### L2 - Org patterns\n';
|
|
727
630
|
for (const f of l2Files)
|
|
728
631
|
section += `\`${f.displayPath}\` ${f.label}\n`;
|
|
729
|
-
section += renderDemotedLine(l2Files, 'org');
|
|
730
632
|
section += '\n';
|
|
731
633
|
}
|
|
732
634
|
if (hasL1) {
|
|
733
635
|
section += '### L1 - Your patterns\n';
|
|
734
636
|
for (const f of l1Files)
|
|
735
637
|
section += `\`${f.displayPath}\` ${f.label}\n`;
|
|
736
|
-
section += renderDemotedLine(l1Files, 'personal');
|
|
737
638
|
section += '\n';
|
|
738
639
|
}
|
|
739
640
|
if (l0CoachingCount > 0 || l0RetroCount > 0) {
|
|
@@ -1409,13 +1310,11 @@ function listDomainLearningFiles(dir, prefix, fileType) {
|
|
|
1409
1310
|
}
|
|
1410
1311
|
// Parse the `## [P-…] Title` entries (and their body prose) out of one learning
|
|
1411
1312
|
// file. Score metadata (Last seen / Recurrences) is captured to compute the
|
|
1412
|
-
// effective decayed score
|
|
1413
|
-
//
|
|
1313
|
+
// effective decayed score for display; those bookkeeping lines are excluded from
|
|
1314
|
+
// the rendered body.
|
|
1414
1315
|
//
|
|
1415
|
-
//
|
|
1416
|
-
|
|
1417
|
-
// all entries are injected.
|
|
1418
|
-
function parseLearningEntries(filePath, displayPath, category, level, domain = 'global', tier, threshold = exports.DEFAULT_THRESHOLD, usageStats) {
|
|
1316
|
+
// `injected` reflects tier standing (hot = true, cold = false); score is display-only.
|
|
1317
|
+
function parseLearningEntries(filePath, displayPath, category, level, domain = 'global', tier, usageStats) {
|
|
1419
1318
|
if (!(0, fs_1.existsSync)(filePath))
|
|
1420
1319
|
return [];
|
|
1421
1320
|
let content;
|
|
@@ -1426,7 +1325,7 @@ function parseLearningEntries(filePath, displayPath, category, level, domain = '
|
|
|
1426
1325
|
return [];
|
|
1427
1326
|
}
|
|
1428
1327
|
const fileType = exports.CATEGORY_TO_FILETYPE[category];
|
|
1429
|
-
//
|
|
1328
|
+
// mistake-patterns and validated-patterns carry a display score; other categories do not.
|
|
1430
1329
|
const isScoreGated = fileType === 'mistake-patterns' || fileType === 'validated-patterns';
|
|
1431
1330
|
const out = [];
|
|
1432
1331
|
let current = null;
|
|
@@ -1443,12 +1342,11 @@ function parseLearningEntries(filePath, displayPath, category, level, domain = '
|
|
|
1443
1342
|
if (curLastSeen !== null) {
|
|
1444
1343
|
const s = computeEffectiveScore(current.severity, curLastSeen, curRecurrences, fileType, new Date());
|
|
1445
1344
|
current.score = s;
|
|
1446
|
-
current.injected =
|
|
1345
|
+
current.injected = tier !== 'cold';
|
|
1447
1346
|
}
|
|
1448
1347
|
else {
|
|
1449
1348
|
current.score = null;
|
|
1450
|
-
|
|
1451
|
-
current.injected = true;
|
|
1349
|
+
current.injected = tier !== 'cold';
|
|
1452
1350
|
}
|
|
1453
1351
|
}
|
|
1454
1352
|
else {
|
|
@@ -1620,7 +1518,6 @@ function readRejectedProposals(workspaceRoot, userId) {
|
|
|
1620
1518
|
}
|
|
1621
1519
|
function readPreservedLearnings(workspaceRoot, userId, scope, level = 'machine') {
|
|
1622
1520
|
const roots = getLearningRoots(workspaceRoot);
|
|
1623
|
-
const threshold = getScoreThreshold(workspaceRoot);
|
|
1624
1521
|
const usageStats = resolveUsageStats();
|
|
1625
1522
|
// Only the human library reads these observations. An unavailable store must
|
|
1626
1523
|
// fail this read, so a client can retain known history rather than invent zeroes.
|
|
@@ -1657,7 +1554,7 @@ function readPreservedLearnings(workspaceRoot, userId, scope, level = 'machine')
|
|
|
1657
1554
|
// ResolvedLearningTier uses 'manager' to mean machine-level personal; map it to 'machine'.
|
|
1658
1555
|
const tierLevel = (t) => t.level === 'manager' ? 'machine' : t.level;
|
|
1659
1556
|
const parseTiers = (tiers, cat, tierVal, domainVal) => mergeLearningEntriesAcrossTiers(tiers.map((t) => ({
|
|
1660
|
-
entries: parseLearningEntries(t.path, t.displayPath, cat, tierLevel(t), domainVal ?? 'global', tierVal,
|
|
1557
|
+
entries: parseLearningEntries(t.path, t.displayPath, cat, tierLevel(t), domainVal ?? 'global', tierVal, usageStats)
|
|
1661
1558
|
.map(entry => decorateAssessment(entry, t.path)),
|
|
1662
1559
|
})));
|
|
1663
1560
|
if (scope === 'org') {
|
|
@@ -1679,7 +1576,7 @@ function readPreservedLearnings(workspaceRoot, userId, scope, level = 'machine')
|
|
|
1679
1576
|
seenDomainFile.add(fileName);
|
|
1680
1577
|
const domainTiers = resolveOrgLearningFileTiers(roots.repoLearningsBase, fileName);
|
|
1681
1578
|
if (domainTiers.length === 0) {
|
|
1682
|
-
out.push(...parseLearningEntries((0, path_1.join)(base, fileName), `${REPO_LEARNINGS_REL}/${fileName}`, cat, 'org', domain, undefined,
|
|
1579
|
+
out.push(...parseLearningEntries((0, path_1.join)(base, fileName), `${REPO_LEARNINGS_REL}/${fileName}`, cat, 'org', domain, undefined, usageStats)
|
|
1683
1580
|
.map(entry => decorateAssessment(entry, (0, path_1.join)(base, fileName))));
|
|
1684
1581
|
}
|
|
1685
1582
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-hub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.351",
|
|
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",
|
|
@@ -246,7 +246,7 @@
|
|
|
246
246
|
"electron-updater": "^6.8.9",
|
|
247
247
|
"express": "^5.2.1",
|
|
248
248
|
"extract-zip": "^2.0.1",
|
|
249
|
-
"fraim": "2.0.
|
|
249
|
+
"fraim": "2.0.351",
|
|
250
250
|
"mongodb": "^7.0.0",
|
|
251
251
|
"node-cron": "4.2.1",
|
|
252
252
|
"node-edge-tts": "^1.2.10",
|