fraim-hub 2.0.207 → 2.0.208
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/bin/fraim-hub.js +1 -1
- package/dist/src/ai-hub/cli.js +13 -7
- package/dist/src/ai-hub/desktop-main.js +4 -3
- package/dist/src/ai-hub/server.js +71 -44
- package/dist/src/ai-hub/word-sideload.js +95 -0
- package/dist/src/cli/fraim-hub.js +1 -1
- package/dist/src/cli/setup/ide-invocation-surfaces.js +6 -6
- package/extensions/office-word/taskpane.html +6 -6
- package/index.js +1 -1
- package/package.json +2 -2
- package/public/ai-hub/index.html +10 -9
- package/public/ai-hub/powerpoint-taskpane/index.html +236 -236
- package/public/ai-hub/powerpoint-taskpane/manifest.xml +29 -29
- package/public/ai-hub/script.js +35 -8
- package/public/ai-hub/styles.css +20 -0
- package/public/portfolio/ashley.html +1 -1
- package/public/portfolio/casey.html +1 -1
- package/public/portfolio/celia.html +1 -1
- package/public/portfolio/gautam.html +1 -1
- package/public/portfolio/hari.html +1 -1
- package/public/portfolio/maestro.html +1 -1
- package/public/portfolio/mandy.html +1 -1
- package/public/portfolio/pam.html +6 -6
- package/public/portfolio/qasm.html +1 -1
- package/public/portfolio/sade.html +1 -1
- package/public/portfolio/sam.html +1 -1
- package/public/portfolio/swen.html +6 -6
package/bin/fraim-hub.js
CHANGED
package/dist/src/ai-hub/cli.js
CHANGED
|
@@ -80,7 +80,10 @@ function openDesktopWindow(projectPath, preferredPort) {
|
|
|
80
80
|
if (!electronBinary || !desktopEntry) {
|
|
81
81
|
return false;
|
|
82
82
|
}
|
|
83
|
-
const
|
|
83
|
+
const args = projectPath
|
|
84
|
+
? [desktopEntry, '--project-path', projectPath, '--port', String(preferredPort)]
|
|
85
|
+
: [desktopEntry, '--port', String(preferredPort)];
|
|
86
|
+
const child = (0, child_process_1.spawn)(electronBinary, args, {
|
|
84
87
|
detached: true,
|
|
85
88
|
stdio: 'ignore',
|
|
86
89
|
});
|
|
@@ -182,7 +185,7 @@ async function reconcileRunningHub(flags) {
|
|
|
182
185
|
async function runHub(options) {
|
|
183
186
|
const { AiHubServer, findAvailablePort } = await Promise.resolve().then(() => __importStar(require('./server')));
|
|
184
187
|
const preferredPort = options.port || (0, git_utils_1.getPort)() + 100;
|
|
185
|
-
const projectPath = path_1.default.resolve(options.projectPath
|
|
188
|
+
const projectPath = options.projectPath ? path_1.default.resolve(options.projectPath) : undefined;
|
|
186
189
|
if (options.open) {
|
|
187
190
|
const wantDesktop = !options.browser;
|
|
188
191
|
if (wantDesktop) {
|
|
@@ -191,22 +194,25 @@ async function runHub(options) {
|
|
|
191
194
|
const openedDesktop = wantDesktop && openDesktopWindow(projectPath, preferredPort);
|
|
192
195
|
if (!openedDesktop) {
|
|
193
196
|
const port = await findAvailablePort(preferredPort);
|
|
194
|
-
const server = new AiHubServer({ projectPath });
|
|
197
|
+
const server = new AiHubServer(projectPath ? { projectPath } : {});
|
|
195
198
|
await server.start(port);
|
|
196
199
|
const url = `http://127.0.0.1:${port}/ai-hub/`;
|
|
197
200
|
console.log(`AI Hub running at ${url}`);
|
|
198
|
-
console.log(`Project path: ${
|
|
201
|
+
console.log(`Project path: ${server.getProjectPath()}`);
|
|
199
202
|
openBrowser(url);
|
|
200
203
|
return;
|
|
201
204
|
}
|
|
202
205
|
console.log('AI Hub desktop shell launched.');
|
|
203
|
-
|
|
206
|
+
if (projectPath)
|
|
207
|
+
console.log(`Project path: ${projectPath}`);
|
|
208
|
+
else
|
|
209
|
+
console.log('Project path: loaded from saved Hub state');
|
|
204
210
|
return;
|
|
205
211
|
}
|
|
206
212
|
const port = await findAvailablePort(preferredPort);
|
|
207
|
-
const server = new AiHubServer({ projectPath });
|
|
213
|
+
const server = new AiHubServer(projectPath ? { projectPath } : {});
|
|
208
214
|
await server.start(port);
|
|
209
215
|
const url = `http://127.0.0.1:${port}/ai-hub/`;
|
|
210
216
|
console.log(`AI Hub running at ${url}`);
|
|
211
|
-
console.log(`Project path: ${
|
|
217
|
+
console.log(`Project path: ${server.getProjectPath()}`);
|
|
212
218
|
}
|
|
@@ -33,7 +33,7 @@ function preferredWindowSize() {
|
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
function parseArgs(argv) {
|
|
36
|
-
let projectPath
|
|
36
|
+
let projectPath;
|
|
37
37
|
let preferredPort = 43091;
|
|
38
38
|
for (let i = 0; i < argv.length; i += 1) {
|
|
39
39
|
if (argv[i] === '--project-path' && argv[i + 1]) {
|
|
@@ -355,7 +355,7 @@ async function launchDesktopShell(options) {
|
|
|
355
355
|
// Fast on subsequent launches (file read); ~200ms on first launch (key gen).
|
|
356
356
|
const certBundle = await (0, cert_store_1.loadOrCreateCert)();
|
|
357
357
|
server = new server_1.AiHubServer({
|
|
358
|
-
projectPath: options.projectPath,
|
|
358
|
+
...(options.projectPath ? { projectPath: options.projectPath } : {}),
|
|
359
359
|
// Issue #701: no local DB. Persona/manager-team state resolves through the hosted
|
|
360
360
|
// server via the Hub's default remote gateway (authenticated by the user's API key).
|
|
361
361
|
httpsPort,
|
|
@@ -370,6 +370,7 @@ async function launchDesktopShell(options) {
|
|
|
370
370
|
},
|
|
371
371
|
});
|
|
372
372
|
await server.start(httpPort);
|
|
373
|
+
const resolvedProjectPath = server.getProjectPath();
|
|
373
374
|
// #755: record the live instance so `fraim hub` can detect a stale build and
|
|
374
375
|
// replace it instead of re-focusing an outdated process.
|
|
375
376
|
try {
|
|
@@ -383,7 +384,7 @@ async function launchDesktopShell(options) {
|
|
|
383
384
|
catch (err) {
|
|
384
385
|
console.warn('[fraim] could not write hub-runtime.json:', err);
|
|
385
386
|
}
|
|
386
|
-
ensureWordSideload(
|
|
387
|
+
ensureWordSideload(resolvedProjectPath, httpPort);
|
|
387
388
|
const hubUrl = `http://127.0.0.1:${httpPort}/ai-hub/`;
|
|
388
389
|
createTray(hubUrl);
|
|
389
390
|
await createWindow(hubUrl);
|
|
@@ -130,6 +130,12 @@ function loadPersonaCapabilityModule() {
|
|
|
130
130
|
function getProtectedPersonaForHubJob(jobName) {
|
|
131
131
|
return loadPersonaCapabilityModule()?.getProtectedPersonaForJob(jobName) ?? null;
|
|
132
132
|
}
|
|
133
|
+
const GENERIC_WORKER_PERSONA_KEY = 'fraimworker';
|
|
134
|
+
function getHubPersonaForJob(jobName) {
|
|
135
|
+
if (!jobName || jobName === '__freeform__')
|
|
136
|
+
return null;
|
|
137
|
+
return getProtectedPersonaForHubJob(jobName) ?? GENERIC_WORKER_PERSONA_KEY;
|
|
138
|
+
}
|
|
133
139
|
const FRAIM_INTERNAL_JOB_IDS = new Set([
|
|
134
140
|
'contribute-to-fraim',
|
|
135
141
|
'create-registry-asset',
|
|
@@ -177,6 +183,24 @@ function buildHubManagerHiringCatalog() {
|
|
|
177
183
|
roles: {},
|
|
178
184
|
};
|
|
179
185
|
}
|
|
186
|
+
function directoryExists(projectPath) {
|
|
187
|
+
try {
|
|
188
|
+
return fs_1.default.statSync(projectPath).isDirectory();
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function resolveInitialHubProjectPath(preferencesStore, fallbackPath) {
|
|
195
|
+
const fallback = path_1.default.resolve(fallbackPath || process.cwd());
|
|
196
|
+
const recorded = preferencesStore.load(fallback).projectPath;
|
|
197
|
+
if (typeof recorded === 'string' && recorded.trim().length > 0) {
|
|
198
|
+
const resolved = path_1.default.resolve(recorded);
|
|
199
|
+
if (directoryExists(resolved))
|
|
200
|
+
return resolved;
|
|
201
|
+
}
|
|
202
|
+
return fallback;
|
|
203
|
+
}
|
|
180
204
|
class AiHubRunRegistry {
|
|
181
205
|
constructor() {
|
|
182
206
|
this.runs = new Map();
|
|
@@ -1185,8 +1209,10 @@ class AiHubServer {
|
|
|
1185
1209
|
this.app = (0, express_1.default)();
|
|
1186
1210
|
this.runRegistry = new AiHubRunRegistry();
|
|
1187
1211
|
this.cronHandles = new Map();
|
|
1188
|
-
this.projectPath = options.projectPath || process.cwd();
|
|
1189
1212
|
this.preferencesStore = options.preferencesStore || new preferences_1.AiHubPreferencesStore();
|
|
1213
|
+
this.projectPath = options.projectPath
|
|
1214
|
+
? path_1.default.resolve(options.projectPath)
|
|
1215
|
+
: resolveInitialHubProjectPath(this.preferencesStore, process.cwd());
|
|
1190
1216
|
this.conversationStore = options.conversationStore || new conversation_store_1.AiHubConversationStore();
|
|
1191
1217
|
this.configuredAgentStore = options.configuredAgentStore || new configured_agents_1.AiHubConfiguredAgentStore();
|
|
1192
1218
|
this.wordTaskpaneDir = options.wordTaskpaneDir ?? resolveWordTaskpaneDir(this.projectPath);
|
|
@@ -1325,6 +1351,12 @@ class AiHubServer {
|
|
|
1325
1351
|
getApp() {
|
|
1326
1352
|
return this.app;
|
|
1327
1353
|
}
|
|
1354
|
+
getProjectPath() {
|
|
1355
|
+
return this.projectPath;
|
|
1356
|
+
}
|
|
1357
|
+
defaultProjectPath() {
|
|
1358
|
+
return resolveInitialHubProjectPath(this.preferencesStore, this.projectPath);
|
|
1359
|
+
}
|
|
1328
1360
|
async start(port) {
|
|
1329
1361
|
this.httpPort = port;
|
|
1330
1362
|
await new Promise((resolve, reject) => {
|
|
@@ -1946,7 +1978,7 @@ class AiHubServer {
|
|
|
1946
1978
|
id: employeeJob.id,
|
|
1947
1979
|
title: employeeJob.title,
|
|
1948
1980
|
stubPath: employeeJob.stubPath,
|
|
1949
|
-
personaKey: employeeJob.requiredPersonaKey ??
|
|
1981
|
+
personaKey: employeeJob.requiredPersonaKey ?? getHubPersonaForJob(employeeJob.id),
|
|
1950
1982
|
};
|
|
1951
1983
|
}
|
|
1952
1984
|
const managerTemplate = (0, catalog_1.discoverManagerTemplates)(projectPath).find((job) => job.id === jobId);
|
|
@@ -1955,7 +1987,7 @@ class AiHubServer {
|
|
|
1955
1987
|
id: managerTemplate.id,
|
|
1956
1988
|
title: managerTemplate.title,
|
|
1957
1989
|
stubPath: managerTemplate.stubPath,
|
|
1958
|
-
personaKey:
|
|
1990
|
+
personaKey: getHubPersonaForJob(managerTemplate.id),
|
|
1959
1991
|
};
|
|
1960
1992
|
}
|
|
1961
1993
|
return null;
|
|
@@ -2228,20 +2260,9 @@ class AiHubServer {
|
|
|
2228
2260
|
}
|
|
2229
2261
|
catch { }
|
|
2230
2262
|
}
|
|
2231
|
-
// #719: a bare reload
|
|
2232
|
-
//
|
|
2233
|
-
|
|
2234
|
-
// folder would resurrect a removed launch-folder project on every reload
|
|
2235
|
-
// (tfSwitchProjectFolder already documents the recorded folder as "the
|
|
2236
|
-
// default on next load"). The launch folder stays the fallback when nothing
|
|
2237
|
-
// is recorded or the recorded folder no longer exists.
|
|
2238
|
-
if (!projectPath) {
|
|
2239
|
-
const recorded = this.preferencesStore.load(this.projectPath).projectPath;
|
|
2240
|
-
if (recorded && path_1.default.resolve(recorded) !== path_1.default.resolve(this.projectPath) && fs_1.default.existsSync(recorded)) {
|
|
2241
|
-
projectPath = recorded;
|
|
2242
|
-
}
|
|
2243
|
-
}
|
|
2244
|
-
res.json(await this.bootstrapResponse(projectPath || this.projectPath));
|
|
2263
|
+
// #719 / local Hub package: a bare reload must land on the user-level saved
|
|
2264
|
+
// Hub project, not whatever directory happened to launch the process.
|
|
2265
|
+
res.json(await this.bootstrapResponse(projectPath || this.defaultProjectPath()));
|
|
2245
2266
|
});
|
|
2246
2267
|
// Issue #512 (S3, R14) — Brain summary as a standalone route, returning the
|
|
2247
2268
|
// same projection folded into bootstrap. Useful for the avatar→Brain view
|
|
@@ -2249,7 +2270,7 @@ class AiHubServer {
|
|
|
2249
2270
|
this.app.get('/api/ai-hub/brain', async (req, res) => {
|
|
2250
2271
|
const projectPath = typeof req.query.projectPath === 'string' && req.query.projectPath.length > 0
|
|
2251
2272
|
? path_1.default.resolve(req.query.projectPath)
|
|
2252
|
-
: this.
|
|
2273
|
+
: this.defaultProjectPath();
|
|
2253
2274
|
const jobCount = (0, catalog_1.discoverEmployeeJobs)(projectPath).length + (0, catalog_1.discoverManagerTemplates)(projectPath).length;
|
|
2254
2275
|
const userEmail = await this.resolveHubIdentity();
|
|
2255
2276
|
return res.json(this.computeBrain(projectPath, jobCount, userEmail));
|
|
@@ -2264,7 +2285,7 @@ class AiHubServer {
|
|
|
2264
2285
|
? path_1.default.resolve(req.query.projectPath)
|
|
2265
2286
|
: (typeof (req.body && req.body.projectPath) === 'string' && req.body.projectPath.length > 0
|
|
2266
2287
|
? path_1.default.resolve(req.body.projectPath)
|
|
2267
|
-
: this.
|
|
2288
|
+
: this.defaultProjectPath());
|
|
2268
2289
|
this.app.get('/api/ai-hub/learnings', async (req, res) => {
|
|
2269
2290
|
const scope = req.query.scope;
|
|
2270
2291
|
if (typeof scope !== 'string' || !VALID_SCOPES.includes(scope)) {
|
|
@@ -2344,7 +2365,7 @@ class AiHubServer {
|
|
|
2344
2365
|
? (0, conversation_store_1.conversationScopeKey)(scope, '')
|
|
2345
2366
|
: (typeof req.query.projectPath === 'string' && req.query.projectPath.length > 0
|
|
2346
2367
|
? path_1.default.resolve(req.query.projectPath)
|
|
2347
|
-
: this.
|
|
2368
|
+
: this.defaultProjectPath());
|
|
2348
2369
|
const loaded = this.conversationStore.loadProject(key);
|
|
2349
2370
|
return res.json({ projectPath: key, scope: scope ?? 'project', ...loaded, source: 'disk' });
|
|
2350
2371
|
});
|
|
@@ -2352,7 +2373,10 @@ class AiHubServer {
|
|
|
2352
2373
|
try {
|
|
2353
2374
|
const body = (req.body ?? {});
|
|
2354
2375
|
const scope = scopeParam(body.scope);
|
|
2355
|
-
|
|
2376
|
+
if (!scope && (typeof body.projectPath !== 'string' || body.projectPath.trim().length === 0)) {
|
|
2377
|
+
return res.status(400).json({ error: 'projectPath required for project conversations' });
|
|
2378
|
+
}
|
|
2379
|
+
const key = scope ? (0, conversation_store_1.conversationScopeKey)(scope, '') : ensureDirectoryPath(body.projectPath);
|
|
2356
2380
|
if (!Array.isArray(body.conversations)) {
|
|
2357
2381
|
return res.status(400).json({ error: 'conversations array required' });
|
|
2358
2382
|
}
|
|
@@ -2401,7 +2425,10 @@ class AiHubServer {
|
|
|
2401
2425
|
try {
|
|
2402
2426
|
const body = (req.body ?? {});
|
|
2403
2427
|
const scope = scopeParam(body.scope);
|
|
2404
|
-
|
|
2428
|
+
if (!scope && (typeof body.projectPath !== 'string' || body.projectPath.trim().length === 0)) {
|
|
2429
|
+
return res.status(400).json({ error: 'projectPath required for project conversations' });
|
|
2430
|
+
}
|
|
2431
|
+
const projectPath = scope ? (0, conversation_store_1.conversationScopeKey)(scope, '') : ensureDirectoryPath(body.projectPath);
|
|
2405
2432
|
const saved = this.conversationStore.patchConversation(projectPath, req.params.conversationId, body);
|
|
2406
2433
|
if (body.activeId !== undefined) {
|
|
2407
2434
|
const withActive = this.conversationStore.replaceProject(projectPath, { ...saved, activeId: body.activeId });
|
|
@@ -2416,13 +2443,13 @@ class AiHubServer {
|
|
|
2416
2443
|
this.app.get('/api/ai-hub/projects', (req, res) => {
|
|
2417
2444
|
const projectPath = typeof req.query.projectPath === 'string' && req.query.projectPath.length > 0
|
|
2418
2445
|
? path_1.default.resolve(req.query.projectPath)
|
|
2419
|
-
: this.
|
|
2446
|
+
: this.defaultProjectPath();
|
|
2420
2447
|
return res.json({ projectPath, projects: this.knownProjects(projectPath), source: 'disk' });
|
|
2421
2448
|
});
|
|
2422
2449
|
this.app.put('/api/ai-hub/projects', (req, res) => {
|
|
2423
2450
|
try {
|
|
2424
2451
|
const body = (req.body ?? {});
|
|
2425
|
-
const projectPath = ensureDirectoryPath(body.projectPath || this.
|
|
2452
|
+
const projectPath = ensureDirectoryPath(body.projectPath || this.defaultProjectPath());
|
|
2426
2453
|
if (!Array.isArray(body.projects)) {
|
|
2427
2454
|
return res.status(400).json({ error: 'projects array required' });
|
|
2428
2455
|
}
|
|
@@ -2442,7 +2469,7 @@ class AiHubServer {
|
|
|
2442
2469
|
this.app.delete('/api/ai-hub/projects/:id', (req, res) => {
|
|
2443
2470
|
const projectPath = typeof req.query.projectPath === 'string' && req.query.projectPath.length > 0
|
|
2444
2471
|
? path_1.default.resolve(req.query.projectPath)
|
|
2445
|
-
: this.
|
|
2472
|
+
: this.defaultProjectPath();
|
|
2446
2473
|
const known = this.knownProjects(projectPath);
|
|
2447
2474
|
// Match by id, falling back to the canonical folderPath. The client and server
|
|
2448
2475
|
// hash a path to an id with different algorithms, and the client mints its own id
|
|
@@ -2486,7 +2513,7 @@ class AiHubServer {
|
|
|
2486
2513
|
// (set by `fraim setup`), read fresh via resolveApiKey() on every request.
|
|
2487
2514
|
this.app.post('/api/ai-hub/preferences', (req, res) => {
|
|
2488
2515
|
const { personaKey } = req.body;
|
|
2489
|
-
const prefs = this.preferencesStore.load(this.
|
|
2516
|
+
const prefs = this.preferencesStore.load(this.defaultProjectPath());
|
|
2490
2517
|
this.preferencesStore.save({ ...prefs, personaKey: personaKey ?? null });
|
|
2491
2518
|
return res.json({ ok: true });
|
|
2492
2519
|
});
|
|
@@ -2544,7 +2571,7 @@ class AiHubServer {
|
|
|
2544
2571
|
this.app.get('/api/ai-hub/context', (req, res) => {
|
|
2545
2572
|
const projectPath = typeof req.query.projectPath === 'string' && req.query.projectPath.length > 0
|
|
2546
2573
|
? path_1.default.resolve(req.query.projectPath)
|
|
2547
|
-
: this.
|
|
2574
|
+
: this.defaultProjectPath();
|
|
2548
2575
|
const rawKey = typeof req.query.key === 'string' ? req.query.key : undefined;
|
|
2549
2576
|
if (rawKey !== undefined) {
|
|
2550
2577
|
if (!(0, learning_context_builder_1.isTeamContextKey)(rawKey)) {
|
|
@@ -2568,7 +2595,7 @@ class AiHubServer {
|
|
|
2568
2595
|
}
|
|
2569
2596
|
const projectPath = typeof body.projectPath === 'string' && body.projectPath.length > 0
|
|
2570
2597
|
? path_1.default.resolve(body.projectPath)
|
|
2571
|
-
: this.
|
|
2598
|
+
: this.defaultProjectPath();
|
|
2572
2599
|
const loc = (0, learning_context_builder_1.resolveTeamContextFile)(projectPath, body.key);
|
|
2573
2600
|
if (loc.managedByOrgSync || loc.managedByManagerSync || !loc.writePath) {
|
|
2574
2601
|
// Enforcement only: block editing a synced org file (it would be
|
|
@@ -2607,14 +2634,14 @@ class AiHubServer {
|
|
|
2607
2634
|
this.app.get('/api/ai-hub/brand', (req, res) => {
|
|
2608
2635
|
const projectPath = typeof req.query.projectPath === 'string' && req.query.projectPath.length > 0
|
|
2609
2636
|
? path_1.default.resolve(req.query.projectPath)
|
|
2610
|
-
: this.
|
|
2637
|
+
: this.defaultProjectPath();
|
|
2611
2638
|
return res.json({ brand: (0, learning_context_builder_1.readOrgBrand)(projectPath) });
|
|
2612
2639
|
});
|
|
2613
2640
|
this.app.post('/api/ai-hub/brand', (req, res) => {
|
|
2614
2641
|
const body = (req.body ?? {});
|
|
2615
2642
|
const projectPath = typeof body.projectPath === 'string' && body.projectPath.length > 0
|
|
2616
2643
|
? path_1.default.resolve(body.projectPath)
|
|
2617
|
-
: this.
|
|
2644
|
+
: this.defaultProjectPath();
|
|
2618
2645
|
const input = {
|
|
2619
2646
|
name: typeof body.name === 'string' ? body.name : undefined,
|
|
2620
2647
|
color: typeof body.color === 'string' ? body.color : undefined,
|
|
@@ -2640,7 +2667,7 @@ class AiHubServer {
|
|
|
2640
2667
|
const rawPath = typeof req.body?.path === 'string' ? req.body.path : '';
|
|
2641
2668
|
const projectPath = typeof req.body?.projectPath === 'string' && req.body.projectPath.length > 0
|
|
2642
2669
|
? path_1.default.resolve(req.body.projectPath)
|
|
2643
|
-
: this.
|
|
2670
|
+
: this.defaultProjectPath();
|
|
2644
2671
|
if (!rawPath)
|
|
2645
2672
|
return res.status(400).json({ error: 'path is required.' });
|
|
2646
2673
|
const resolved = resolveSafeArtifactPath(rawPath, projectPath);
|
|
@@ -2825,7 +2852,7 @@ class AiHubServer {
|
|
|
2825
2852
|
});
|
|
2826
2853
|
this.app.post('/api/ai-hub/runs', (req, res) => {
|
|
2827
2854
|
try {
|
|
2828
|
-
const projectPath = ensureDirectoryPath(req.body.projectPath || this.
|
|
2855
|
+
const projectPath = ensureDirectoryPath(req.body.projectPath || this.defaultProjectPath());
|
|
2829
2856
|
const requestedHostId = req.body.hostId;
|
|
2830
2857
|
const instructions = (req.body.instructions || '').trim();
|
|
2831
2858
|
const legacyMessage = (req.body.message || '').trim();
|
|
@@ -2876,7 +2903,7 @@ class AiHubServer {
|
|
|
2876
2903
|
phaseHistory: [],
|
|
2877
2904
|
totals: emptyTotals(),
|
|
2878
2905
|
lastStatusChangeAt: startTimestamp,
|
|
2879
|
-
personaKey: jobMetadata?.personaKey ??
|
|
2906
|
+
personaKey: jobMetadata?.personaKey ?? getHubPersonaForJob(jobId),
|
|
2880
2907
|
// Issue #442: mark this as the FRAIM side of an A/B pair when applicable.
|
|
2881
2908
|
...(compareMode === 'ab' ? { runRole: 'fraim' } : {}),
|
|
2882
2909
|
// #0: trigger source — defaults to 'manager' when not provided by the caller.
|
|
@@ -3172,7 +3199,7 @@ class AiHubServer {
|
|
|
3172
3199
|
this.app.post('/api/ai-hub/runs/resume', (req, res) => {
|
|
3173
3200
|
try {
|
|
3174
3201
|
const body = (req.body ?? {});
|
|
3175
|
-
const projectPath = ensureDirectoryPath(body.projectPath || this.
|
|
3202
|
+
const projectPath = ensureDirectoryPath(body.projectPath || this.defaultProjectPath());
|
|
3176
3203
|
const requestedHostId = body.hostId;
|
|
3177
3204
|
const sessionId = (body.sessionId || '').trim();
|
|
3178
3205
|
const jobId = (body.jobId || '').trim();
|
|
@@ -3206,7 +3233,7 @@ class AiHubServer {
|
|
|
3206
3233
|
totals: persistedRun?.totals || emptyTotals(),
|
|
3207
3234
|
lastStatusChangeAt: now,
|
|
3208
3235
|
runDiscriminant: persistedRun?.runDiscriminant || undefined,
|
|
3209
|
-
personaKey:
|
|
3236
|
+
personaKey: getHubPersonaForJob(jobId),
|
|
3210
3237
|
};
|
|
3211
3238
|
// Continue-turn message (FRAIM invocation for the job + instructions) plus
|
|
3212
3239
|
// the shared-browser note so the resumed agent knows about it.
|
|
@@ -3378,7 +3405,7 @@ class AiHubServer {
|
|
|
3378
3405
|
type: 'scheduled',
|
|
3379
3406
|
label,
|
|
3380
3407
|
jobId,
|
|
3381
|
-
projectPath: ensureDirectoryPath(projectPath || this.
|
|
3408
|
+
projectPath: ensureDirectoryPath(projectPath || this.defaultProjectPath()),
|
|
3382
3409
|
hostId: resolvedHostId,
|
|
3383
3410
|
...(typeof configuredAgentId === 'string' && configuredAgentId.trim() ? { configuredAgentId: configuredAgentId.trim() } : {}),
|
|
3384
3411
|
cronExpr,
|
|
@@ -3396,7 +3423,7 @@ class AiHubServer {
|
|
|
3396
3423
|
// GET /api/ai-hub/schedules — list scheduled deployments for one project.
|
|
3397
3424
|
this.app.get('/api/ai-hub/schedules', (req, res) => {
|
|
3398
3425
|
try {
|
|
3399
|
-
const projectPath = deploymentProjectFilter(req.query.projectPath, this.
|
|
3426
|
+
const projectPath = deploymentProjectFilter(req.query.projectPath, this.defaultProjectPath());
|
|
3400
3427
|
return res.json(this.deploymentStore.load().filter((d) => d.type === 'scheduled' && deploymentBelongsToProject(d, projectPath, this.projectPath)));
|
|
3401
3428
|
}
|
|
3402
3429
|
catch (err) {
|
|
@@ -3430,7 +3457,7 @@ class AiHubServer {
|
|
|
3430
3457
|
let resolvedProjectPath;
|
|
3431
3458
|
try {
|
|
3432
3459
|
if (projectPath !== undefined)
|
|
3433
|
-
resolvedProjectPath = ensureDirectoryPath(projectPath || this.
|
|
3460
|
+
resolvedProjectPath = ensureDirectoryPath(projectPath || this.defaultProjectPath());
|
|
3434
3461
|
}
|
|
3435
3462
|
catch (err) {
|
|
3436
3463
|
return res.status(400).json({ error: err instanceof Error ? err.message : 'Invalid project path.' });
|
|
@@ -3480,7 +3507,7 @@ class AiHubServer {
|
|
|
3480
3507
|
type: 'webhook',
|
|
3481
3508
|
label,
|
|
3482
3509
|
jobId,
|
|
3483
|
-
projectPath: ensureDirectoryPath(projectPath || this.
|
|
3510
|
+
projectPath: ensureDirectoryPath(projectPath || this.defaultProjectPath()),
|
|
3484
3511
|
hostId: resolvedHostId,
|
|
3485
3512
|
...(typeof configuredAgentId === 'string' && configuredAgentId.trim() ? { configuredAgentId: configuredAgentId.trim() } : {}),
|
|
3486
3513
|
instructions: typeof instructions === 'string' ? instructions : undefined,
|
|
@@ -3497,7 +3524,7 @@ class AiHubServer {
|
|
|
3497
3524
|
this.app.get('/api/ai-hub/webhooks', (req, res) => {
|
|
3498
3525
|
const hubBase = this.hubBase;
|
|
3499
3526
|
try {
|
|
3500
|
-
const projectPath = deploymentProjectFilter(req.query.projectPath, this.
|
|
3527
|
+
const projectPath = deploymentProjectFilter(req.query.projectPath, this.defaultProjectPath());
|
|
3501
3528
|
return res.json(this.deploymentStore.load()
|
|
3502
3529
|
.filter((d) => d.type === 'webhook' && deploymentBelongsToProject(d, projectPath, this.projectPath))
|
|
3503
3530
|
.map((d) => ({ ...d, inboundUrl: `${hubBase}/api/ai-hub/webhooks/${d.id}/inbound` })));
|
|
@@ -3520,7 +3547,7 @@ class AiHubServer {
|
|
|
3520
3547
|
let resolvedProjectPath;
|
|
3521
3548
|
try {
|
|
3522
3549
|
if (projectPath !== undefined)
|
|
3523
|
-
resolvedProjectPath = ensureDirectoryPath(projectPath || this.
|
|
3550
|
+
resolvedProjectPath = ensureDirectoryPath(projectPath || this.defaultProjectPath());
|
|
3524
3551
|
}
|
|
3525
3552
|
catch (err) {
|
|
3526
3553
|
return res.status(400).json({ error: err instanceof Error ? err.message : 'Invalid project path.' });
|
|
@@ -3623,7 +3650,7 @@ class AiHubServer {
|
|
|
3623
3650
|
if (!jobName) {
|
|
3624
3651
|
return res.status(400).json({ error: 'jobName is required.' });
|
|
3625
3652
|
}
|
|
3626
|
-
const projectPath = ensureDirectoryPath(reqProjectPath || this.
|
|
3653
|
+
const projectPath = ensureDirectoryPath(reqProjectPath || this.defaultProjectPath());
|
|
3627
3654
|
// Use the requested agent directly — caller specifies which agent to run (claude, codex, gemini).
|
|
3628
3655
|
const requestedAgentId = req.body.configuredAgentId || employeeId;
|
|
3629
3656
|
const requestedHostId = VALID_EMPLOYEE_IDS.includes(employeeId) ? employeeId : undefined;
|
|
@@ -3657,7 +3684,7 @@ class AiHubServer {
|
|
|
3657
3684
|
phaseHistory: [],
|
|
3658
3685
|
totals: emptyTotals(),
|
|
3659
3686
|
lastStatusChangeAt: startTimestamp,
|
|
3660
|
-
personaKey:
|
|
3687
|
+
personaKey: getHubPersonaForJob(jobName),
|
|
3661
3688
|
};
|
|
3662
3689
|
// Register the run before spawning so onEvent/onExit callbacks can
|
|
3663
3690
|
// safely call update() even if they fire synchronously (FakeHostRuntime).
|
|
@@ -3775,7 +3802,7 @@ class AiHubServer {
|
|
|
3775
3802
|
phaseHistory: [],
|
|
3776
3803
|
totals: emptyTotals(),
|
|
3777
3804
|
lastStatusChangeAt: startTimestamp,
|
|
3778
|
-
personaKey: jobMetadata?.personaKey ??
|
|
3805
|
+
personaKey: jobMetadata?.personaKey ?? getHubPersonaForJob(jobId),
|
|
3779
3806
|
};
|
|
3780
3807
|
// Pre-register before startRun so synchronous onEvent calls (e.g. FakeHostRuntime)
|
|
3781
3808
|
// can call runRegistry.update without "Run not found" throws.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Sideloads the Word add-in manifest so it appears in Word's Developer Add-ins
|
|
4
|
+
* list without admin rights or AppSource publishing.
|
|
5
|
+
*
|
|
6
|
+
* Windows: writes a registry value under HKCU\SOFTWARE\Microsoft\Office\16.0\WEF\Developer
|
|
7
|
+
* macOS: writes an entry to ~/Library/Containers/com.microsoft.Word/Data/Documents/wef/
|
|
8
|
+
*
|
|
9
|
+
* Both paths are non-admin and survive app updates (keyed by manifest GUID).
|
|
10
|
+
* Safe to call multiple times — checks before writing.
|
|
11
|
+
*/
|
|
12
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
13
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
14
|
+
};
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.isSideloaded = isSideloaded;
|
|
17
|
+
exports.sideloadManifest = sideloadManifest;
|
|
18
|
+
exports.removeSideload = removeSideload;
|
|
19
|
+
const fs_1 = __importDefault(require("fs"));
|
|
20
|
+
const path_1 = __importDefault(require("path"));
|
|
21
|
+
const os_1 = __importDefault(require("os"));
|
|
22
|
+
const child_process_1 = require("child_process");
|
|
23
|
+
const MANIFEST_GUID = 'd1090951-50cf-4cf2-9d12-b0f8541d265c';
|
|
24
|
+
function resolveManifestPath(projectPath) {
|
|
25
|
+
const candidates = [
|
|
26
|
+
path_1.default.resolve(projectPath, 'extensions/office-word/manifest.xml'),
|
|
27
|
+
path_1.default.resolve(__dirname, '..', '..', 'extensions/office-word/manifest.xml'),
|
|
28
|
+
path_1.default.resolve(__dirname, '..', '..', '..', 'extensions/office-word/manifest.xml'),
|
|
29
|
+
];
|
|
30
|
+
return candidates.find(c => fs_1.default.existsSync(c)) ?? null;
|
|
31
|
+
}
|
|
32
|
+
function isSideloaded() {
|
|
33
|
+
if (process.platform === 'win32') {
|
|
34
|
+
const result = (0, child_process_1.spawnSync)('reg', [
|
|
35
|
+
'query',
|
|
36
|
+
`HKCU\\SOFTWARE\\Microsoft\\Office\\16.0\\WEF\\Developer`,
|
|
37
|
+
'/v', MANIFEST_GUID,
|
|
38
|
+
], { encoding: 'utf8' });
|
|
39
|
+
return result.status === 0 && result.stdout.includes(MANIFEST_GUID);
|
|
40
|
+
}
|
|
41
|
+
if (process.platform === 'darwin') {
|
|
42
|
+
const wefDir = path_1.default.join(os_1.default.homedir(), 'Library', 'Containers', 'com.microsoft.Word', 'Data', 'Documents', 'wef');
|
|
43
|
+
return fs_1.default.existsSync(path_1.default.join(wefDir, `${MANIFEST_GUID}.xml`));
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
function sideloadManifest(projectPath) {
|
|
48
|
+
const manifestPath = resolveManifestPath(projectPath);
|
|
49
|
+
if (!manifestPath) {
|
|
50
|
+
return { ok: false, reason: 'Manifest file not found — is extensions/office-word/ present?' };
|
|
51
|
+
}
|
|
52
|
+
if (process.platform === 'win32') {
|
|
53
|
+
const result = (0, child_process_1.spawnSync)('reg', [
|
|
54
|
+
'add',
|
|
55
|
+
`HKCU\\SOFTWARE\\Microsoft\\Office\\16.0\\WEF\\Developer`,
|
|
56
|
+
'/v', MANIFEST_GUID,
|
|
57
|
+
'/t', 'REG_SZ',
|
|
58
|
+
'/d', manifestPath,
|
|
59
|
+
'/f',
|
|
60
|
+
], { encoding: 'utf8' });
|
|
61
|
+
if (result.status !== 0) {
|
|
62
|
+
return { ok: false, reason: result.stderr || 'reg add failed' };
|
|
63
|
+
}
|
|
64
|
+
return { ok: true };
|
|
65
|
+
}
|
|
66
|
+
if (process.platform === 'darwin') {
|
|
67
|
+
const wefDir = path_1.default.join(os_1.default.homedir(), 'Library', 'Containers', 'com.microsoft.Word', 'Data', 'Documents', 'wef');
|
|
68
|
+
try {
|
|
69
|
+
fs_1.default.mkdirSync(wefDir, { recursive: true });
|
|
70
|
+
fs_1.default.copyFileSync(manifestPath, path_1.default.join(wefDir, `${MANIFEST_GUID}.xml`));
|
|
71
|
+
return { ok: true };
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
return { ok: false, reason: String(err) };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { ok: false, reason: `Unsupported platform: ${process.platform}` };
|
|
78
|
+
}
|
|
79
|
+
function removeSideload() {
|
|
80
|
+
if (process.platform === 'win32') {
|
|
81
|
+
(0, child_process_1.spawnSync)('reg', [
|
|
82
|
+
'delete',
|
|
83
|
+
`HKCU\\SOFTWARE\\Microsoft\\Office\\16.0\\WEF\\Developer`,
|
|
84
|
+
'/v', MANIFEST_GUID,
|
|
85
|
+
'/f',
|
|
86
|
+
], { encoding: 'utf8' });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (process.platform === 'darwin') {
|
|
90
|
+
const wefDir = path_1.default.join(os_1.default.homedir(), 'Library', 'Containers', 'com.microsoft.Word', 'Data', 'Documents', 'wef');
|
|
91
|
+
const target = path_1.default.join(wefDir, `${MANIFEST_GUID}.xml`);
|
|
92
|
+
if (fs_1.default.existsSync(target))
|
|
93
|
+
fs_1.default.unlinkSync(target);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -33,7 +33,7 @@ function createFraimHubProgram(action = cli_1.runHub) {
|
|
|
33
33
|
.description('Start the FRAIM Hub local companion')
|
|
34
34
|
.version(readPackageVersion())
|
|
35
35
|
.option('--port <port>', 'Preferred local port for the hub', (value) => Number(value), 43091)
|
|
36
|
-
.option('--project-path <path>', 'Initial project path for job discovery'
|
|
36
|
+
.option('--project-path <path>', 'Initial project path for job discovery')
|
|
37
37
|
.option('--no-open', 'Do not open the hub after startup')
|
|
38
38
|
.option('--browser', 'Open in the default browser instead of the desktop shell')
|
|
39
39
|
.option('--restart', 'Replace any running Hub instance with this build (even at the same version)')
|
|
@@ -96,18 +96,18 @@ ${buildFraimInvocationBody('generic-tool-discovery')}
|
|
|
96
96
|
`;
|
|
97
97
|
}
|
|
98
98
|
function buildCodexSkillContent() {
|
|
99
|
-
return `# FRAIM
|
|
100
|
-
|
|
99
|
+
return `# FRAIM
|
|
100
|
+
|
|
101
101
|
${buildFraimInvocationBody('codex-tool-search')}`;
|
|
102
102
|
}
|
|
103
103
|
function buildGrokSkillContent() {
|
|
104
|
-
return `# FRAIM
|
|
105
|
-
|
|
104
|
+
return `# FRAIM
|
|
105
|
+
|
|
106
106
|
${buildFraimInvocationBody('generic-tool-discovery')}`;
|
|
107
107
|
}
|
|
108
108
|
function buildWindsurfCommandContent() {
|
|
109
|
-
return `# FRAIM
|
|
110
|
-
|
|
109
|
+
return `# FRAIM
|
|
110
|
+
|
|
111
111
|
${buildFraimInvocationBody('generic-tool-discovery')}`;
|
|
112
112
|
}
|
|
113
113
|
function buildKiroCommandContent() {
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>FRAIM Hub</title>
|
|
7
|
-
<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js" type="text/javascript"></script>
|
|
8
|
-
<script src="config.js" type="text/javascript"></script>
|
|
6
|
+
<title>FRAIM Hub</title>
|
|
7
|
+
<script src="https://appsforoffice.microsoft.com/lib/1/hosted/office.js" type="text/javascript"></script>
|
|
8
|
+
<script src="config.js" type="text/javascript"></script>
|
|
9
9
|
<style>
|
|
10
10
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
11
11
|
html, body { height: 100%; overflow: hidden; }
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
// Loading an HTTP iframe from an HTTPS page is mixed-content-blocked, so use the
|
|
20
20
|
// same origin as the taskpane when running over HTTPS (the ssl-proxy forwards all
|
|
21
21
|
// routes to the Hub). HTTP (isolated tests) keeps the direct Hub address.
|
|
22
|
-
var HUB_ORIGIN = window.location.protocol === 'https:'
|
|
23
|
-
? window.location.origin
|
|
24
|
-
: (window.FRAIM_HUB_ORIGIN || 'http://127.0.0.1:43091');
|
|
22
|
+
var HUB_ORIGIN = window.location.protocol === 'https:'
|
|
23
|
+
? window.location.origin
|
|
24
|
+
: (window.FRAIM_HUB_ORIGIN || 'http://127.0.0.1:43091');
|
|
25
25
|
var hubFrame = document.getElementById('hub');
|
|
26
26
|
var pendingPush = null; // context queued before hub-ready fires
|
|
27
27
|
var hubReady = false;
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-hub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.208",
|
|
4
4
|
"description": "FRAIM Hub local companion package.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"dotenv": "^16.4.7",
|
|
90
90
|
"electron": "^41.2.2",
|
|
91
91
|
"express": "^5.2.1",
|
|
92
|
-
"fraim": "2.0.
|
|
92
|
+
"fraim": "2.0.208",
|
|
93
93
|
"mongodb": "^7.0.0",
|
|
94
94
|
"node-cron": "4.2.1",
|
|
95
95
|
"node-edge-tts": "^1.2.10",
|