fraim 2.0.264 → 2.0.266
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.
|
@@ -673,6 +673,19 @@ async function spawnAndHandshake(command, args, timeoutMs = STDIO_HANDSHAKE_TIME
|
|
|
673
673
|
child.on('error', (err) => {
|
|
674
674
|
spawnError = err;
|
|
675
675
|
});
|
|
676
|
+
const handleStdinError = (err) => {
|
|
677
|
+
if (settled)
|
|
678
|
+
return;
|
|
679
|
+
const msg = err.message || String(err);
|
|
680
|
+
settle({
|
|
681
|
+
status: 'error',
|
|
682
|
+
phase: 'spawn',
|
|
683
|
+
message: `Failed to start MCP server: ${msg}`,
|
|
684
|
+
suggestion: 'Verify that the MCP server command is installed and accessible on PATH.',
|
|
685
|
+
details: { command, args, error: (0, fraim_mcp_diagnostics_1.sanitizeDiagnosticText)(msg, knownSecrets) }
|
|
686
|
+
});
|
|
687
|
+
};
|
|
688
|
+
child.stdin?.on('error', handleStdinError);
|
|
676
689
|
const handleStdoutLine = (line) => {
|
|
677
690
|
const trimmed = line.trim();
|
|
678
691
|
if (!trimmed)
|
|
@@ -804,14 +817,20 @@ async function spawnAndHandshake(command, args, timeoutMs = STDIO_HANDSHAKE_TIME
|
|
|
804
817
|
clientInfo: { name: 'fraim-doctor', version: '1.0' }
|
|
805
818
|
}
|
|
806
819
|
}) + '\n';
|
|
807
|
-
child.stdin?.write(initMsg)
|
|
820
|
+
child.stdin?.write(initMsg, (err) => {
|
|
821
|
+
if (err)
|
|
822
|
+
handleStdinError(err);
|
|
823
|
+
});
|
|
808
824
|
const toolsMsg = JSON.stringify({
|
|
809
825
|
jsonrpc: '2.0',
|
|
810
826
|
id: 2,
|
|
811
827
|
method: 'tools/list',
|
|
812
828
|
params: {}
|
|
813
829
|
}) + '\n';
|
|
814
|
-
child.stdin?.write(toolsMsg)
|
|
830
|
+
child.stdin?.write(toolsMsg, (err) => {
|
|
831
|
+
if (err)
|
|
832
|
+
handleStdinError(err);
|
|
833
|
+
});
|
|
815
834
|
}
|
|
816
835
|
catch {
|
|
817
836
|
// stdin may not be writable if the process exited immediately
|
|
@@ -53,8 +53,8 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
53
53
|
pam: {
|
|
54
54
|
personaKey: 'pam',
|
|
55
55
|
bundleId: 'persona-pam-core',
|
|
56
|
-
catalogMetadata: buildCatalogMetadata('pam', ['feature-specification', '
|
|
57
|
-
protectedJobs: ['feature-specification', '
|
|
56
|
+
catalogMetadata: buildCatalogMetadata('pam', ['feature-specification', 'project-plan-creation', 'implementation-feature-review']),
|
|
57
|
+
protectedJobs: ['feature-specification', 'send-newsletter', 'send-thank-you-notes', 'experiment-tracking', 'project-plan-creation', 'implementation-feature-review', 'scrum-sprint-planning', 'mvp-validation-plan', 'sprint-planning', 'customer-prospect-discovery', 'interview-preparation', 'participant-recruitment', 'process-interview-notes', 'review-customer-development', 'triage-customer-needs'],
|
|
58
58
|
protectedAliases: ['product-management', 'product-spec'],
|
|
59
59
|
defaultHireMode: 'job',
|
|
60
60
|
lockCopy: 'Hire PaM to unlock product-management work for this request.'
|
|
@@ -63,7 +63,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
63
63
|
personaKey: 'swen',
|
|
64
64
|
bundleId: 'persona-swen-core',
|
|
65
65
|
catalogMetadata: buildCatalogMetadata('swen', ['feature-implementation', 'technical-design', 'code-refactoring']),
|
|
66
|
-
protectedJobs: ['feature-implementation', 'implementation-design-review', 'code-refactoring', 'pr-iteration', 'mobile-app-development', 'mcp-server-creation', 'cloud-application-deployment', 'cloud-cost-optimization', 'cloud-performance-diagnosis', 'route-llm-spend-to-cloud-credits', 'set-up-cloud-cost-alerts', 'gitlabs-to-github', 'system-migration', 'cross-cloud-migration', 'data-pipeline-design', 'data-quality-monitoring', 'data-platform-architecture', 'write-dev-docs', 'database-schema-design', 'create-architecture', 'project-scaffolding', 'codebase-analysis-and-ideation', 'github-org-setup', 'google-workspace-setup', 'mobile-app-rejection-response', 'mobile-app-submission', 'publish-mcp-app', 'application-replication-workflow'],
|
|
66
|
+
protectedJobs: ['feature-implementation', 'technical-design', 'implementation-design-review', 'code-refactoring', 'pr-iteration', 'mobile-app-development', 'mcp-server-creation', 'cloud-application-deployment', 'cloud-cost-optimization', 'cloud-performance-diagnosis', 'route-llm-spend-to-cloud-credits', 'set-up-cloud-cost-alerts', 'gitlabs-to-github', 'system-migration', 'cross-cloud-migration', 'data-pipeline-design', 'data-quality-monitoring', 'data-platform-architecture', 'write-dev-docs', 'database-schema-design', 'create-architecture', 'project-scaffolding', 'codebase-analysis-and-ideation', 'github-org-setup', 'google-workspace-setup', 'mobile-app-rejection-response', 'mobile-app-submission', 'publish-mcp-app', 'application-replication-workflow'],
|
|
67
67
|
protectedAliases: ['software-engineering', 'implementation'],
|
|
68
68
|
defaultHireMode: 'job',
|
|
69
69
|
lockCopy: 'Hire SWEn to unlock software-engineering delivery for this request.'
|
|
@@ -117,7 +117,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
117
117
|
personaKey: 'ashley',
|
|
118
118
|
bundleId: 'persona-ashley-core',
|
|
119
119
|
catalogMetadata: buildCatalogMetadata('ashley', ['chief-of-staff-briefing', 'executive-assistant', 'analyze-transcript']),
|
|
120
|
-
protectedJobs: ['chief-of-staff-briefing', 'calendar-triage', 'meeting-preparation', 'executive-assistant', '
|
|
120
|
+
protectedJobs: ['chief-of-staff-briefing', 'calendar-triage', 'meeting-preparation', 'executive-assistant', 'analyze-transcript', 'travel-planning', 'travel-disruption-rebooking'],
|
|
121
121
|
protectedAliases: ['executive-assistant', 'operations-assistant'],
|
|
122
122
|
defaultHireMode: 'job',
|
|
123
123
|
lockCopy: 'Hire AshLey to unlock executive-assistant work for this request.'
|
|
@@ -273,6 +273,7 @@ for (const bundle of Object.values(exports.PERSONA_CAPABILITY_BUNDLES)) {
|
|
|
273
273
|
// as "free") so the Hub attributes them to FRAIMworker, but they are never
|
|
274
274
|
// hire-gated because FRAIMworker is not a purchasable persona.
|
|
275
275
|
const GENERIC_WORKER_OWNED_JOBS = new Set([
|
|
276
|
+
'create-hub-configured-agent',
|
|
276
277
|
'contribute-to-fraim',
|
|
277
278
|
'file-fraim-issue',
|
|
278
279
|
'praise-fraim',
|
|
@@ -90,6 +90,13 @@ function getLearningRoots(workspaceRoot) {
|
|
|
90
90
|
repoLearningsBase: (0, project_fraim_paths_1.getWorkspaceLearningsDir)(workspaceRoot)
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
|
+
function sameResolvedPath(a, b) {
|
|
94
|
+
return (0, path_1.resolve)(a) === (0, path_1.resolve)(b);
|
|
95
|
+
}
|
|
96
|
+
function portablePersonalBaseIsLive(globalBase) {
|
|
97
|
+
return sameResolvedPath((0, path_1.join)((0, pack_home_1.resolvePackHome)('manager').contentRoot, 'learnings'), globalBase)
|
|
98
|
+
|| !sameResolvedPath(globalBase, (0, project_fraim_paths_1.getUserFraimLearningsDir)());
|
|
99
|
+
}
|
|
93
100
|
function resolvePersonalLearningFile(repoBase, managerCacheBase, managerCacheDisplayBase, globalBase, globalDisplayBase, fileName) {
|
|
94
101
|
const repoPath = (0, path_1.join)(repoBase, fileName);
|
|
95
102
|
if ((0, fs_1.existsSync)(repoPath)) {
|
|
@@ -100,14 +107,14 @@ function resolvePersonalLearningFile(repoBase, managerCacheBase, managerCacheDis
|
|
|
100
107
|
};
|
|
101
108
|
}
|
|
102
109
|
const globalPath = (0, path_1.join)(globalBase, fileName);
|
|
103
|
-
|
|
110
|
+
const managerCachePath = (0, path_1.join)(managerCacheBase, fileName);
|
|
111
|
+
if (portablePersonalBaseIsLive(globalBase) && (0, fs_1.existsSync)(globalPath)) {
|
|
104
112
|
return {
|
|
105
113
|
present: true,
|
|
106
114
|
path: globalPath,
|
|
107
115
|
displayPath: `${globalDisplayBase.replace(/\/$/, '')}/${fileName}`
|
|
108
116
|
};
|
|
109
117
|
}
|
|
110
|
-
const managerCachePath = (0, path_1.join)(managerCacheBase, fileName);
|
|
111
118
|
if ((0, fs_1.existsSync)(managerCachePath)) {
|
|
112
119
|
return {
|
|
113
120
|
present: true,
|
|
@@ -140,7 +147,9 @@ function resolvePersonalLearningFileTiers(repoBase, managerCacheBase, managerCac
|
|
|
140
147
|
});
|
|
141
148
|
}
|
|
142
149
|
const globalPath = (0, path_1.join)(globalBase, fileName);
|
|
143
|
-
if ((
|
|
150
|
+
if (portablePersonalBaseIsLive(globalBase) &&
|
|
151
|
+
(0, fs_1.existsSync)(globalPath) &&
|
|
152
|
+
!tiers.some((tier) => sameResolvedPath(tier.path, globalPath))) {
|
|
144
153
|
tiers.push({
|
|
145
154
|
level: 'manager',
|
|
146
155
|
path: globalPath,
|
|
@@ -405,10 +414,25 @@ function collectPendingL0SourceFiles(workspaceRoot, resolvedUserId, roots) {
|
|
|
405
414
|
}
|
|
406
415
|
return sources.sort((a, b) => b.sortKey.localeCompare(a.sortKey) || a.displayPath.localeCompare(b.displayPath));
|
|
407
416
|
}
|
|
417
|
+
/**
|
|
418
|
+
* Issue #1078 fix: derive the org display path from contentRoot rather than
|
|
419
|
+
* hardcoding the pre-migration flat layout. After migrateOrgFlatContent runs,
|
|
420
|
+
* contentRoot is ~/.fraim/org/personalized-employee/ — not ~/.fraim/org/.
|
|
421
|
+
* The relative portion after the ~/.fraim/ prefix is computed dynamically so
|
|
422
|
+
* it cannot drift from the read path again.
|
|
423
|
+
*/
|
|
424
|
+
function orgCacheDisplayPath(fileName) {
|
|
425
|
+
const fraimDir = (0, project_fraim_paths_1.getUserFraimDirPath)();
|
|
426
|
+
const cachePath = (0, path_1.join)((0, pack_home_1.resolvePackHome)('org').contentRoot, 'learnings', fileName);
|
|
427
|
+
const rel = cachePath.startsWith(fraimDir)
|
|
428
|
+
? cachePath.slice(fraimDir.length).replace(/\\/g, '/').replace(/^\//, '')
|
|
429
|
+
: cachePath.replace(/\\/g, '/');
|
|
430
|
+
return (0, project_fraim_paths_1.getUserFraimDisplayPath)(rel);
|
|
431
|
+
}
|
|
408
432
|
/**
|
|
409
433
|
* Resolve an L2 org-scope learning file (issue #563): a repo-local override
|
|
410
434
|
* (`fraim/personalized-employee/learnings/`) wins, then the synced org cache
|
|
411
|
-
* (
|
|
435
|
+
* (contentRoot/learnings/). Org learnings are part of the shared org pack,
|
|
412
436
|
* so a synced copy must be readable by the agent even with no repo-local copy.
|
|
413
437
|
* Mirrors the org context/rules resolution order. Returns `present:false` with
|
|
414
438
|
* the repo path/display when neither tier has the file.
|
|
@@ -420,7 +444,7 @@ function resolveOrgLearningFile(repoLearningsBase, fileName) {
|
|
|
420
444
|
}
|
|
421
445
|
const cachePath = (0, path_1.join)((0, pack_home_1.resolvePackHome)('org').contentRoot, 'learnings', fileName);
|
|
422
446
|
if ((0, fs_1.existsSync)(cachePath)) {
|
|
423
|
-
return { present: true, path: cachePath, displayPath: (
|
|
447
|
+
return { present: true, path: cachePath, displayPath: orgCacheDisplayPath(fileName) };
|
|
424
448
|
}
|
|
425
449
|
return { present: false, path: repoPath, displayPath: `${REPO_LEARNINGS_REL}/${fileName}` };
|
|
426
450
|
}
|
|
@@ -436,7 +460,7 @@ function resolveOrgLearningFileTiers(repoLearningsBase, fileName) {
|
|
|
436
460
|
tiers.push({
|
|
437
461
|
level: 'org',
|
|
438
462
|
path: cachePath,
|
|
439
|
-
displayPath: (
|
|
463
|
+
displayPath: orgCacheDisplayPath(fileName)
|
|
440
464
|
});
|
|
441
465
|
}
|
|
442
466
|
const repoPath = (0, path_1.join)(repoLearningsBase, fileName);
|
|
@@ -1158,11 +1182,20 @@ function collectBrainLearningDots(workspaceRoot, userId) {
|
|
|
1158
1182
|
if (coldEntries.length) {
|
|
1159
1183
|
processed.push({ type: fileType, typeLabel, region, scope: 'cold', displayPath: coldResolved.displayPath, entries: coldEntries });
|
|
1160
1184
|
}
|
|
1161
|
-
// L2 org
|
|
1162
|
-
|
|
1163
|
-
const
|
|
1164
|
-
if (
|
|
1165
|
-
|
|
1185
|
+
// L2 org — issue #1078 fix: use tiered resolver + merge so synced org home
|
|
1186
|
+
// and repo-local org-*.md are both included, same as buildLearningContextSection.
|
|
1187
|
+
const orgTiers = resolveOrgLearningFileTiers(roots.repoLearningsBase, `org-${fileType}.md`);
|
|
1188
|
+
if (orgTiers.length > 0) {
|
|
1189
|
+
// Merge entries across tiers; deduplicate by title (last tier wins = repo-local wins).
|
|
1190
|
+
const allOrgEntries = orgTiers.flatMap(t => parseBrainLearningEntries(t.path, fileType));
|
|
1191
|
+
const byTitle = new Map();
|
|
1192
|
+
for (const e of allOrgEntries)
|
|
1193
|
+
byTitle.set(e.title.trim().toLowerCase(), e);
|
|
1194
|
+
const mergedOrgEntries = [...byTitle.values()];
|
|
1195
|
+
if (mergedOrgEntries.length) {
|
|
1196
|
+
// Use the first (highest-precedence = synced org home) tier's displayPath.
|
|
1197
|
+
processed.push({ type: fileType, typeLabel, region, scope: 'org', displayPath: orgTiers[0].displayPath, entries: mergedOrgEntries });
|
|
1198
|
+
}
|
|
1166
1199
|
}
|
|
1167
1200
|
}
|
|
1168
1201
|
return { pending: { count: pendingSignals.length, signals: pendingSignals }, processed };
|