fraim-framework 2.0.192 → 2.0.193
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.
|
@@ -19,6 +19,7 @@ const defaultPreferences = (projectPath) => ({
|
|
|
19
19
|
recentJobInstructions: {},
|
|
20
20
|
personaKey: null,
|
|
21
21
|
projects: normalizeAiHubProjectList([], projectPath),
|
|
22
|
+
removedProjectPaths: [],
|
|
22
23
|
});
|
|
23
24
|
function normalizeProjectPath(projectPath) {
|
|
24
25
|
return path_1.default.resolve(projectPath || process.cwd());
|
|
@@ -38,6 +39,21 @@ function projectPathExists(projectPath) {
|
|
|
38
39
|
function projectIdForPath(projectPath) {
|
|
39
40
|
return `p-${(0, crypto_1.createHash)('sha1').update(canonicalProjectPath(projectPath)).digest('base64url').slice(0, 16)}`;
|
|
40
41
|
}
|
|
42
|
+
function normalizeRemovedProjectPaths(raw, currentProjectPath) {
|
|
43
|
+
if (!Array.isArray(raw))
|
|
44
|
+
return [];
|
|
45
|
+
const currentKey = currentProjectPath ? canonicalProjectPath(currentProjectPath) : null;
|
|
46
|
+
const seen = new Set();
|
|
47
|
+
for (const value of raw) {
|
|
48
|
+
if (typeof value !== 'string' || value.trim().length === 0)
|
|
49
|
+
continue;
|
|
50
|
+
const key = canonicalProjectPath(value);
|
|
51
|
+
if (currentKey && key === currentKey)
|
|
52
|
+
continue;
|
|
53
|
+
seen.add(key);
|
|
54
|
+
}
|
|
55
|
+
return Array.from(seen);
|
|
56
|
+
}
|
|
41
57
|
function uniqueProjectId(entry, seenIds) {
|
|
42
58
|
const fallbackId = projectIdForPath(entry.folderPath);
|
|
43
59
|
const rawId = typeof entry.id === 'string' ? entry.id.trim() : '';
|
|
@@ -86,10 +102,13 @@ function normalizeProjectEntry(raw, fallbackPath) {
|
|
|
86
102
|
function normalizeAiHubProjectList(projects, currentProjectPath, options = {}) {
|
|
87
103
|
const byPath = new Map();
|
|
88
104
|
const currentKey = currentProjectPath ? normalizeProjectPath(currentProjectPath) : null;
|
|
105
|
+
const removedKeys = new Set(normalizeRemovedProjectPaths(options.removedProjectPaths, currentProjectPath));
|
|
89
106
|
const add = (entry) => {
|
|
90
107
|
if (!entry)
|
|
91
108
|
return;
|
|
92
109
|
const key = normalizeProjectPath(entry.folderPath);
|
|
110
|
+
if (removedKeys.has(canonicalProjectPath(key)))
|
|
111
|
+
return;
|
|
93
112
|
if (!options.includeMissing && key !== currentKey && !projectPathExists(key))
|
|
94
113
|
return;
|
|
95
114
|
const existing = byPath.get(key);
|
|
@@ -111,6 +130,7 @@ class AiHubPreferencesStore {
|
|
|
111
130
|
}
|
|
112
131
|
try {
|
|
113
132
|
const raw = JSON.parse(fs_1.default.readFileSync(this.stateFilePath, 'utf8'));
|
|
133
|
+
const removedProjectPaths = normalizeRemovedProjectPaths(raw.removedProjectPaths, projectPath);
|
|
114
134
|
return {
|
|
115
135
|
projectPath: raw.projectPath || projectPath,
|
|
116
136
|
employeeId: (raw.employeeId === 'claude' || raw.employeeId === 'codex' || raw.employeeId === 'gemini' || raw.employeeId === 'copilot') ? raw.employeeId : DEFAULT_EMPLOYEE,
|
|
@@ -121,7 +141,8 @@ class AiHubPreferencesStore {
|
|
|
121
141
|
: {},
|
|
122
142
|
personaKey: typeof raw.personaKey === 'string' ? raw.personaKey : null,
|
|
123
143
|
apiKey: typeof raw.apiKey === 'string' && raw.apiKey.length > 0 ? raw.apiKey : undefined,
|
|
124
|
-
projects: normalizeAiHubProjectList(Array.isArray(raw.projects) ? raw.projects : [], projectPath),
|
|
144
|
+
projects: normalizeAiHubProjectList(Array.isArray(raw.projects) ? raw.projects : [], projectPath, { removedProjectPaths }),
|
|
145
|
+
removedProjectPaths,
|
|
125
146
|
};
|
|
126
147
|
}
|
|
127
148
|
catch {
|
|
@@ -132,18 +153,30 @@ class AiHubPreferencesStore {
|
|
|
132
153
|
fs_1.default.mkdirSync(path_1.default.dirname(this.stateFilePath), { recursive: true });
|
|
133
154
|
fs_1.default.writeFileSync(this.stateFilePath, JSON.stringify(preferences, null, 2));
|
|
134
155
|
}
|
|
135
|
-
saveProjects(projectPath, projects) {
|
|
156
|
+
saveProjects(projectPath, projects, options = {}) {
|
|
136
157
|
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
137
158
|
const preferences = this.load(normalizedProjectPath);
|
|
138
|
-
const
|
|
139
|
-
|
|
159
|
+
const reviveKeys = new Set(normalizeRemovedProjectPaths(options.reviveRemovedPaths, normalizedProjectPath));
|
|
160
|
+
const removedProjectPaths = normalizeRemovedProjectPaths(preferences.removedProjectPaths, normalizedProjectPath)
|
|
161
|
+
.filter((removedPath) => !reviveKeys.has(removedPath));
|
|
162
|
+
const nextProjects = normalizeAiHubProjectList(projects, normalizedProjectPath, { removedProjectPaths });
|
|
163
|
+
this.save({ ...preferences, projectPath: normalizedProjectPath, projects: nextProjects, removedProjectPaths });
|
|
140
164
|
return nextProjects;
|
|
141
165
|
}
|
|
142
166
|
mergeProjects(projectPath, projects) {
|
|
143
167
|
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
144
168
|
const preferences = this.load(normalizedProjectPath);
|
|
145
|
-
const
|
|
146
|
-
|
|
169
|
+
const removedProjectPaths = normalizeRemovedProjectPaths(preferences.removedProjectPaths, normalizedProjectPath);
|
|
170
|
+
const nextProjects = normalizeAiHubProjectList([...(preferences.projects || []), ...projects], normalizedProjectPath, { removedProjectPaths });
|
|
171
|
+
this.save({ ...preferences, projectPath: normalizedProjectPath, projects: nextProjects, removedProjectPaths });
|
|
172
|
+
return nextProjects;
|
|
173
|
+
}
|
|
174
|
+
removeProject(projectPath, projects, removedProjectPath) {
|
|
175
|
+
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
|
176
|
+
const preferences = this.load(normalizedProjectPath);
|
|
177
|
+
const removedProjectPaths = normalizeRemovedProjectPaths([...(preferences.removedProjectPaths || []), removedProjectPath], normalizedProjectPath);
|
|
178
|
+
const nextProjects = normalizeAiHubProjectList(projects, normalizedProjectPath, { removedProjectPaths });
|
|
179
|
+
this.save({ ...preferences, projectPath: normalizedProjectPath, projects: nextProjects, removedProjectPaths });
|
|
147
180
|
return nextProjects;
|
|
148
181
|
}
|
|
149
182
|
remember(preferences, jobId, instructions) {
|
|
@@ -1382,10 +1382,7 @@ class AiHubServer {
|
|
|
1382
1382
|
...(preferences.projects || []),
|
|
1383
1383
|
...conversationProjects,
|
|
1384
1384
|
...extras,
|
|
1385
|
-
], normalizedProjectPath);
|
|
1386
|
-
}
|
|
1387
|
-
saveKnownProjects(projectPath, projects) {
|
|
1388
|
-
return this.preferencesStore.saveProjects(path_1.default.resolve(projectPath || this.projectPath), this.knownProjects(projectPath, projects));
|
|
1385
|
+
], normalizedProjectPath, { removedProjectPaths: preferences.removedProjectPaths || [] });
|
|
1389
1386
|
}
|
|
1390
1387
|
async bootstrapResponse(projectPath, apiKey) {
|
|
1391
1388
|
const normalizedProjectPath = path_1.default.resolve(projectPath || this.projectPath);
|
|
@@ -2270,7 +2267,7 @@ class AiHubServer {
|
|
|
2270
2267
|
if (!Array.isArray(body.projects)) {
|
|
2271
2268
|
return res.status(400).json({ error: 'projects array required' });
|
|
2272
2269
|
}
|
|
2273
|
-
const projects = this.
|
|
2270
|
+
const projects = this.preferencesStore.saveProjects(projectPath, [...this.knownProjects(projectPath), ...body.projects], { reviveRemovedPaths: Array.isArray(body.reviveRemovedPaths) ? body.reviveRemovedPaths : [] });
|
|
2274
2271
|
return res.json({ projectPath, projects, source: 'disk' });
|
|
2275
2272
|
}
|
|
2276
2273
|
catch (error) {
|
|
@@ -2312,9 +2309,9 @@ class AiHubServer {
|
|
|
2312
2309
|
// Delete the conversation-store KEY — an empty list would still re-derive
|
|
2313
2310
|
// the project via listProjectPaths (R9).
|
|
2314
2311
|
this.conversationStore.removeProject(entry.folderPath);
|
|
2315
|
-
// Persist the filtered list
|
|
2316
|
-
//
|
|
2317
|
-
const projects = this.preferencesStore.
|
|
2312
|
+
// Persist the filtered list with a tombstone: a normal project save
|
|
2313
|
+
// still merges derived projects and cannot express removal.
|
|
2314
|
+
const projects = this.preferencesStore.removeProject(projectPath, known.filter((project) => project.id !== entry.id), entry.folderPath);
|
|
2318
2315
|
return res.json({ ok: true, projects });
|
|
2319
2316
|
});
|
|
2320
2317
|
this.app.post('/api/ai-hub/api-key', (req, res) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-framework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.193",
|
|
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": {
|
package/public/ai-hub/script.js
CHANGED
|
@@ -1011,6 +1011,21 @@ function aggregateDotClass(dots) {
|
|
|
1011
1011
|
|
|
1012
1012
|
function renderRail() {
|
|
1013
1013
|
renderTeamRoster();
|
|
1014
|
+
const activeRailProjectKey = (typeof tf !== 'undefined' && tf && tf.activeProjectId)
|
|
1015
|
+
|| state.projectPath
|
|
1016
|
+
|| 'current';
|
|
1017
|
+
const employeeGroupOpenState = new Map();
|
|
1018
|
+
els['conv-list'].querySelectorAll('details.conv-employee-group[data-employee-key]').forEach((details) => {
|
|
1019
|
+
const key = details.dataset ? details.dataset.employeeKey : '';
|
|
1020
|
+
const projectKey = details.dataset ? (details.dataset.projectKey || activeRailProjectKey) : activeRailProjectKey;
|
|
1021
|
+
if (key) employeeGroupOpenState.set(projectKey + ':' + key, details.open);
|
|
1022
|
+
});
|
|
1023
|
+
const employeeGroupStateKey = (key) => activeRailProjectKey + ':' + key;
|
|
1024
|
+
const preservedEmployeeGroupOpen = (key) => (
|
|
1025
|
+
employeeGroupOpenState.has(employeeGroupStateKey(key))
|
|
1026
|
+
? employeeGroupOpenState.get(employeeGroupStateKey(key))
|
|
1027
|
+
: true
|
|
1028
|
+
);
|
|
1014
1029
|
els['conv-list'].innerHTML = '';
|
|
1015
1030
|
// R4: filter by selected persona when one is active.
|
|
1016
1031
|
// #693 R1: the "Project Updates" section was retired; project-lifecycle runs
|
|
@@ -1167,7 +1182,9 @@ function renderRail() {
|
|
|
1167
1182
|
for (const group of groups.values()) {
|
|
1168
1183
|
const details = document.createElement('details');
|
|
1169
1184
|
details.className = 'conv-employee-group';
|
|
1170
|
-
details.
|
|
1185
|
+
details.dataset.employeeKey = group.key;
|
|
1186
|
+
details.dataset.projectKey = activeRailProjectKey;
|
|
1187
|
+
details.open = preservedEmployeeGroupOpen(group.key);
|
|
1171
1188
|
const summary = document.createElement('summary');
|
|
1172
1189
|
summary.className = 'conv-employee-tab';
|
|
1173
1190
|
const avatar = buildConversationEmployeeAvatar(group.sample, 'conv-employee-avatar');
|
|
@@ -1237,7 +1254,9 @@ function renderRail() {
|
|
|
1237
1254
|
if (watercoolerConvs.length > 0) {
|
|
1238
1255
|
const wcDetails = document.createElement('details');
|
|
1239
1256
|
wcDetails.className = 'conv-employee-group conv-employee-group--adhoc';
|
|
1240
|
-
wcDetails.
|
|
1257
|
+
wcDetails.dataset.employeeKey = '__watercooler__';
|
|
1258
|
+
wcDetails.dataset.projectKey = activeRailProjectKey;
|
|
1259
|
+
wcDetails.open = preservedEmployeeGroupOpen('__watercooler__');
|
|
1241
1260
|
const wcSummary = document.createElement('summary');
|
|
1242
1261
|
wcSummary.className = 'conv-employee-tab';
|
|
1243
1262
|
// #693.8: watercooler icon (replaces the blank dashed placeholder).
|
|
@@ -6400,13 +6419,14 @@ function tfLoadStorage() {
|
|
|
6400
6419
|
try { tf.assignments = JSON.parse(window.localStorage.getItem(STORAGE_KEY_ASSIGNMENTS_512) || '{}'); }
|
|
6401
6420
|
catch { tf.assignments = {}; }
|
|
6402
6421
|
}
|
|
6403
|
-
function tfPersistProjects() {
|
|
6422
|
+
function tfPersistProjects(options) {
|
|
6404
6423
|
try { window.localStorage.setItem(STORAGE_KEY_PROJECTS_512, JSON.stringify(tf.projects)); } catch {}
|
|
6405
6424
|
if (!state.projectPath) return;
|
|
6425
|
+
const reviveRemovedPaths = options && Array.isArray(options.reviveRemovedPaths) ? options.reviveRemovedPaths : [];
|
|
6406
6426
|
requestJson('/api/ai-hub/projects', {
|
|
6407
6427
|
method: 'PUT',
|
|
6408
6428
|
headers: { 'Content-Type': 'application/json' },
|
|
6409
|
-
body: JSON.stringify({ projectPath: state.projectPath, projects: tf.projects }),
|
|
6429
|
+
body: JSON.stringify({ projectPath: state.projectPath, projects: tf.projects, reviveRemovedPaths }),
|
|
6410
6430
|
}).catch((error) => console.warn('[512] Could not persist projects to local disk:', error));
|
|
6411
6431
|
}
|
|
6412
6432
|
function tfPersistAssignments() {
|
|
@@ -9400,32 +9420,32 @@ function tfCloseConnectedSurface() {
|
|
|
9400
9420
|
tfShowArea(returnArea);
|
|
9401
9421
|
}
|
|
9402
9422
|
|
|
9403
|
-
async function tfHandleConnectedAuthMessage(event) {
|
|
9404
|
-
const frame = document.getElementById('connected-frame');
|
|
9405
|
-
if (frame && frame.contentWindow && event.source !== frame.contentWindow) return;
|
|
9406
|
-
let remoteOrigin = '';
|
|
9407
|
-
try { remoteOrigin = new URL(tfConnectedRemoteBase()).origin; }
|
|
9423
|
+
async function tfHandleConnectedAuthMessage(event) {
|
|
9424
|
+
const frame = document.getElementById('connected-frame');
|
|
9425
|
+
if (frame && frame.contentWindow && event.source !== frame.contentWindow) return;
|
|
9426
|
+
let remoteOrigin = '';
|
|
9427
|
+
try { remoteOrigin = new URL(tfConnectedRemoteBase()).origin; }
|
|
9408
9428
|
catch { return; }
|
|
9409
9429
|
if (event.origin !== remoteOrigin) return;
|
|
9410
|
-
const msg = event.data || {};
|
|
9411
|
-
if (!msg || msg.type !== 'fraim-auth-api-key' || typeof msg.apiKey !== 'string' || !msg.apiKey) return;
|
|
9412
|
-
const previousKey = tfConnectedApiKey();
|
|
9413
|
-
await tfRememberConnectedApiKey(msg.apiKey);
|
|
9414
|
-
if (state.connectedSurface) {
|
|
9415
|
-
const nextUrl = tfConnectedSurfaceUrl(state.connectedSurface);
|
|
9416
|
-
const frameEl = document.getElementById('connected-frame');
|
|
9417
|
-
if (frameEl) {
|
|
9418
|
-
try {
|
|
9419
|
-
const current = new URL(frameEl.src || '', window.location.href);
|
|
9420
|
-
const next = new URL(nextUrl, window.location.href);
|
|
9421
|
-
if (previousKey === msg.apiKey && current.origin === next.origin && current.pathname === next.pathname) {
|
|
9422
|
-
return;
|
|
9423
|
-
}
|
|
9424
|
-
} catch {}
|
|
9425
|
-
frameEl.src = nextUrl;
|
|
9426
|
-
}
|
|
9427
|
-
}
|
|
9428
|
-
}
|
|
9430
|
+
const msg = event.data || {};
|
|
9431
|
+
if (!msg || msg.type !== 'fraim-auth-api-key' || typeof msg.apiKey !== 'string' || !msg.apiKey) return;
|
|
9432
|
+
const previousKey = tfConnectedApiKey();
|
|
9433
|
+
await tfRememberConnectedApiKey(msg.apiKey);
|
|
9434
|
+
if (state.connectedSurface) {
|
|
9435
|
+
const nextUrl = tfConnectedSurfaceUrl(state.connectedSurface);
|
|
9436
|
+
const frameEl = document.getElementById('connected-frame');
|
|
9437
|
+
if (frameEl) {
|
|
9438
|
+
try {
|
|
9439
|
+
const current = new URL(frameEl.src || '', window.location.href);
|
|
9440
|
+
const next = new URL(nextUrl, window.location.href);
|
|
9441
|
+
if (previousKey === msg.apiKey && current.origin === next.origin && current.pathname === next.pathname) {
|
|
9442
|
+
return;
|
|
9443
|
+
}
|
|
9444
|
+
} catch {}
|
|
9445
|
+
frameEl.src = nextUrl;
|
|
9446
|
+
}
|
|
9447
|
+
}
|
|
9448
|
+
}
|
|
9429
9449
|
|
|
9430
9450
|
async function tfRememberConnectedApiKey(apiKey) {
|
|
9431
9451
|
const changed = apiKey !== state.storedApiKey;
|
|
@@ -9808,7 +9828,7 @@ async function tfCreateProject() {
|
|
|
9808
9828
|
};
|
|
9809
9829
|
tf.projects.push(project);
|
|
9810
9830
|
tf.assignments[id] = [];
|
|
9811
|
-
tfPersistProjects();
|
|
9831
|
+
tfPersistProjects({ reviveRemovedPaths: [folderPath] });
|
|
9812
9832
|
tfPersistAssignments();
|
|
9813
9833
|
tf.activeProjectId = id;
|
|
9814
9834
|
tfCloseNewProject();
|