fraim-framework 2.0.160 → 2.0.162
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.
- package/dist/src/ai-hub/conversation-store.js +164 -0
- package/dist/src/ai-hub/desktop-main.js +34 -12
- package/dist/src/ai-hub/hosts.js +383 -27
- package/dist/src/ai-hub/managed-browser.js +269 -0
- package/dist/src/ai-hub/manager-turns.js +13 -0
- package/dist/src/ai-hub/office-sideload.js +21 -3
- package/dist/src/ai-hub/preferences.js +10 -1
- package/dist/src/ai-hub/server.js +1243 -65
- package/dist/src/cli/commands/init-project.js +7 -1
- package/dist/src/cli/utils/agent-adapters.js +1 -1
- package/dist/src/core/fraim-config-schema.generated.js +50 -13
- package/dist/src/core/quality-evidence.js +4 -1
- package/dist/src/local-mcp-server/agent-token-prices.js +23 -0
- package/dist/src/local-mcp-server/learning-context-builder.js +438 -2
- package/dist/src/local-mcp-server/stdio-server.js +73 -15
- package/package.json +5 -4
- package/public/ai-hub/index.html +456 -7
- package/public/ai-hub/powerpoint-taskpane/index.html +2 -1
- package/public/ai-hub/review.css +354 -0
- package/public/ai-hub/script.js +5945 -1279
- package/public/ai-hub/styles.css +1805 -16
|
@@ -472,6 +472,33 @@ class FraimLocalMCPServer {
|
|
|
472
472
|
}
|
|
473
473
|
return '';
|
|
474
474
|
}
|
|
475
|
+
// #533: persist the authenticated user email into ~/.fraim/preferences.json so
|
|
476
|
+
// the AI Hub (a separate process) can resolve personal learnings + show the real
|
|
477
|
+
// profile. Merge-and-write; never throw out of the connect path.
|
|
478
|
+
persistUserEmail(userEmail) {
|
|
479
|
+
try {
|
|
480
|
+
const fs = require('fs');
|
|
481
|
+
const path = require('path');
|
|
482
|
+
const dir = (0, project_fraim_paths_1.getUserFraimDirPath)();
|
|
483
|
+
const prefsPath = path.join(dir, 'preferences.json');
|
|
484
|
+
let prefs = {};
|
|
485
|
+
try {
|
|
486
|
+
prefs = JSON.parse(fs.readFileSync(prefsPath, 'utf8'));
|
|
487
|
+
}
|
|
488
|
+
catch {
|
|
489
|
+
prefs = {};
|
|
490
|
+
}
|
|
491
|
+
if (prefs.userEmail === userEmail)
|
|
492
|
+
return; // already current
|
|
493
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
494
|
+
prefs.userEmail = userEmail;
|
|
495
|
+
(0, fs_1.writeFileSync)(prefsPath, JSON.stringify(prefs, null, 2), 'utf8');
|
|
496
|
+
this.log(`Persisted userEmail to ${prefsPath}`);
|
|
497
|
+
}
|
|
498
|
+
catch (e) {
|
|
499
|
+
this.log(`Could not persist userEmail: ${e instanceof Error ? e.message : String(e)}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
475
502
|
log(message) {
|
|
476
503
|
// Log to stderr (stdout is reserved for MCP protocol)
|
|
477
504
|
const key = this.apiKey || 'MISSING_API_KEY';
|
|
@@ -663,14 +690,28 @@ class FraimLocalMCPServer {
|
|
|
663
690
|
}
|
|
664
691
|
async performLocalCatalogSync(projectRoot) {
|
|
665
692
|
const { runSync } = await Promise.resolve().then(() => __importStar(require('../cli/commands/sync')));
|
|
666
|
-
await
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
693
|
+
await this.runWithConsoleLogRedirectedToStderr(async () => {
|
|
694
|
+
await runSync({
|
|
695
|
+
projectRoot,
|
|
696
|
+
skipUpdates: true,
|
|
697
|
+
local: this.shouldUseLocalSyncTarget(),
|
|
698
|
+
failHard: 'throw'
|
|
699
|
+
});
|
|
671
700
|
});
|
|
672
701
|
this.writeLocalCatalogMetadata(projectRoot);
|
|
673
702
|
}
|
|
703
|
+
async runWithConsoleLogRedirectedToStderr(operation) {
|
|
704
|
+
const originalLog = console.log;
|
|
705
|
+
console.log = (...args) => {
|
|
706
|
+
console.error(...args);
|
|
707
|
+
};
|
|
708
|
+
try {
|
|
709
|
+
return await operation();
|
|
710
|
+
}
|
|
711
|
+
finally {
|
|
712
|
+
console.log = originalLog;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
674
715
|
async ensureFreshLocalCatalogOnConnect(requestId) {
|
|
675
716
|
if (process.env.FRAIM_DISABLE_SYNC_ON_CONNECT === '1') {
|
|
676
717
|
this.latestConnectSyncWarning = null;
|
|
@@ -1185,6 +1226,11 @@ class FraimLocalMCPServer {
|
|
|
1185
1226
|
const userEmail = emailMatch[1].trim();
|
|
1186
1227
|
this.ensureEngine().setUserEmail(userEmail);
|
|
1187
1228
|
this.log(`[req:${requestId}] Captured user email for template substitution: ${userEmail}`);
|
|
1229
|
+
// #533: persist the authenticated email locally so the AI Hub (a separate
|
|
1230
|
+
// server that never sees fraim_connect) can resolve personal/manager
|
|
1231
|
+
// learnings and show the real identity in its profile card. Merge into
|
|
1232
|
+
// the existing preferences.json rather than overwriting it.
|
|
1233
|
+
this.persistUserEmail(userEmail);
|
|
1188
1234
|
// Inject learning context from the local workspace (RFC 177: files live on disk, not server).
|
|
1189
1235
|
const workspaceRoot = this.findProjectRoot() || process.cwd();
|
|
1190
1236
|
const learningSection = (0, learning_context_builder_js_1.buildLearningContextSection)(workspaceRoot, userEmail, false);
|
|
@@ -1192,6 +1238,11 @@ class FraimLocalMCPServer {
|
|
|
1192
1238
|
responseText += learningSection;
|
|
1193
1239
|
this.log(`[req:${requestId}] Injected learning context for ${userEmail} from ${workspaceRoot}`);
|
|
1194
1240
|
}
|
|
1241
|
+
const teamContextSection = (0, learning_context_builder_js_1.buildTeamContextSection)(workspaceRoot, false);
|
|
1242
|
+
if (teamContextSection) {
|
|
1243
|
+
responseText += teamContextSection;
|
|
1244
|
+
this.log(`[req:${requestId}] Injected team context from ${workspaceRoot}`);
|
|
1245
|
+
}
|
|
1195
1246
|
}
|
|
1196
1247
|
if (this.latestConnectSyncWarning) {
|
|
1197
1248
|
responseText += `\n\n## Local Catalog\n${this.latestConnectSyncWarning}`;
|
|
@@ -1206,9 +1257,10 @@ class FraimLocalMCPServer {
|
|
|
1206
1257
|
if (typeof text === 'string' && userEmail) {
|
|
1207
1258
|
const workspaceRoot = this.findProjectRoot() || process.cwd();
|
|
1208
1259
|
const learningSection = (0, learning_context_builder_js_1.buildLearningContextSection)(workspaceRoot, userEmail, true);
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1260
|
+
const teamContextSection = (0, learning_context_builder_js_1.buildTeamContextSection)(workspaceRoot, true);
|
|
1261
|
+
if (learningSection || teamContextSection) {
|
|
1262
|
+
finalizedResponse.result.content[0].text = text + `\n\n---` + learningSection + teamContextSection;
|
|
1263
|
+
this.log(`[req:${requestId}] Injected job-focus learning/team context for ${userEmail}`);
|
|
1212
1264
|
}
|
|
1213
1265
|
}
|
|
1214
1266
|
}
|
|
@@ -1863,7 +1915,7 @@ class FraimLocalMCPServer {
|
|
|
1863
1915
|
//
|
|
1864
1916
|
// When a QUALITY_PRODUCING_JOBS job reaches its final phase
|
|
1865
1917
|
// (nextPhase === null) with status === 'complete', the agent MUST
|
|
1866
|
-
// include a valid `evidence.quality` object. If invalid, swap the
|
|
1918
|
+
// include `evidence.artifactPath` and a valid `evidence.quality` object. If invalid, swap the
|
|
1867
1919
|
// accomplishment message for a rejection and DO NOT emit. If valid,
|
|
1868
1920
|
// fire-and-forget POST to /api/analytics/quality-score so the row
|
|
1869
1921
|
// lands in fraim_quality_scores.
|
|
@@ -1871,8 +1923,13 @@ class FraimLocalMCPServer {
|
|
|
1871
1923
|
const isFinalCompletion = args.status === 'complete' &&
|
|
1872
1924
|
(tutoringResponse.nextPhase === null || tutoringResponse.nextPhase === undefined);
|
|
1873
1925
|
if (isQualityJob && isFinalCompletion) {
|
|
1874
|
-
const qualityErrors =
|
|
1875
|
-
|
|
1926
|
+
const qualityErrors = [
|
|
1927
|
+
...((0, quality_evidence_1.validateQualityEvidence)(args.evidence?.quality, args.jobName) || []),
|
|
1928
|
+
...(typeof args.evidence?.artifactPath === 'string' && args.evidence.artifactPath.trim()
|
|
1929
|
+
? []
|
|
1930
|
+
: ['evidence.artifactPath is required'])
|
|
1931
|
+
];
|
|
1932
|
+
if (qualityErrors.length > 0) {
|
|
1876
1933
|
this.log(`❌ Quality enforcement rejected ${args.jobName} completion: ${qualityErrors.join('; ')}`);
|
|
1877
1934
|
const rejection = (0, quality_evidence_1.buildQualityRejectionMessage)(args.jobName, args.currentPhase, qualityErrors);
|
|
1878
1935
|
return await this.finalizeLocalToolTextResponse(request, requestSessionId, requestId, rejection);
|
|
@@ -1886,7 +1943,7 @@ class FraimLocalMCPServer {
|
|
|
1886
1943
|
jobId: args.jobId,
|
|
1887
1944
|
sessionId: requestSessionId || args.sessionId || 'unknown',
|
|
1888
1945
|
quality: args.evidence.quality,
|
|
1889
|
-
artifactPath: args.evidence
|
|
1946
|
+
artifactPath: args.evidence?.artifactPath,
|
|
1890
1947
|
repoIdentifier: args.evidence?.reviewContext?.repoIdentifier || this.repoInfo?.url,
|
|
1891
1948
|
reviewContext: {
|
|
1892
1949
|
...(args.evidence?.reviewContext || {}),
|
|
@@ -1923,9 +1980,10 @@ class FraimLocalMCPServer {
|
|
|
1923
1980
|
if (userEmail) {
|
|
1924
1981
|
const workspaceRoot = this.findProjectRoot() || process.cwd();
|
|
1925
1982
|
const learningSection = (0, learning_context_builder_js_1.buildLearningContextSection)(workspaceRoot, userEmail, true);
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1983
|
+
const teamContextSection = (0, learning_context_builder_js_1.buildTeamContextSection)(workspaceRoot, true);
|
|
1984
|
+
if (learningSection || teamContextSection) {
|
|
1985
|
+
responseText += `\n\n---` + learningSection + teamContextSection;
|
|
1986
|
+
this.log(`✅ Injected job-focus learning/team context for ${userEmail} (local override path)`);
|
|
1929
1987
|
}
|
|
1930
1988
|
}
|
|
1931
1989
|
return await this.finalizeLocalToolTextResponse(request, requestSessionId, requestId, responseText);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-framework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.162",
|
|
4
4
|
"description": "FRAIM: AI Workforce Infrastructure — the organizational capability that turns AI agents into an accountable workforce, their operators into capable AI managers, and executives into leaders with clear optics on AI proficiency.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,12 +9,13 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"dev": "tsx --watch src/fraim-mcp-server.ts > server.log 2>&1",
|
|
11
11
|
"dev:prod": "npm run build && node dist/src/fraim-mcp-server.js > server.log 2>&1",
|
|
12
|
-
"build": "tsx scripts/build-fraim-config-schema-template.ts && npm run typecheck:scripts && tsc && npm run build:stubs && npm run build:fraim-brain && node scripts/copy-registry.js && npm run validate:registry && npm run validate:fraim-pro-assets && npm run validate:employee-catalog && tsx scripts/validate-purity.ts",
|
|
12
|
+
"build": "tsx scripts/build-fraim-config-schema-template.ts && npm run typecheck:scripts && tsc && npm run build:stubs && npm run build:fraim-brain && node scripts/copy-registry.js && npm run validate:registry && npm run validate:fraim-pro-assets && npm run validate:employee-catalog && npm run validate:learning-format-contract && tsx scripts/validate-purity.ts",
|
|
13
|
+
"validate:learning-format-contract": "tsx scripts/validate-learning-format-contract.ts",
|
|
13
14
|
"build:stubs": "tsx scripts/build-stub-registry.ts",
|
|
14
15
|
"build:fraim-brain": "node scripts/generate-fraim-brain.js",
|
|
15
|
-
"test-all": "npm run test && npm run test:isolated && npm run test:ui",
|
|
16
|
+
"test-all": "npm run test && npm run test:isolated tests/isolated/test-*.ts && npm run test:ui",
|
|
16
17
|
"test": "node scripts/test-with-server.js",
|
|
17
|
-
"test:isolated": "npx tsx --test --test-concurrency=1 --test-reporter=spec
|
|
18
|
+
"test:isolated": "npx tsx --test --test-concurrency=1 --test-reporter=spec ",
|
|
18
19
|
"test:smoke": "node scripts/test-with-server.js --tags=smoke",
|
|
19
20
|
"test:coverage": "node scripts/test-with-server.js --tags=smoke --coverage",
|
|
20
21
|
"test:stripe": "node scripts/test-with-server.js tests/test-stripe-payment-complete.ts",
|