fraim-hub 2.0.256 → 2.0.258
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/custom-employees.js +1 -1
- package/dist/src/ai-hub/hosts.js +4 -3
- package/dist/src/cli/utils/org-publish.js +6 -4
- package/dist/src/cli/utils/pack-home.js +63 -31
- package/dist/src/local-mcp-server/learning-context-builder.js +17 -10
- package/package.json +2 -2
- package/public/ai-hub/script.js +29 -0
|
@@ -32,7 +32,7 @@ function layerEmployeeReadDirs(layer) {
|
|
|
32
32
|
}
|
|
33
33
|
/** The single writable employees directory for a layer. */
|
|
34
34
|
function layerEmployeeWriteDir(layer) {
|
|
35
|
-
return path_1.default.join((0, pack_home_1.resolvePackHome)(layer).
|
|
35
|
+
return path_1.default.join((0, pack_home_1.resolvePackHome)(layer).contentRoot, EMPLOYEES_SUBDIR);
|
|
36
36
|
}
|
|
37
37
|
function readEmployeesFromDir(dir, scope) {
|
|
38
38
|
if (!fs_1.default.existsSync(dir))
|
package/dist/src/ai-hub/hosts.js
CHANGED
|
@@ -35,6 +35,7 @@ const managed_agent_paths_1 = require("../cli/utils/managed-agent-paths");
|
|
|
35
35
|
const mcp_config_generator_1 = require("../cli/setup/mcp-config-generator");
|
|
36
36
|
const agent_token_prices_1 = require("../local-mcp-server/agent-token-prices");
|
|
37
37
|
const configured_agents_1 = require("./configured-agents");
|
|
38
|
+
const pack_home_1 = require("../cli/utils/pack-home");
|
|
38
39
|
// Parse a single line of host stdout looking for a seekMentoring tool-use
|
|
39
40
|
// signal. Returns null if the line does not contain one. Supports both
|
|
40
41
|
// hosts FRAIM ships against today:
|
|
@@ -1084,10 +1085,10 @@ function transformHeadlessFraimMessage(message, kind) {
|
|
|
1084
1085
|
}
|
|
1085
1086
|
function machineLevelStorageGuard(jobId) {
|
|
1086
1087
|
const normalized = jobId.toLowerCase();
|
|
1087
|
-
const userFraim = path_1.default.join(os_1.default.homedir(), '.fraim');
|
|
1088
1088
|
if (normalized === 'manager-agreements') {
|
|
1089
|
-
const
|
|
1090
|
-
const
|
|
1089
|
+
const managerContentRoot = (0, pack_home_1.resolvePackHome)('manager').contentRoot;
|
|
1090
|
+
const managerContext = path_1.default.join(managerContentRoot, 'context', 'manager_context.md');
|
|
1091
|
+
const managerRules = path_1.default.join(managerContentRoot, 'rules', 'manager_rules.md');
|
|
1091
1092
|
return [
|
|
1092
1093
|
'Storage scope guardrail:',
|
|
1093
1094
|
'- Manager agreements artifacts are machine-level, not repo-level.',
|
|
@@ -78,16 +78,18 @@ async function publishOrgArtifacts(artifacts, opts) {
|
|
|
78
78
|
throw new Error(`Invalid org-pack deletion path '${rel}'.`);
|
|
79
79
|
}
|
|
80
80
|
if (organization.backend === 'local-folder') {
|
|
81
|
-
|
|
81
|
+
// Write into contentRoot (localPath/personalized-employee/) so the read
|
|
82
|
+
// side (packReadRoots -> contentRoot) sees the file immediately.
|
|
83
|
+
const contentRoot = (0, pack_home_1.resolvePackHome)('org').contentRoot;
|
|
82
84
|
const writtenPaths = [];
|
|
83
85
|
for (const a of artifacts) {
|
|
84
|
-
const result = (0, local_folder_sync_1.writeLocalFolderFile)(
|
|
86
|
+
const result = (0, local_folder_sync_1.writeLocalFolderFile)(contentRoot, a.relativePath, a.content);
|
|
85
87
|
if (result.skipped) {
|
|
86
88
|
throw new Error(`Skipped writing '${a.relativePath}' to local folder: ${result.reason} Re-sync before publishing.`);
|
|
87
89
|
}
|
|
88
|
-
writtenPaths.push(path_1.default.join(
|
|
90
|
+
writtenPaths.push(path_1.default.join(contentRoot, a.relativePath));
|
|
89
91
|
}
|
|
90
|
-
const deletedPaths = (0, local_folder_sync_1.removeLocalFolderFiles)(
|
|
92
|
+
const deletedPaths = (0, local_folder_sync_1.removeLocalFolderFiles)(contentRoot, deletions, artifacts.map((a) => a.relativePath));
|
|
91
93
|
return { backend: 'local-folder', writtenPaths, deletedPaths };
|
|
92
94
|
}
|
|
93
95
|
if (organization.backend === 'fraim-cloud') {
|
|
@@ -50,18 +50,52 @@ function packsDir() {
|
|
|
50
50
|
return path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), 'packs');
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
* The standard local home, used when the layer is not
|
|
53
|
+
* The standard local home root (the pack repo root), used when the layer is not
|
|
54
|
+
* synced anywhere.
|
|
54
55
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
56
|
+
* For manager: `~/.fraim/` — capability content lives at
|
|
57
|
+
* `~/.fraim/personalized-employee/`, matching the project-level layout exactly.
|
|
58
|
+
* The historical on-disk path (`~/.fraim/personalized-employee/{stuff}`) is
|
|
59
|
+
* preserved via `contentRoot = join(root, 'personalized-employee')`.
|
|
60
|
+
*
|
|
61
|
+
* For org: `~/.fraim/org/` — capability content lives at
|
|
62
|
+
* `~/.fraim/org/personalized-employee/`. New installs land here automatically;
|
|
63
|
+
* existing installs with flat content at `~/.fraim/org/{stuff}` are migrated by
|
|
64
|
+
* `migrateOrgFlatContent`.
|
|
59
65
|
*/
|
|
60
66
|
function standardHome(layer) {
|
|
61
67
|
if (layer === 'manager')
|
|
62
|
-
return
|
|
68
|
+
return (0, project_fraim_paths_1.getUserFraimDirPath)();
|
|
63
69
|
return path_1.default.join((0, project_fraim_paths_1.getUserFraimDirPath)(), 'org');
|
|
64
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Auto-migrate flat org content (pre-#1070 layout: `~/.fraim/org/{stuff}`)
|
|
73
|
+
* to the uniform subdir layout (`~/.fraim/org/personalized-employee/{stuff}`).
|
|
74
|
+
* Runs at most once per process per orgRoot path (guarded by a Set).
|
|
75
|
+
* Never throws: migration failures are silently skipped.
|
|
76
|
+
*/
|
|
77
|
+
const _migratedOrgRoots = new Set();
|
|
78
|
+
const ORG_CONTENT_DIRS = ['context', 'rules', 'learnings', 'jobs', 'skills', 'templates', 'employees'];
|
|
79
|
+
function migrateOrgFlatContent(orgRoot) {
|
|
80
|
+
if (_migratedOrgRoots.has(orgRoot))
|
|
81
|
+
return;
|
|
82
|
+
_migratedOrgRoots.add(orgRoot);
|
|
83
|
+
const dest = path_1.default.join(orgRoot, 'personalized-employee');
|
|
84
|
+
try {
|
|
85
|
+
for (const dir of ORG_CONTENT_DIRS) {
|
|
86
|
+
const src = path_1.default.join(orgRoot, dir);
|
|
87
|
+
if (!fs_1.default.existsSync(src))
|
|
88
|
+
continue;
|
|
89
|
+
// Skip if already under personalized-employee.
|
|
90
|
+
const destDir = path_1.default.join(dest, dir);
|
|
91
|
+
if (fs_1.default.existsSync(destDir))
|
|
92
|
+
continue;
|
|
93
|
+
fs_1.default.mkdirSync(dest, { recursive: true });
|
|
94
|
+
fs_1.default.renameSync(src, destDir);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch { /* migration is best-effort; never fail a read over it */ }
|
|
98
|
+
}
|
|
65
99
|
/**
|
|
66
100
|
* Where the cloud backend materializes. Also the historical path for each
|
|
67
101
|
* layer, so switching to or from `fraim-cloud` does not relocate anything.
|
|
@@ -81,57 +115,52 @@ function defaultCloneDir(layer) {
|
|
|
81
115
|
function resolvePackHome(layer) {
|
|
82
116
|
const config = (0, user_config_1.getLayerBackend)(layer);
|
|
83
117
|
const standard = standardHome(layer);
|
|
118
|
+
const make = (root, extra) => {
|
|
119
|
+
if (layer === 'org')
|
|
120
|
+
migrateOrgFlatContent(root);
|
|
121
|
+
return { layer, root, contentRoot: path_1.default.join(root, 'personalized-employee'), ...extra };
|
|
122
|
+
};
|
|
84
123
|
if (!config || config.backend === 'single-machine') {
|
|
85
|
-
return {
|
|
86
|
-
layer,
|
|
124
|
+
return make(standard, {
|
|
87
125
|
backend: config?.backend ?? 'single-machine',
|
|
88
|
-
root: standard,
|
|
89
126
|
synced: false,
|
|
90
127
|
requiresPublish: false,
|
|
91
|
-
};
|
|
128
|
+
});
|
|
92
129
|
}
|
|
93
130
|
if (config.backend === 'local-folder' && config.localPath) {
|
|
94
|
-
return {
|
|
95
|
-
layer,
|
|
131
|
+
return make(absolutize(config.localPath), {
|
|
96
132
|
backend: 'local-folder',
|
|
97
|
-
root: absolutize(config.localPath),
|
|
98
133
|
synced: true,
|
|
99
134
|
// The sync client replicates the write; there is nothing further to do.
|
|
100
135
|
requiresPublish: false,
|
|
101
136
|
legacyRoot: standard,
|
|
102
|
-
};
|
|
137
|
+
});
|
|
103
138
|
}
|
|
104
139
|
if (config.backend === 'git' && config.gitUrl) {
|
|
105
|
-
return {
|
|
106
|
-
layer,
|
|
140
|
+
return make(config.localPath ? absolutize(config.localPath) : defaultCloneDir(layer), {
|
|
107
141
|
backend: 'git',
|
|
108
|
-
root: config.localPath ? absolutize(config.localPath) : defaultCloneDir(layer),
|
|
109
142
|
synced: true,
|
|
110
143
|
requiresPublish: true,
|
|
111
144
|
gitUrl: config.gitUrl,
|
|
112
145
|
legacyRoot: standard,
|
|
113
|
-
};
|
|
146
|
+
});
|
|
114
147
|
}
|
|
115
148
|
if (config.backend === 'fraim-cloud') {
|
|
116
|
-
return {
|
|
117
|
-
layer,
|
|
149
|
+
return make(cloudHome(layer), {
|
|
118
150
|
backend: 'fraim-cloud',
|
|
119
|
-
root: cloudHome(layer),
|
|
120
151
|
synced: true,
|
|
121
152
|
requiresPublish: true,
|
|
122
153
|
legacyRoot: standard,
|
|
123
|
-
};
|
|
154
|
+
});
|
|
124
155
|
}
|
|
125
156
|
// A backend named but missing its required pointer (git without gitUrl,
|
|
126
157
|
// local-folder without localPath). Fall back to the standard home rather
|
|
127
158
|
// than inventing a location.
|
|
128
|
-
return {
|
|
129
|
-
layer,
|
|
159
|
+
return make(standard, {
|
|
130
160
|
backend: config.backend,
|
|
131
|
-
root: standard,
|
|
132
161
|
synced: false,
|
|
133
162
|
requiresPublish: false,
|
|
134
|
-
};
|
|
163
|
+
});
|
|
135
164
|
}
|
|
136
165
|
/**
|
|
137
166
|
* Where to read this layer from. Exactly one directory: the home.
|
|
@@ -141,7 +170,7 @@ function resolvePackHome(layer) {
|
|
|
141
170
|
* entry again, the single-place invariant has been lost.
|
|
142
171
|
*/
|
|
143
172
|
function packReadRoots(layer) {
|
|
144
|
-
return [resolvePackHome(layer).
|
|
173
|
+
return [resolvePackHome(layer).contentRoot];
|
|
145
174
|
}
|
|
146
175
|
/**
|
|
147
176
|
* Content sitting at the standard local path that the configured home does not
|
|
@@ -155,9 +184,12 @@ function packReadRoots(layer) {
|
|
|
155
184
|
*/
|
|
156
185
|
function findStrandedLegacyContent(layer) {
|
|
157
186
|
const home = resolvePackHome(layer);
|
|
158
|
-
|
|
187
|
+
// legacyRoot is the old standard home root; stranded content is content that
|
|
188
|
+
// exists at the old location but not in the new contentRoot.
|
|
189
|
+
const legacyContentRoot = home.legacyRoot ? path_1.default.join(home.legacyRoot, 'personalized-employee') : null;
|
|
190
|
+
if (!legacyContentRoot || legacyContentRoot === home.contentRoot)
|
|
159
191
|
return [];
|
|
160
|
-
if (!fs_1.default.existsSync(
|
|
192
|
+
if (!fs_1.default.existsSync(legacyContentRoot))
|
|
161
193
|
return [];
|
|
162
194
|
const stranded = [];
|
|
163
195
|
const walk = (absDir, relDir) => {
|
|
@@ -173,11 +205,11 @@ function findStrandedLegacyContent(layer) {
|
|
|
173
205
|
const abs = path_1.default.join(absDir, entry.name);
|
|
174
206
|
if (entry.isDirectory())
|
|
175
207
|
walk(abs, rel);
|
|
176
|
-
else if (entry.isFile() && !fs_1.default.existsSync(path_1.default.join(home.
|
|
208
|
+
else if (entry.isFile() && !fs_1.default.existsSync(path_1.default.join(home.contentRoot, rel)))
|
|
177
209
|
stranded.push(rel);
|
|
178
210
|
}
|
|
179
211
|
};
|
|
180
|
-
walk(
|
|
212
|
+
walk(legacyContentRoot, '');
|
|
181
213
|
return stranded.sort();
|
|
182
214
|
}
|
|
183
215
|
const ALLOWED_GIT_URL = /^(https?:\/\/|ssh:\/\/|git:\/\/|file:\/\/|[\w.-]+@[\w.-]+:)/;
|
|
@@ -65,16 +65,20 @@ function learningEntryHeadingRegex(flags = '') {
|
|
|
65
65
|
function managerSecondaryLearningsBase() {
|
|
66
66
|
const home = (0, pack_home_1.resolvePackHome)('manager');
|
|
67
67
|
if (home.synced)
|
|
68
|
-
return (0, path_1.join)(home.
|
|
68
|
+
return (0, path_1.join)(home.contentRoot, 'learnings');
|
|
69
69
|
// pack-home-exempt: deliberate historical secondary location, see above.
|
|
70
70
|
return (0, path_1.join)((0, project_fraim_paths_1.getUserFraimDirPath)(), 'manager', 'learnings');
|
|
71
71
|
}
|
|
72
72
|
function getLearningRoots(workspaceRoot) {
|
|
73
|
+
const managerHome = (0, pack_home_1.resolvePackHome)('manager');
|
|
74
|
+
const managerHomeBase = (0, path_1.join)(managerHome.contentRoot, 'learnings');
|
|
73
75
|
return {
|
|
74
76
|
globalPersonalBase: (0, project_fraim_paths_1.getConfiguredPortableLearningsDir)(workspaceRoot),
|
|
75
77
|
globalPersonalDisplayBase: (0, project_fraim_paths_1.getConfiguredPortableLearningsDisplayPath)(workspaceRoot),
|
|
76
78
|
managerCacheBase: managerSecondaryLearningsBase(),
|
|
77
79
|
managerCacheDisplayBase: (0, project_fraim_paths_1.getUserFraimDisplayPath)('manager/learnings'),
|
|
80
|
+
managerHomeBase,
|
|
81
|
+
managerHomeDisplayBase: managerHomeBase.replace(/\\/g, '/'),
|
|
78
82
|
repoLearningsBase: (0, project_fraim_paths_1.getWorkspaceLearningsDir)(workspaceRoot)
|
|
79
83
|
};
|
|
80
84
|
}
|
|
@@ -406,7 +410,7 @@ function resolveOrgLearningFile(repoLearningsBase, fileName) {
|
|
|
406
410
|
if ((0, fs_1.existsSync)(repoPath)) {
|
|
407
411
|
return { present: true, path: repoPath, displayPath: `${REPO_LEARNINGS_REL}/${fileName}` };
|
|
408
412
|
}
|
|
409
|
-
const cachePath = (0, path_1.join)((0, pack_home_1.resolvePackHome)('org').
|
|
413
|
+
const cachePath = (0, path_1.join)((0, pack_home_1.resolvePackHome)('org').contentRoot, 'learnings', fileName);
|
|
410
414
|
if ((0, fs_1.existsSync)(cachePath)) {
|
|
411
415
|
return { present: true, path: cachePath, displayPath: (0, project_fraim_paths_1.getUserFraimDisplayPath)(`org/learnings/${fileName}`) };
|
|
412
416
|
}
|
|
@@ -419,7 +423,7 @@ function resolveOrgLearningFile(repoLearningsBase, fileName) {
|
|
|
419
423
|
*/
|
|
420
424
|
function resolveOrgLearningFileTiers(repoLearningsBase, fileName) {
|
|
421
425
|
const tiers = [];
|
|
422
|
-
const cachePath = (0, path_1.join)((0, pack_home_1.resolvePackHome)('org').
|
|
426
|
+
const cachePath = (0, path_1.join)((0, pack_home_1.resolvePackHome)('org').contentRoot, 'learnings', fileName);
|
|
423
427
|
if ((0, fs_1.existsSync)(cachePath)) {
|
|
424
428
|
tiers.push({
|
|
425
429
|
level: 'org',
|
|
@@ -624,7 +628,7 @@ function resolveOrgContextFile(workspaceRoot, relativePath, orgCacheEligible = t
|
|
|
624
628
|
};
|
|
625
629
|
}
|
|
626
630
|
if (orgCacheEligible) {
|
|
627
|
-
const cachePath = (0, path_1.join)((0, pack_home_1.resolvePackHome)('org').
|
|
631
|
+
const cachePath = (0, path_1.join)((0, pack_home_1.resolvePackHome)('org').contentRoot, relativePath);
|
|
628
632
|
if ((0, fs_1.existsSync)(cachePath)) {
|
|
629
633
|
return {
|
|
630
634
|
present: true,
|
|
@@ -633,7 +637,7 @@ function resolveOrgContextFile(workspaceRoot, relativePath, orgCacheEligible = t
|
|
|
633
637
|
}
|
|
634
638
|
}
|
|
635
639
|
if (!orgCacheEligible) {
|
|
636
|
-
const managerCachePath = (0, path_1.join)((0, pack_home_1.resolvePackHome)('manager').
|
|
640
|
+
const managerCachePath = (0, path_1.join)((0, pack_home_1.resolvePackHome)('manager').contentRoot, relativePath);
|
|
637
641
|
if ((0, fs_1.existsSync)(managerCachePath)) {
|
|
638
642
|
return {
|
|
639
643
|
present: true,
|
|
@@ -795,7 +799,7 @@ function resolveTeamContextFile(workspaceRoot, key) {
|
|
|
795
799
|
// destination (R5.1), so writePath stays empty and the caller must route
|
|
796
800
|
// edits through the org backend's propose-and-approve flow.
|
|
797
801
|
if (key === 'org' || key === 'orgRules') {
|
|
798
|
-
const cachePath = (0, path_1.join)((0, pack_home_1.resolvePackHome)('org').
|
|
802
|
+
const cachePath = (0, path_1.join)((0, pack_home_1.resolvePackHome)('org').contentRoot, relativePath);
|
|
799
803
|
if ((0, fs_1.existsSync)(cachePath)) {
|
|
800
804
|
return {
|
|
801
805
|
present: true,
|
|
@@ -808,7 +812,7 @@ function resolveTeamContextFile(workspaceRoot, key) {
|
|
|
808
812
|
}
|
|
809
813
|
}
|
|
810
814
|
if (key === 'manager' || key === 'managerRules') {
|
|
811
|
-
const managerCachePath = (0, path_1.join)((0, pack_home_1.resolvePackHome)('manager').
|
|
815
|
+
const managerCachePath = (0, path_1.join)((0, pack_home_1.resolvePackHome)('manager').contentRoot, relativePath);
|
|
812
816
|
if ((0, fs_1.existsSync)(managerCachePath)) {
|
|
813
817
|
return {
|
|
814
818
|
present: true,
|
|
@@ -845,7 +849,7 @@ function resolveTeamContextFile(workspaceRoot, key) {
|
|
|
845
849
|
function orgBrandCandidateDirs(workspaceRoot) {
|
|
846
850
|
return [
|
|
847
851
|
(0, path_1.join)((0, project_fraim_paths_1.getWorkspaceFraimDir)(workspaceRoot), 'personalized-employee', 'context'),
|
|
848
|
-
(0, path_1.join)((0, pack_home_1.resolvePackHome)('org').
|
|
852
|
+
(0, path_1.join)((0, pack_home_1.resolvePackHome)('org').contentRoot, 'context'),
|
|
849
853
|
(0, path_1.join)((0, project_fraim_paths_1.getUserFraimDirPath)(), 'personalized-employee', 'context'),
|
|
850
854
|
];
|
|
851
855
|
}
|
|
@@ -1224,7 +1228,9 @@ function parseLearningEntries(filePath, displayPath, category, level, domain = '
|
|
|
1224
1228
|
}
|
|
1225
1229
|
function levelDir(roots, level) {
|
|
1226
1230
|
if (level === 'machine') {
|
|
1227
|
-
|
|
1231
|
+
// Issue #1070: use the configured manager home, not the portable authoring base.
|
|
1232
|
+
// For single-machine backends managerHomeBase == globalPersonalBase (no change).
|
|
1233
|
+
return { dir: roots.managerHomeBase, displayBase: roots.managerHomeDisplayBase.replace(/\/$/, '') };
|
|
1228
1234
|
}
|
|
1229
1235
|
return { dir: roots.repoLearningsBase, displayBase: REPO_LEARNINGS_REL };
|
|
1230
1236
|
}
|
|
@@ -1268,7 +1274,8 @@ function resolveLearningFilePath(workspaceRoot, userId, ref) {
|
|
|
1268
1274
|
if (ref.scope === 'org')
|
|
1269
1275
|
return (0, path_1.join)(roots.repoLearningsBase, `org-${fileType}.md`);
|
|
1270
1276
|
const resolvedUserId = resolveLearningUserId(workspaceRoot, userId, roots);
|
|
1271
|
-
|
|
1277
|
+
// Issue #1070: for the 'machine' level use the configured manager home.
|
|
1278
|
+
const base = ref.level === 'project' ? roots.repoLearningsBase : roots.managerHomeBase;
|
|
1272
1279
|
return (0, path_1.join)(base, `${resolvedUserId}-${fileType}.md`);
|
|
1273
1280
|
}
|
|
1274
1281
|
function titleCaseFromFileType(fileType) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-hub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.258",
|
|
4
4
|
"description": "FRAIM Hub local companion package.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"fraim-hub": "bin/fraim-hub.js",
|
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
"electron": "^41.2.2",
|
|
164
164
|
"electron-updater": "^6.8.9",
|
|
165
165
|
"express": "^5.2.1",
|
|
166
|
-
"fraim": "2.0.
|
|
166
|
+
"fraim": "2.0.258",
|
|
167
167
|
"mongodb": "^7.0.0",
|
|
168
168
|
"node-cron": "4.2.1",
|
|
169
169
|
"node-edge-tts": "^1.2.10",
|
package/public/ai-hub/script.js
CHANGED
|
@@ -15276,6 +15276,31 @@ function openInlineCreateEmployee(options) {
|
|
|
15276
15276
|
roleField.appendChild(roleInput);
|
|
15277
15277
|
body.appendChild(roleField);
|
|
15278
15278
|
|
|
15279
|
+
// Scope picker — Issue #1067: let the manager choose where the employee lives.
|
|
15280
|
+
const scopeField = document.createElement('div');
|
|
15281
|
+
scopeField.className = 'ipe-card-field';
|
|
15282
|
+
const scopeLabel = document.createElement('div');
|
|
15283
|
+
scopeLabel.className = 'ipe-card-label';
|
|
15284
|
+
scopeLabel.textContent = 'Visibility';
|
|
15285
|
+
const scopeSelect = document.createElement('select');
|
|
15286
|
+
scopeSelect.name = 'scope';
|
|
15287
|
+
scopeSelect.setAttribute('aria-label', 'Employee visibility');
|
|
15288
|
+
const scopeOptions = [
|
|
15289
|
+
{ value: 'project', label: 'This project' },
|
|
15290
|
+
{ value: 'manager', label: 'Yours (all projects)' },
|
|
15291
|
+
{ value: 'org', label: 'Everyone' },
|
|
15292
|
+
];
|
|
15293
|
+
for (const opt of scopeOptions) {
|
|
15294
|
+
const el = document.createElement('option');
|
|
15295
|
+
el.value = opt.value;
|
|
15296
|
+
el.textContent = opt.label;
|
|
15297
|
+
scopeSelect.appendChild(el);
|
|
15298
|
+
}
|
|
15299
|
+
scopeSelect.value = (isEdit && editEmployee.scope) ? editEmployee.scope : 'project';
|
|
15300
|
+
scopeField.appendChild(scopeLabel);
|
|
15301
|
+
scopeField.appendChild(scopeSelect);
|
|
15302
|
+
body.appendChild(scopeField);
|
|
15303
|
+
|
|
15279
15304
|
// Job picker — searchable list matching the command palette style.
|
|
15280
15305
|
const selectedJobIds = new Set(isEdit && Array.isArray(editEmployee.jobIds) ? editEmployee.jobIds : []);
|
|
15281
15306
|
const jobsField = document.createElement('div');
|
|
@@ -15392,6 +15417,7 @@ function openInlineCreateEmployee(options) {
|
|
|
15392
15417
|
role: roleInput.value.trim() || undefined,
|
|
15393
15418
|
icon: { kind: 'generated', value: displayName },
|
|
15394
15419
|
jobIds: pickedJobIds,
|
|
15420
|
+
scope: scopeSelect.value || 'project',
|
|
15395
15421
|
};
|
|
15396
15422
|
const resp = await fetch(isEdit
|
|
15397
15423
|
? '/api/ai-hub/custom-employees/' + encodeURIComponent(editEmployee.key)
|
|
@@ -15459,4 +15485,7 @@ if (typeof window !== 'undefined') {
|
|
|
15459
15485
|
// Issue #866 R4: let the suite drive the same onboarding-settled refresh the
|
|
15460
15486
|
// poll paths call, to assert the project view refreshes live without a restart.
|
|
15461
15487
|
window.onProjectOnboardingSettled = onProjectOnboardingSettled;
|
|
15488
|
+
// Issue #1067: let the suite trigger the create-employee form so scope picker
|
|
15489
|
+
// tests can assert the rendered controls without depending on click coordinates.
|
|
15490
|
+
window.openInlineCreateEmployee = openInlineCreateEmployee;
|
|
15462
15491
|
}
|