fraim-framework 2.0.162 → 2.0.164
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/desktop-main.js +4 -1
- package/dist/src/ai-hub/hosts.js +97 -12
- package/dist/src/ai-hub/preferences.js +1 -1
- package/dist/src/ai-hub/server.js +49 -123
- package/dist/src/cli/commands/init-project.js +15 -14
- package/dist/src/cli/commands/sync.js +38 -0
- package/dist/src/cli/doctor/check-runner.js +3 -1
- package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +261 -2
- package/dist/src/cli/utils/git-org-sync.js +56 -0
- package/dist/src/cli/utils/org-migration.js +50 -0
- package/dist/src/cli/utils/org-pack-sync.js +208 -0
- package/dist/src/cli/utils/project-bootstrap.js +20 -7
- package/dist/src/cli/utils/user-config.js +68 -0
- package/dist/src/core/fraim-config-schema.generated.js +10 -0
- package/dist/src/first-run/types.js +8 -0
- package/dist/src/local-mcp-server/agent-token-prices.js +30 -0
- package/dist/src/local-mcp-server/learning-context-builder.js +78 -29
- package/dist/src/local-mcp-server/stdio-server.js +30 -0
- package/index.js +1 -1
- package/package.json +2 -3
- package/public/ai-hub/index.html +5 -5
- package/public/ai-hub/powerpoint-taskpane/index.html +236 -236
- package/public/ai-hub/powerpoint-taskpane/manifest.xml +29 -29
- package/public/ai-hub/review.css +15 -15
- package/public/ai-hub/script.js +254 -195
- package/public/ai-hub/styles.css +206 -16
- package/public/first-run/styles.css +73 -73
- package/dist/src/ai-hub/word-sideload.js +0 -95
- package/dist/src/cli/commands/test-mcp.js +0 -171
- package/dist/src/cli/setup/first-run.js +0 -242
- package/dist/src/core/config-writer.js +0 -75
- package/dist/src/core/utils/job-aliases.js +0 -47
- package/dist/src/core/utils/workflow-parser.js +0 -174
|
@@ -9,7 +9,11 @@ exports.buildInitProjectSummary = buildInitProjectSummary;
|
|
|
9
9
|
exports.printInitProjectSummary = printInitProjectSummary;
|
|
10
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
11
|
const ONBOARDING_VIDEO_PLAYLIST_URL = 'https://fraimworks.ai/resources.html#videos';
|
|
12
|
-
function formatModeLabel(
|
|
12
|
+
function formatModeLabel(result) {
|
|
13
|
+
if (result.mode === 'conversational' && result.repositoryDetected) {
|
|
14
|
+
return 'Pending project onboarding';
|
|
15
|
+
}
|
|
16
|
+
const mode = result.mode;
|
|
13
17
|
switch (mode) {
|
|
14
18
|
case 'conversational':
|
|
15
19
|
return 'Conversational';
|
|
@@ -19,7 +23,11 @@ function formatModeLabel(mode) {
|
|
|
19
23
|
return 'Integrated';
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
|
-
function getModeSpecificNextStep(
|
|
26
|
+
function getModeSpecificNextStep(result) {
|
|
27
|
+
if (result.mode === 'conversational' && result.repositoryDetected) {
|
|
28
|
+
return `The agent will create fraim/config.json during onboarding, preserve the detected repository identity, then focus on project context, validation commands, durable project rules, and any optional automation choices. For a walkthrough, watch the onboarding videos at ${ONBOARDING_VIDEO_PLAYLIST_URL}.`;
|
|
29
|
+
}
|
|
30
|
+
const mode = result.mode;
|
|
23
31
|
switch (mode) {
|
|
24
32
|
case 'conversational':
|
|
25
33
|
return `The agent will create fraim/config.json during onboarding, then focus on project context, validation commands, and durable project rules. For a walkthrough, watch the onboarding videos at ${ONBOARDING_VIDEO_PLAYLIST_URL}.`;
|
|
@@ -53,7 +61,8 @@ function buildInitProjectSummary(result) {
|
|
|
53
61
|
return {
|
|
54
62
|
status: 'FRAIM project initialized.',
|
|
55
63
|
fields: {
|
|
56
|
-
mode: formatModeLabel(result
|
|
64
|
+
mode: formatModeLabel(result),
|
|
65
|
+
projectContext: result.repositoryDetected ? 'repository-backed' : 'local-folder',
|
|
57
66
|
project: result.projectName,
|
|
58
67
|
repositoryDetection: result.repositoryDetected ? 'detected' : 'not detected',
|
|
59
68
|
issueTracking: result.issueTrackingDetected ? 'detected' : 'not detected',
|
|
@@ -64,19 +73,23 @@ function buildInitProjectSummary(result) {
|
|
|
64
73
|
warnings: [...result.warnings],
|
|
65
74
|
nextStep: {
|
|
66
75
|
prompt: 'Tell your AI agent "Onboard this project".',
|
|
67
|
-
explanation: getModeSpecificNextStep(result
|
|
76
|
+
explanation: getModeSpecificNextStep(result)
|
|
68
77
|
}
|
|
69
78
|
};
|
|
70
79
|
}
|
|
71
80
|
function printInitProjectSummary(result) {
|
|
72
81
|
const summary = buildInitProjectSummary(result);
|
|
73
|
-
const
|
|
82
|
+
const showRepositoryDetection = result.mode !== 'conversational' || result.repositoryDetected;
|
|
83
|
+
const showIssueTrackingDetails = result.mode !== 'conversational';
|
|
74
84
|
console.log(chalk_1.default.green(`\n${summary.status}`));
|
|
75
85
|
console.log(chalk_1.default.blue('Project summary:'));
|
|
76
|
-
console.log(chalk_1.default.gray(`
|
|
86
|
+
console.log(chalk_1.default.gray(` Automation mode: ${summary.fields.mode}`));
|
|
87
|
+
console.log(chalk_1.default.gray(` Project context: ${summary.fields.projectContext}`));
|
|
77
88
|
console.log(chalk_1.default.gray(` Project: ${summary.fields.project}`));
|
|
78
|
-
if (
|
|
89
|
+
if (showRepositoryDetection) {
|
|
79
90
|
console.log(chalk_1.default.gray(` Repository detection: ${summary.fields.repositoryDetection}`));
|
|
91
|
+
}
|
|
92
|
+
if (showIssueTrackingDetails) {
|
|
80
93
|
console.log(chalk_1.default.gray(` Issue tracking: ${summary.fields.issueTracking}`));
|
|
81
94
|
}
|
|
82
95
|
console.log(chalk_1.default.gray(` Sync: ${summary.fields.sync}`));
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readUserFraimConfig = readUserFraimConfig;
|
|
7
|
+
exports.writeUserFraimConfig = writeUserFraimConfig;
|
|
8
|
+
exports.getOrganizationConfig = getOrganizationConfig;
|
|
9
|
+
/**
|
|
10
|
+
* Typed read/write helpers for the user-level FRAIM config (~/.fraim/config.json),
|
|
11
|
+
* including the organization block introduced by issue #563.
|
|
12
|
+
*
|
|
13
|
+
* The user-level config is machine-scoped and distinct from the workspace
|
|
14
|
+
* config (fraim/config.json). Honors the FRAIM_USER_DIR override used by tests.
|
|
15
|
+
*/
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
|
|
19
|
+
function getUserConfigPath() {
|
|
20
|
+
return path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), 'config.json');
|
|
21
|
+
}
|
|
22
|
+
function readUserFraimConfig() {
|
|
23
|
+
try {
|
|
24
|
+
const configPath = getUserConfigPath();
|
|
25
|
+
if (!fs_1.default.existsSync(configPath))
|
|
26
|
+
return {};
|
|
27
|
+
const parsed = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
28
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Shallow-merge a patch into the user config and persist it. Existing keys
|
|
36
|
+
* not named in the patch are preserved. Setting a key to undefined removes it.
|
|
37
|
+
*/
|
|
38
|
+
function writeUserFraimConfig(patch) {
|
|
39
|
+
const current = readUserFraimConfig();
|
|
40
|
+
const merged = { ...current, ...patch };
|
|
41
|
+
for (const key of Object.keys(patch)) {
|
|
42
|
+
if (patch[key] === undefined)
|
|
43
|
+
delete merged[key];
|
|
44
|
+
}
|
|
45
|
+
const dir = (0, project_fraim_paths_1.getUserFraimDirPath)();
|
|
46
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
47
|
+
fs_1.default.writeFileSync(getUserConfigPath(), JSON.stringify(merged, null, 2));
|
|
48
|
+
return merged;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Resolve the validated organization configuration, or null when no valid
|
|
52
|
+
* organization is configured on this machine (R1.1).
|
|
53
|
+
*/
|
|
54
|
+
function getOrganizationConfig() {
|
|
55
|
+
const raw = readUserFraimConfig().organization;
|
|
56
|
+
if (!raw || typeof raw !== 'object')
|
|
57
|
+
return null;
|
|
58
|
+
if (raw.backend === 'git') {
|
|
59
|
+
const gitUrl = typeof raw.gitUrl === 'string' ? raw.gitUrl.trim() : '';
|
|
60
|
+
if (!gitUrl)
|
|
61
|
+
return null;
|
|
62
|
+
return { backend: 'git', gitUrl, id: raw.id };
|
|
63
|
+
}
|
|
64
|
+
if (raw.backend === 'fraim-cloud') {
|
|
65
|
+
return { backend: 'fraim-cloud', id: raw.id };
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
@@ -296,6 +296,9 @@ exports.FRAIM_CONFIG_SCHEMA = {
|
|
|
296
296
|
},
|
|
297
297
|
"instanceUrl": {
|
|
298
298
|
"kind": "string"
|
|
299
|
+
},
|
|
300
|
+
"accessScript": {
|
|
301
|
+
"kind": "string"
|
|
299
302
|
}
|
|
300
303
|
}
|
|
301
304
|
},
|
|
@@ -390,6 +393,12 @@ exports.FRAIM_CONFIG_SCHEMA = {
|
|
|
390
393
|
},
|
|
391
394
|
"instructions": {
|
|
392
395
|
"kind": "string"
|
|
396
|
+
},
|
|
397
|
+
"accessScript": {
|
|
398
|
+
"kind": "string"
|
|
399
|
+
},
|
|
400
|
+
"purpose": {
|
|
401
|
+
"kind": "string"
|
|
393
402
|
}
|
|
394
403
|
}
|
|
395
404
|
}
|
|
@@ -588,6 +597,7 @@ exports.SUPPORTED_FRAIM_CONFIG_PATHS = [
|
|
|
588
597
|
"integrations.itsm",
|
|
589
598
|
"integrations.itsm.provider",
|
|
590
599
|
"integrations.itsm.instanceUrl",
|
|
600
|
+
"integrations.itsm.accessScript",
|
|
591
601
|
"integrations.identity",
|
|
592
602
|
"integrations.identity.provider",
|
|
593
603
|
"automation",
|
|
@@ -50,6 +50,14 @@ exports.FIRST_RUN_AGENT_OPTIONS = [
|
|
|
50
50
|
launchCommand: 'gemini',
|
|
51
51
|
installPackage: '@google/gemini-cli',
|
|
52
52
|
},
|
|
53
|
+
{
|
|
54
|
+
id: 'copilot-cli',
|
|
55
|
+
label: 'GitHub Copilot',
|
|
56
|
+
detectAliases: ['copilot'],
|
|
57
|
+
loginCommand: 'copilot login',
|
|
58
|
+
launchCommand: 'copilot',
|
|
59
|
+
installPackage: '@github/copilot',
|
|
60
|
+
},
|
|
53
61
|
];
|
|
54
62
|
/**
|
|
55
63
|
* The canonical row set, in display order. Each row starts in `pending`;
|
|
@@ -109,6 +109,36 @@ exports.AGENT_TOKEN_PRICES = [
|
|
|
109
109
|
source: 'https://ai.google.dev/gemini-api/docs/pricing',
|
|
110
110
|
verifiedOn: '2026-06-02',
|
|
111
111
|
},
|
|
112
|
+
// GitHub Copilot CLI. The CLI's agentic mode uses GPT-4.1 (also branded
|
|
113
|
+
// "gpt-4.1" in the Copilot API stream). GitHub charges for Copilot via
|
|
114
|
+
// premium-request quota rather than per-token billing on most plans; the
|
|
115
|
+
// per-token rates below apply to the Copilot API / enterprise API billing
|
|
116
|
+
// path (pay-per-token, not included-requests) as published at
|
|
117
|
+
// https://docs.github.com/en/copilot/about-github-copilot/plans-for-github-copilot
|
|
118
|
+
// and the underlying OpenAI model pricing for gpt-4.1 at openai.com/api/pricing/.
|
|
119
|
+
// Coverage is 'partial' for Copilot runs where the CLI does not emit a cost
|
|
120
|
+
// field — the table backstops cost computation when a token count is present
|
|
121
|
+
// but no direct costUsd is in the stream.
|
|
122
|
+
{
|
|
123
|
+
agent: 'copilot',
|
|
124
|
+
model: 'gpt-4.1',
|
|
125
|
+
inputPerMTok: 2.00,
|
|
126
|
+
outputPerMTok: 8.00,
|
|
127
|
+
cacheReadPerMTok: 0.50,
|
|
128
|
+
cacheCreationPerMTok: 0,
|
|
129
|
+
source: 'https://openai.com/api/pricing/ (gpt-4.1 row; GitHub Copilot CLI agentic mode)',
|
|
130
|
+
verifiedOn: '2026-06-09',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
agent: 'copilot',
|
|
134
|
+
model: 'gpt-4o',
|
|
135
|
+
inputPerMTok: 2.50,
|
|
136
|
+
outputPerMTok: 10.00,
|
|
137
|
+
cacheReadPerMTok: 1.25,
|
|
138
|
+
cacheCreationPerMTok: 0,
|
|
139
|
+
source: 'https://openai.com/api/pricing/ (gpt-4o row; GitHub Copilot CLI fallback model)',
|
|
140
|
+
verifiedOn: '2026-06-09',
|
|
141
|
+
},
|
|
112
142
|
];
|
|
113
143
|
/**
|
|
114
144
|
* Look up the price entry for an agent + model. Agent is matched
|
|
@@ -341,20 +341,40 @@ function computeOldestL0AgeDays(workspaceRoot, userId) {
|
|
|
341
341
|
}
|
|
342
342
|
return oldest;
|
|
343
343
|
}
|
|
344
|
+
/**
|
|
345
|
+
* Resolve an L2 org-scope learning file (issue #563): a repo-local override
|
|
346
|
+
* (`fraim/personalized-employee/learnings/`) wins, then the synced org cache
|
|
347
|
+
* (`~/.fraim/org/learnings/`). Org learnings are part of the shared org pack,
|
|
348
|
+
* so a synced copy must be readable by the agent even with no repo-local copy.
|
|
349
|
+
* Mirrors the org context/rules resolution order. Returns `present:false` with
|
|
350
|
+
* the repo path/display when neither tier has the file.
|
|
351
|
+
*/
|
|
352
|
+
function resolveOrgLearningFile(repoLearningsBase, fileName) {
|
|
353
|
+
const repoPath = (0, path_1.join)(repoLearningsBase, fileName);
|
|
354
|
+
if ((0, fs_1.existsSync)(repoPath)) {
|
|
355
|
+
return { present: true, path: repoPath, displayPath: `${REPO_LEARNINGS_REL}/${fileName}` };
|
|
356
|
+
}
|
|
357
|
+
const cachePath = (0, path_1.join)((0, project_fraim_paths_1.getUserFraimDirPath)(), 'org', 'learnings', fileName);
|
|
358
|
+
if ((0, fs_1.existsSync)(cachePath)) {
|
|
359
|
+
return { present: true, path: cachePath, displayPath: (0, project_fraim_paths_1.getUserFraimDisplayPath)(`org/learnings/${fileName}`) };
|
|
360
|
+
}
|
|
361
|
+
return { present: false, path: repoPath, displayPath: `${REPO_LEARNINGS_REL}/${fileName}` };
|
|
362
|
+
}
|
|
344
363
|
function buildLearningContextSection(workspaceRoot, userId, forJob) {
|
|
345
364
|
const roots = getLearningRoots(workspaceRoot);
|
|
346
365
|
const resolvedUserId = resolveLearningUserId(workspaceRoot, userId, roots);
|
|
347
366
|
const threshold = getScoreThreshold(workspaceRoot);
|
|
348
|
-
|
|
349
|
-
const
|
|
350
|
-
const
|
|
351
|
-
const
|
|
352
|
-
const
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
const
|
|
356
|
-
const
|
|
357
|
-
const
|
|
367
|
+
// L2 org learnings resolve repo-local first, then the synced org cache (#563).
|
|
368
|
+
const l2Mistake = resolveOrgLearningFile(roots.repoLearningsBase, 'org-mistake-patterns.md');
|
|
369
|
+
const l2Pref = resolveOrgLearningFile(roots.repoLearningsBase, 'org-preferences.md');
|
|
370
|
+
const l2Coach = resolveOrgLearningFile(roots.repoLearningsBase, 'org-manager-coaching.md');
|
|
371
|
+
const l2Validated = resolveOrgLearningFile(roots.repoLearningsBase, 'org-validated-patterns.md');
|
|
372
|
+
const l2MistakePresent = l2Mistake.present;
|
|
373
|
+
const l2PrefPresent = l2Pref.present;
|
|
374
|
+
const l2CoachPresent = l2Coach.present;
|
|
375
|
+
const l2ValidatedPresent = l2Validated.present;
|
|
376
|
+
const l2MistakeStats = l2MistakePresent ? scanMistakePatternFile(l2Mistake.path, threshold, 'mistake-patterns') : null;
|
|
377
|
+
const l2ValidatedStats = l2ValidatedPresent ? scanMistakePatternFile(l2Validated.path, threshold, 'validated-patterns') : null;
|
|
358
378
|
const l1Mistake = resolvePersonalLearningFile(roots.repoLearningsBase, roots.globalPersonalBase, roots.globalPersonalDisplayBase, `${resolvedUserId}-mistake-patterns.md`);
|
|
359
379
|
const l1Pref = resolvePersonalLearningFile(roots.repoLearningsBase, roots.globalPersonalBase, roots.globalPersonalDisplayBase, `${resolvedUserId}-preferences.md`);
|
|
360
380
|
const l1Coach = resolvePersonalLearningFile(roots.repoLearningsBase, roots.globalPersonalBase, roots.globalPersonalDisplayBase, `${resolvedUserId}-manager-coaching.md`);
|
|
@@ -394,13 +414,13 @@ function buildLearningContextSection(workspaceRoot, userId, forJob) {
|
|
|
394
414
|
if (hasL2) {
|
|
395
415
|
section += '### L2 - Org patterns\n';
|
|
396
416
|
if (l2MistakePresent)
|
|
397
|
-
section += `\`${
|
|
417
|
+
section += `\`${l2Mistake.displayPath}\` (entries above score threshold)\n`;
|
|
398
418
|
if (l2PrefPresent)
|
|
399
|
-
section += `\`${
|
|
419
|
+
section += `\`${l2Pref.displayPath}\` (all entries)\n`;
|
|
400
420
|
if (l2CoachPresent)
|
|
401
|
-
section += `\`${
|
|
421
|
+
section += `\`${l2Coach.displayPath}\` (manager-facing; all entries)\n`;
|
|
402
422
|
if (l2ValidatedPresent)
|
|
403
|
-
section += `\`${
|
|
423
|
+
section += `\`${l2Validated.displayPath}\` (entries above score threshold)\n`;
|
|
404
424
|
const l2DormantTotal = (l2MistakeStats?.dormant || 0) + (l2ValidatedStats?.dormant || 0);
|
|
405
425
|
if (l2DormantTotal > 0) {
|
|
406
426
|
section += `Dormant: ${l2DormantTotal} org pattern${l2DormantTotal !== 1 ? 's' : ''} below threshold\n`;
|
|
@@ -465,12 +485,13 @@ function buildLearningContextSection(workspaceRoot, userId, forJob) {
|
|
|
465
485
|
return section;
|
|
466
486
|
}
|
|
467
487
|
/**
|
|
468
|
-
* Resolve an organization/manager-scope context file
|
|
469
|
-
*
|
|
470
|
-
*
|
|
471
|
-
*
|
|
488
|
+
* Resolve an organization/manager-scope context file (issue #563 order, R3.1):
|
|
489
|
+
* 1. repo-local override (`fraim/personalized-employee/…`) — wins when present
|
|
490
|
+
* 2. managed org cache (`~/.fraim/org/…`) — the synced shared org layer;
|
|
491
|
+
* eligible for org files only, never manager files (R3.3)
|
|
492
|
+
* 3. legacy machine-local (`~/.fraim/personalized-employee/…`)
|
|
472
493
|
*/
|
|
473
|
-
function resolveOrgContextFile(workspaceRoot, relativePath) {
|
|
494
|
+
function resolveOrgContextFile(workspaceRoot, relativePath, orgCacheEligible = true) {
|
|
474
495
|
try {
|
|
475
496
|
const repoPath = (0, path_1.join)((0, project_fraim_paths_1.getWorkspaceFraimDir)(workspaceRoot), 'personalized-employee', relativePath);
|
|
476
497
|
if ((0, fs_1.existsSync)(repoPath)) {
|
|
@@ -479,6 +500,15 @@ function resolveOrgContextFile(workspaceRoot, relativePath) {
|
|
|
479
500
|
displayPath: (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)(`personalized-employee/${relativePath}`)
|
|
480
501
|
};
|
|
481
502
|
}
|
|
503
|
+
if (orgCacheEligible) {
|
|
504
|
+
const cachePath = (0, path_1.join)((0, project_fraim_paths_1.getUserFraimDirPath)(), 'org', relativePath);
|
|
505
|
+
if ((0, fs_1.existsSync)(cachePath)) {
|
|
506
|
+
return {
|
|
507
|
+
present: true,
|
|
508
|
+
displayPath: (0, project_fraim_paths_1.getUserFraimDisplayPath)(`org/${relativePath}`)
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
}
|
|
482
512
|
const userPath = (0, path_1.join)((0, project_fraim_paths_1.getUserFraimDirPath)(), 'personalized-employee', relativePath);
|
|
483
513
|
if ((0, fs_1.existsSync)(userPath)) {
|
|
484
514
|
return {
|
|
@@ -518,11 +548,12 @@ function resolveProjectContextFile(workspaceRoot, relativePath) {
|
|
|
518
548
|
* lists only present files, and returns '' when none exist.
|
|
519
549
|
*/
|
|
520
550
|
function buildTeamContextSection(workspaceRoot, forJob) {
|
|
521
|
-
// Organization layer
|
|
551
|
+
// Organization layer: repo override, then org cache, then legacy machine-local.
|
|
552
|
+
// Manager files are personal and never resolve from the shared org cache (R3.3).
|
|
522
553
|
const orgContext = resolveOrgContextFile(workspaceRoot, 'context/org_context.md');
|
|
523
|
-
const managerContext = resolveOrgContextFile(workspaceRoot, 'context/manager_context.md');
|
|
554
|
+
const managerContext = resolveOrgContextFile(workspaceRoot, 'context/manager_context.md', false);
|
|
524
555
|
const orgRules = resolveOrgContextFile(workspaceRoot, 'rules/org_rules.md');
|
|
525
|
-
const managerRules = resolveOrgContextFile(workspaceRoot, 'rules/manager_rules.md');
|
|
556
|
+
const managerRules = resolveOrgContextFile(workspaceRoot, 'rules/manager_rules.md', false);
|
|
526
557
|
// Project layer (repo-local only).
|
|
527
558
|
const projectContext = resolveProjectContextFile(workspaceRoot, 'context/project_context.md');
|
|
528
559
|
const projectBrief = resolveProjectContextFile(workspaceRoot, 'context/project_brief.md');
|
|
@@ -571,9 +602,9 @@ function buildTeamContextSection(workspaceRoot, forJob) {
|
|
|
571
602
|
function resolveTeamContextFiles(workspaceRoot) {
|
|
572
603
|
return {
|
|
573
604
|
orgContext: resolveOrgContextFile(workspaceRoot, 'context/org_context.md'),
|
|
574
|
-
managerContext: resolveOrgContextFile(workspaceRoot, 'context/manager_context.md'),
|
|
605
|
+
managerContext: resolveOrgContextFile(workspaceRoot, 'context/manager_context.md', false),
|
|
575
606
|
orgRules: resolveOrgContextFile(workspaceRoot, 'rules/org_rules.md'),
|
|
576
|
-
managerRules: resolveOrgContextFile(workspaceRoot, 'rules/manager_rules.md'),
|
|
607
|
+
managerRules: resolveOrgContextFile(workspaceRoot, 'rules/manager_rules.md', false),
|
|
577
608
|
projectContext: resolveProjectContextFile(workspaceRoot, 'context/project_context.md'),
|
|
578
609
|
projectBrief: resolveProjectContextFile(workspaceRoot, 'context/project_brief.md'),
|
|
579
610
|
projectQa: resolveProjectContextFile(workspaceRoot, 'context/project_qa.md'),
|
|
@@ -627,6 +658,23 @@ function resolveTeamContextFile(workspaceRoot, key) {
|
|
|
627
658
|
scope
|
|
628
659
|
};
|
|
629
660
|
}
|
|
661
|
+
// Synced org cache (org files only, never manager files — R3.3). Reads
|
|
662
|
+
// resolve here, but the cache is managed content: it is never a write
|
|
663
|
+
// destination (R5.1), so writePath stays empty and the caller must route
|
|
664
|
+
// edits through the org backend's propose-and-approve flow.
|
|
665
|
+
if (key === 'org' || key === 'orgRules') {
|
|
666
|
+
const cachePath = (0, path_1.join)((0, project_fraim_paths_1.getUserFraimDirPath)(), 'org', relativePath);
|
|
667
|
+
if ((0, fs_1.existsSync)(cachePath)) {
|
|
668
|
+
return {
|
|
669
|
+
present: true,
|
|
670
|
+
readPath: cachePath,
|
|
671
|
+
writePath: '',
|
|
672
|
+
displayPath: (0, project_fraim_paths_1.getUserFraimDisplayPath)(`org/${relativePath}`),
|
|
673
|
+
scope,
|
|
674
|
+
managedByOrgSync: true
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
}
|
|
630
678
|
const userPath = (0, path_1.join)((0, project_fraim_paths_1.getUserFraimDirPath)(), 'personalized-employee', relativePath);
|
|
631
679
|
const userDisplay = (0, project_fraim_paths_1.getUserFraimDisplayPath)(`personalized-employee/${relativePath}`);
|
|
632
680
|
return {
|
|
@@ -674,11 +722,12 @@ function countLearningEntries(filePath) {
|
|
|
674
722
|
function countPreservedLearnings(workspaceRoot, userId) {
|
|
675
723
|
const roots = getLearningRoots(workspaceRoot);
|
|
676
724
|
const resolvedUserId = resolveLearningUserId(workspaceRoot, userId, roots);
|
|
677
|
-
// L2 organization-scope preserved files
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
countLearningEntries((
|
|
681
|
-
countLearningEntries((
|
|
725
|
+
// L2 organization-scope preserved files: repo-local override, then the
|
|
726
|
+
// synced org cache (#563), matching what buildLearningContextSection injects.
|
|
727
|
+
const organization = countLearningEntries(resolveOrgLearningFile(roots.repoLearningsBase, 'org-mistake-patterns.md').path) +
|
|
728
|
+
countLearningEntries(resolveOrgLearningFile(roots.repoLearningsBase, 'org-preferences.md').path) +
|
|
729
|
+
countLearningEntries(resolveOrgLearningFile(roots.repoLearningsBase, 'org-manager-coaching.md').path) +
|
|
730
|
+
countLearningEntries(resolveOrgLearningFile(roots.repoLearningsBase, 'org-validated-patterns.md').path);
|
|
682
731
|
const resolve = (fileName) => resolvePersonalLearningFile(roots.repoLearningsBase, roots.globalPersonalBase, roots.globalPersonalDisplayBase, fileName);
|
|
683
732
|
// L1 manager-facing reverse-mentoring file.
|
|
684
733
|
const manager = countLearningEntries(resolve(`${resolvedUserId}-manager-coaching.md`).path);
|
|
@@ -415,6 +415,7 @@ class FraimLocalMCPServer {
|
|
|
415
415
|
this.mentoringResponseCache = null;
|
|
416
416
|
this.connectSyncInFlight = null;
|
|
417
417
|
this.latestConnectSyncWarning = null;
|
|
418
|
+
this.orgCacheRefreshInFlight = false;
|
|
418
419
|
this.writer = writer || process.stdout.write.bind(process.stdout);
|
|
419
420
|
this.remoteUrl = process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
|
|
420
421
|
this.apiKey = this.loadApiKey();
|
|
@@ -748,6 +749,35 @@ class FraimLocalMCPServer {
|
|
|
748
749
|
finally {
|
|
749
750
|
this.connectSyncInFlight = null;
|
|
750
751
|
}
|
|
752
|
+
this.maybeRefreshOrgCache(String(requestId));
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Issue #563 (R4.2): opportunistically refresh the managed org cache
|
|
756
|
+
* (~/.fraim/org/) at session bootstrap when it is missing or older than
|
|
757
|
+
* 24 hours. Fire-and-forget: session start never blocks on the org
|
|
758
|
+
* backend, and a failed refresh leaves the existing cache serving stale
|
|
759
|
+
* with its age (R4.3).
|
|
760
|
+
*/
|
|
761
|
+
maybeRefreshOrgCache(requestId) {
|
|
762
|
+
if (this.orgCacheRefreshInFlight)
|
|
763
|
+
return;
|
|
764
|
+
this.orgCacheRefreshInFlight = true;
|
|
765
|
+
void Promise.resolve().then(() => __importStar(require('../cli/utils/org-pack-sync'))).then(async ({ getOrgCacheAgeHours, syncOrgCache }) => {
|
|
766
|
+
const ageHours = getOrgCacheAgeHours();
|
|
767
|
+
if (ageHours !== null && ageHours < 24)
|
|
768
|
+
return;
|
|
769
|
+
const outcome = await syncOrgCache();
|
|
770
|
+
if (outcome.status !== 'disabled') {
|
|
771
|
+
const version = outcome.metadata ? ` (version ${outcome.metadata.version.slice(0, 12)})` : '';
|
|
772
|
+
this.log(`[req:${requestId}] Org cache refresh at connect: ${outcome.status}${version}`);
|
|
773
|
+
}
|
|
774
|
+
})
|
|
775
|
+
.catch((error) => {
|
|
776
|
+
this.logError(`[req:${requestId}] Org cache refresh failed: ${error?.message || error}`);
|
|
777
|
+
})
|
|
778
|
+
.finally(() => {
|
|
779
|
+
this.orgCacheRefreshInFlight = false;
|
|
780
|
+
});
|
|
751
781
|
}
|
|
752
782
|
/**
|
|
753
783
|
* Automatically detect machine information
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-framework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.164",
|
|
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": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"build:fraim-brain": "node scripts/generate-fraim-brain.js",
|
|
16
16
|
"test-all": "npm run test && npm run test:isolated tests/isolated/test-*.ts && npm run test:ui",
|
|
17
17
|
"test": "node scripts/test-with-server.js",
|
|
18
|
-
"test:isolated": "
|
|
18
|
+
"test:isolated": "node scripts/test-isolated.js",
|
|
19
19
|
"test:smoke": "node scripts/test-with-server.js --tags=smoke",
|
|
20
20
|
"test:coverage": "node scripts/test-with-server.js --tags=smoke --coverage",
|
|
21
21
|
"test:stripe": "node scripts/test-with-server.js tests/test-stripe-payment-complete.ts",
|
|
@@ -104,7 +104,6 @@
|
|
|
104
104
|
"@types/prompts": "^2.4.9",
|
|
105
105
|
"@types/semver": "^7.7.1",
|
|
106
106
|
"fast-glob": "^3.3.3",
|
|
107
|
-
"html-to-docx": "^1.8.0",
|
|
108
107
|
"markdown-it": "^14.1.1",
|
|
109
108
|
"markdown-it-highlightjs": "^4.3.0",
|
|
110
109
|
"playwright": "^1.58.2",
|
package/public/ai-hub/index.html
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<!-- Detect Electron + host platform before CSS paints so html.electron / html.mac /
|
|
10
10
|
html.win / html.linux rules apply on first render (no flash, correct native chrome). -->
|
|
11
11
|
<script>(function(){var h=document.documentElement,ua=navigator.userAgent;if(/Electron/.test(ua))h.classList.add('electron');var p=(navigator.userAgentData&&navigator.userAgentData.platform)||navigator.platform||'';if(/mac/i.test(p)||/Mac|iPhone|iPad|iPod/.test(ua))h.classList.add('mac');else if(/win/i.test(p)||/Windows/.test(ua))h.classList.add('win');else if(/linux/i.test(p)||/Linux|X11/.test(ua))h.classList.add('linux');})();</script>
|
|
12
|
-
<link rel="stylesheet" href="./styles.css">
|
|
12
|
+
<link rel="stylesheet" href="./styles.css?v=conv-panels-20260611b">
|
|
13
13
|
<link rel="stylesheet" href="./review.css">
|
|
14
14
|
</head>
|
|
15
15
|
<body>
|
|
@@ -329,7 +329,7 @@
|
|
|
329
329
|
</span>
|
|
330
330
|
<span class="active-employee-row" onclick="event.stopPropagation()">
|
|
331
331
|
<span class="active-employee-label">Maestro</span>
|
|
332
|
-
<select id="active-employee-select" class="employee-select inline" aria-label="
|
|
332
|
+
<select id="active-employee-select" class="employee-select inline" aria-label="Agent Tool"></select>
|
|
333
333
|
</span>
|
|
334
334
|
</summary>
|
|
335
335
|
<div class="panel-body">
|
|
@@ -363,7 +363,7 @@
|
|
|
363
363
|
<summary>
|
|
364
364
|
<span class="panel-summary-copy">
|
|
365
365
|
<span class="panel-kicker">Micro-manage</span>
|
|
366
|
-
<span class="panel-summary-text">Raw host
|
|
366
|
+
<span class="panel-summary-text">Raw host events and tool calls</span>
|
|
367
367
|
</span>
|
|
368
368
|
</summary>
|
|
369
369
|
<pre class="micro-log" id="micro-log"></pre>
|
|
@@ -476,8 +476,8 @@
|
|
|
476
476
|
<div class="word-ctx-card-body" id="word-ctx-card-body" hidden></div>
|
|
477
477
|
</div>
|
|
478
478
|
<div class="employee-line">
|
|
479
|
-
<span class="employee-label">
|
|
480
|
-
<select id="employee-select" class="employee-select"></select>
|
|
479
|
+
<span class="employee-label">Agent Tool:</span>
|
|
480
|
+
<select id="employee-select" class="employee-select" aria-label="Agent Tool"></select>
|
|
481
481
|
</div>
|
|
482
482
|
<div id="agent-install-panel"></div>
|
|
483
483
|
<div id="ab-toggle-wrap" hidden>
|