fraim-hub 2.0.312 → 2.0.314
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/catalog.js +37 -10
- package/dist/src/ai-hub/server.js +19 -0
- package/package.json +2 -2
- package/public/ai-hub/script.js +0 -1
|
@@ -19,12 +19,31 @@ const pack_home_1 = require("../cli/utils/pack-home");
|
|
|
19
19
|
// Directories scanned for employee jobs at runtime, in lowest-to-highest
|
|
20
20
|
// precedence order. Later entries win on {categoryId, jobId} collision.
|
|
21
21
|
//
|
|
22
|
-
// FRAIM execution normally reads
|
|
23
|
-
//
|
|
22
|
+
// FRAIM execution normally reads three layers: the machine-level baseline
|
|
23
|
+
// (shared across every project on this machine), the project-level synced
|
|
24
|
+
// baseline, and the personalized override. The Hub can explicitly opt into the
|
|
24
25
|
// source `registry/` fallback when running against the FRAIM repo before sync.
|
|
25
|
-
// -
|
|
26
|
-
// - <project>/fraim/
|
|
26
|
+
// - ~/.fraim/ai-employee/jobs/<category>/ - machine-level baseline (issue #1644)
|
|
27
|
+
// - <project>/fraim/ai-employee/jobs/<category>/ - synced baseline
|
|
28
|
+
// - <project>/fraim/personalized-employee/jobs/<category>/ - taught/customized override
|
|
27
29
|
// Only the personalized-employee layer is "personalized" (issue #566).
|
|
30
|
+
//
|
|
31
|
+
// Issue #1644: `fraim sync` Layer 1 (always runs, regardless of whether cwd is
|
|
32
|
+
// a FRAIM project) writes baseline job stubs to ~/.fraim/ai-employee/jobs/ and
|
|
33
|
+
// ~/.fraim/ai-manager/jobs/ so every project on the machine sees the current
|
|
34
|
+
// catalog without its own project-level sync. This is the lowest-precedence
|
|
35
|
+
// layer: a project- or personalized-level stub with the same id always wins.
|
|
36
|
+
//
|
|
37
|
+
// Resolved as a function, not a module-level constant: getUserFraimDirPath()
|
|
38
|
+
// reads FRAIM_USER_DIR at call time, and callers (including tests that
|
|
39
|
+
// isolate a temp machine home) need that resolved fresh on every discovery
|
|
40
|
+
// call, not frozen at module-import time.
|
|
41
|
+
function machineJobLayers(role) {
|
|
42
|
+
const root = path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), role, 'jobs');
|
|
43
|
+
// Issue #1002 convention: a machine-level stub displays as `~/.fraim/...`,
|
|
44
|
+
// not a long `../../..` walk out of the project.
|
|
45
|
+
return [{ root, segments: [], personalized: false, displayRoot: root, displayPrefix: (0, project_fraim_paths_1.getUserFraimDisplayPath)(`${role}/jobs`) }];
|
|
46
|
+
}
|
|
28
47
|
const EMPLOYEE_JOB_LAYERS = [
|
|
29
48
|
{ segments: ['ai-employee', 'jobs'], personalized: false },
|
|
30
49
|
];
|
|
@@ -90,8 +109,14 @@ function resolveExtendedStubPath(projectPath, extendsValue) {
|
|
|
90
109
|
if (!relative || relative.includes('..'))
|
|
91
110
|
return null;
|
|
92
111
|
const candidateRoots = [
|
|
112
|
+
// Project-level checked first, matching the precedence used everywhere
|
|
113
|
+
// else in this file (project overrides machine on a name collision).
|
|
93
114
|
path_1.default.join(projectPath, 'fraim', 'ai-employee', 'jobs'),
|
|
94
115
|
path_1.default.join(projectPath, 'fraim', 'ai-manager', 'jobs'),
|
|
116
|
+
// Issue #1644: the baseline a stub `extends` may exist only at the
|
|
117
|
+
// machine-level home now that Layer 1 writes it there.
|
|
118
|
+
path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), 'ai-employee', 'jobs'),
|
|
119
|
+
path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), 'ai-manager', 'jobs'),
|
|
95
120
|
path_1.default.join(projectPath, 'registry', 'jobs', 'ai-employee'),
|
|
96
121
|
path_1.default.join(projectPath, 'registry', 'jobs', 'ai-manager'),
|
|
97
122
|
];
|
|
@@ -286,8 +311,8 @@ function discoverEmployeeJobs(projectPath, options = {}) {
|
|
|
286
311
|
if (!project.exists || (!project.hasFraim && !options.includeRegistry))
|
|
287
312
|
return [];
|
|
288
313
|
const layers = discoverLayers(projectPath, options.includeRegistry
|
|
289
|
-
? [...REGISTRY_EMPLOYEE_JOB_LAYERS, ...EMPLOYEE_JOB_LAYERS, ...personalizedLayerSegments(projectPath, 'jobs')]
|
|
290
|
-
: [...EMPLOYEE_JOB_LAYERS, ...personalizedLayerSegments(projectPath, 'jobs')]);
|
|
314
|
+
? [...REGISTRY_EMPLOYEE_JOB_LAYERS, ...machineJobLayers('ai-employee'), ...EMPLOYEE_JOB_LAYERS, ...personalizedLayerSegments(projectPath, 'jobs')]
|
|
315
|
+
: [...machineJobLayers('ai-employee'), ...EMPLOYEE_JOB_LAYERS, ...personalizedLayerSegments(projectPath, 'jobs')]);
|
|
291
316
|
// Group by categoryId so all layers contribute to the same labelled category.
|
|
292
317
|
const jobsByKey = new Map();
|
|
293
318
|
for (const layer of layers) {
|
|
@@ -313,8 +338,8 @@ function discoverManagerTemplates(projectPath, options = {}) {
|
|
|
313
338
|
if (!project.exists || (!project.hasFraim && !options.includeRegistry))
|
|
314
339
|
return [];
|
|
315
340
|
const layers = discoverLayers(projectPath, options.includeRegistry
|
|
316
|
-
? [...REGISTRY_MANAGER_JOB_LAYERS, ...MANAGER_JOB_LAYERS, ...personalizedLayerSegments(projectPath, 'manager-jobs')]
|
|
317
|
-
: [...MANAGER_JOB_LAYERS, ...personalizedLayerSegments(projectPath, 'manager-jobs')]);
|
|
341
|
+
? [...REGISTRY_MANAGER_JOB_LAYERS, ...machineJobLayers('ai-manager'), ...MANAGER_JOB_LAYERS, ...personalizedLayerSegments(projectPath, 'manager-jobs')]
|
|
342
|
+
: [...machineJobLayers('ai-manager'), ...MANAGER_JOB_LAYERS, ...personalizedLayerSegments(projectPath, 'manager-jobs')]);
|
|
318
343
|
const templatesByKey = new Map();
|
|
319
344
|
for (const layer of layers) {
|
|
320
345
|
const groupLabel = humanizeName(layer.categoryId);
|
|
@@ -401,6 +426,8 @@ function findJobStubPath(projectPath, jobId) {
|
|
|
401
426
|
// inspect any FRAIM job run, including manager-side jobs such as
|
|
402
427
|
// sleep-on-learnings.
|
|
403
428
|
const layers = discoverLayers(projectPath, [
|
|
429
|
+
...machineJobLayers('ai-employee'),
|
|
430
|
+
...machineJobLayers('ai-manager'),
|
|
404
431
|
...EMPLOYEE_JOB_LAYERS,
|
|
405
432
|
...MANAGER_JOB_LAYERS,
|
|
406
433
|
...personalizedLayerSegments(projectPath, 'jobs'),
|
|
@@ -518,8 +545,8 @@ function getAiHubCategories(projectPath, options = {}) {
|
|
|
518
545
|
return [];
|
|
519
546
|
// A category is any directory found at any layer; deduplicate by id.
|
|
520
547
|
const layers = discoverLayers(projectPath, options.includeRegistry
|
|
521
|
-
? [...REGISTRY_EMPLOYEE_JOB_LAYERS, ...EMPLOYEE_JOB_LAYERS, ...personalizedLayerSegments(projectPath, 'jobs')]
|
|
522
|
-
: [...EMPLOYEE_JOB_LAYERS, ...personalizedLayerSegments(projectPath, 'jobs')]);
|
|
548
|
+
? [...REGISTRY_EMPLOYEE_JOB_LAYERS, ...machineJobLayers('ai-employee'), ...EMPLOYEE_JOB_LAYERS, ...personalizedLayerSegments(projectPath, 'jobs')]
|
|
549
|
+
: [...machineJobLayers('ai-employee'), ...EMPLOYEE_JOB_LAYERS, ...personalizedLayerSegments(projectPath, 'jobs')]);
|
|
523
550
|
const seen = new Map();
|
|
524
551
|
for (const layer of layers) {
|
|
525
552
|
if (!seen.has(layer.categoryId)) {
|
|
@@ -41,6 +41,7 @@ exports.selfHealBrokenManagedAgents = selfHealBrokenManagedAgents;
|
|
|
41
41
|
exports.configureFraimForHubAgent = configureFraimForHubAgent;
|
|
42
42
|
exports.installAgentAndRefreshDetection = installAgentAndRefreshDetection;
|
|
43
43
|
exports.hubCommandVersion = hubCommandVersion;
|
|
44
|
+
exports.__setStartupSyncSpawnForTests = __setStartupSyncSpawnForTests;
|
|
44
45
|
exports.__setAgentInstallTimeoutMsForTests = __setAgentInstallTimeoutMsForTests;
|
|
45
46
|
exports.hubRunProcess = hubRunProcess;
|
|
46
47
|
exports.buildOpenFileInvocation = buildOpenFileInvocation;
|
|
@@ -2147,6 +2148,13 @@ function hubRunSync(command, args, env) {
|
|
|
2147
2148
|
// (typically seconds) while still bounding the failure the way AC-C1 requires.
|
|
2148
2149
|
const DEFAULT_AGENT_INSTALL_TIMEOUT_MS = 90_000;
|
|
2149
2150
|
let agentInstallTimeoutMs = DEFAULT_AGENT_INSTALL_TIMEOUT_MS;
|
|
2151
|
+
// Test seam for the startup machine-level sync (Issue #1645). Allows tests to
|
|
2152
|
+
// intercept the spawn call without actually invoking npx.
|
|
2153
|
+
let startupSyncSpawnFn = child_process_1.spawn;
|
|
2154
|
+
/** Test seam only. Pass null to restore the production spawn. */
|
|
2155
|
+
function __setStartupSyncSpawnForTests(fn) {
|
|
2156
|
+
startupSyncSpawnFn = fn ?? child_process_1.spawn;
|
|
2157
|
+
}
|
|
2150
2158
|
/** Test seam only. Mirrors __setEmployeeDetectionTtlForTests in hosts.ts — proves the bound
|
|
2151
2159
|
* actually fires without a real 90s hang. Pass null to restore the production value. */
|
|
2152
2160
|
function __setAgentInstallTimeoutMsForTests(ms) {
|
|
@@ -3265,6 +3273,17 @@ class AiHubServer {
|
|
|
3265
3273
|
console.warn('[ai-hub] self-heal pass failed:', error?.message || error);
|
|
3266
3274
|
});
|
|
3267
3275
|
}
|
|
3276
|
+
// Issue #1645: fire-and-forget machine-level sync so the delegate job list
|
|
3277
|
+
// is current within the session. Skipped in NODE_ENV=test to avoid spawning
|
|
3278
|
+
// real processes in the test suite's many short-lived Hub instances.
|
|
3279
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
3280
|
+
const syncChild = startupSyncSpawnFn('npx', ['fraim', 'sync', '--global'], {
|
|
3281
|
+
detached: true,
|
|
3282
|
+
stdio: 'ignore',
|
|
3283
|
+
});
|
|
3284
|
+
syncChild.on('error', () => { });
|
|
3285
|
+
syncChild.unref();
|
|
3286
|
+
}
|
|
3268
3287
|
// Update-badge freshness: refresh the npm-latest cache on our own clock, not just when
|
|
3269
3288
|
// a page load or relaunch decision happens to ask for it. Without this, a Hub that's
|
|
3270
3289
|
// resident in the tray for days with no window reload and no tray re-click never learns
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-hub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.314",
|
|
4
4
|
"description": "FRAIM Hub local companion package.",
|
|
5
5
|
"author": "Sid Mathur <sid.mathur@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/mathursrus/FRAIM#readme",
|
|
@@ -211,7 +211,7 @@
|
|
|
211
211
|
"electron-updater": "^6.8.9",
|
|
212
212
|
"express": "^5.2.1",
|
|
213
213
|
"extract-zip": "^2.0.1",
|
|
214
|
-
"fraim": "2.0.
|
|
214
|
+
"fraim": "2.0.314",
|
|
215
215
|
"mongodb": "^7.0.0",
|
|
216
216
|
"node-cron": "4.2.1",
|
|
217
217
|
"node-edge-tts": "^1.2.10",
|
package/public/ai-hub/script.js
CHANGED
|
@@ -5669,7 +5669,6 @@ function scrollThreadAfterViewportSync(conv, shouldScrollForUpdate, forceBottom)
|
|
|
5669
5669
|
if (latest.status === 'running') {
|
|
5670
5670
|
ensurePendingAnchorScrollListener(host);
|
|
5671
5671
|
if (!forceBottom && pendingAnchorUserScrolled(host)) {
|
|
5672
|
-
host.scrollTop = 0;
|
|
5673
5672
|
return;
|
|
5674
5673
|
}
|
|
5675
5674
|
const pendingRow = host.querySelector('.message[data-pending="true"]');
|