fraim-hub 2.0.231 → 2.0.233
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/hosts.js
CHANGED
|
@@ -58,10 +58,8 @@ function parseSeekMentoringSignal(line) {
|
|
|
58
58
|
typeof obj.item === 'object' && obj.item !== null) {
|
|
59
59
|
const item = obj.item;
|
|
60
60
|
const itemType = item.type;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
(tool === 'seekMentoring' || tool === 'mcp__fraim__seekMentoring')) {
|
|
64
|
-
const args = item.arguments;
|
|
61
|
+
if (itemType === 'mcp_tool_call' && isFraimTool(item.tool, 'seekMentoring')) {
|
|
62
|
+
const args = normalizeToolArgs(item.arguments) || undefined;
|
|
65
63
|
const sig = extractSignalFromArgs(args);
|
|
66
64
|
if (sig)
|
|
67
65
|
return sig;
|
|
@@ -82,13 +80,9 @@ function parseSeekMentoringSignal(line) {
|
|
|
82
80
|
continue;
|
|
83
81
|
const c = candidate;
|
|
84
82
|
const isToolUse = c.type === 'tool_use' || c.type === 'function_call';
|
|
85
|
-
|
|
86
|
-
const isSeekMentoring = nameField === 'seekMentoring' ||
|
|
87
|
-
nameField === 'mcp__fraim__seekMentoring' ||
|
|
88
|
-
nameField.endsWith('seekMentoring');
|
|
89
|
-
if (!isToolUse || !isSeekMentoring)
|
|
83
|
+
if (!isToolUse || !isFraimTool(readToolName(c), 'seekMentoring'))
|
|
90
84
|
continue;
|
|
91
|
-
const input = (c.input || c.arguments || c.parameters);
|
|
85
|
+
const input = normalizeToolArgs(c.input || c.arguments || c.parameters) || undefined;
|
|
92
86
|
const sig = extractSignalFromArgs(input);
|
|
93
87
|
if (sig)
|
|
94
88
|
return sig;
|
|
@@ -116,8 +110,7 @@ function parseFraimJobLoadSignal(line) {
|
|
|
116
110
|
if ((obj.type === 'item.started' || obj.type === 'item.completed') &&
|
|
117
111
|
typeof obj.item === 'object' && obj.item !== null) {
|
|
118
112
|
const item = obj.item;
|
|
119
|
-
|
|
120
|
-
if (item.type === 'mcp_tool_call' && isGetFraimJobTool(tool)) {
|
|
113
|
+
if (item.type === 'mcp_tool_call' && isFraimTool(item.tool, 'get_fraim_job')) {
|
|
121
114
|
const sig = readFraimJobFromArgs(item.arguments);
|
|
122
115
|
if (sig)
|
|
123
116
|
return sig;
|
|
@@ -138,8 +131,7 @@ function parseFraimJobLoadSignal(line) {
|
|
|
138
131
|
continue;
|
|
139
132
|
const c = candidate;
|
|
140
133
|
const isToolUse = c.type === 'tool_use' || c.type === 'function_call';
|
|
141
|
-
|
|
142
|
-
if (!isToolUse || !isGetFraimJobTool(nameField))
|
|
134
|
+
if (!isToolUse || !isFraimTool(readToolName(c), 'get_fraim_job'))
|
|
143
135
|
continue;
|
|
144
136
|
const sig = readFraimJobFromArgs(c.input || c.arguments || c.parameters);
|
|
145
137
|
if (sig)
|
|
@@ -276,8 +268,8 @@ function parseAgentIdentitySignal(line) {
|
|
|
276
268
|
// Codex shape.
|
|
277
269
|
if ((obj.type === 'item.started' || obj.type === 'item.completed') && typeof obj.item === 'object' && obj.item !== null) {
|
|
278
270
|
const item = obj.item;
|
|
279
|
-
if (item.type === 'mcp_tool_call' && item.tool
|
|
280
|
-
return readAgentFromArgs(item.arguments);
|
|
271
|
+
if (item.type === 'mcp_tool_call' && isFraimTool(item.tool, 'fraim_connect')) {
|
|
272
|
+
return readAgentFromArgs(normalizeToolArgs(item.arguments) || undefined);
|
|
281
273
|
}
|
|
282
274
|
}
|
|
283
275
|
// Claude Code shape.
|
|
@@ -293,10 +285,9 @@ function parseAgentIdentitySignal(line) {
|
|
|
293
285
|
const c = candidate;
|
|
294
286
|
if (c.type !== 'tool_use' && c.type !== 'function_call')
|
|
295
287
|
continue;
|
|
296
|
-
|
|
297
|
-
if (!name.endsWith('fraim_connect'))
|
|
288
|
+
if (!isFraimTool(readToolName(c), 'fraim_connect'))
|
|
298
289
|
continue;
|
|
299
|
-
const sig = readAgentFromArgs((c.input || c.arguments));
|
|
290
|
+
const sig = readAgentFromArgs(normalizeToolArgs(c.input || c.arguments || c.parameters) || undefined);
|
|
300
291
|
if (sig)
|
|
301
292
|
return sig;
|
|
302
293
|
}
|
|
@@ -312,10 +303,31 @@ function readAgentFromArgs(args) {
|
|
|
312
303
|
return null;
|
|
313
304
|
return { agentName, agentModel };
|
|
314
305
|
}
|
|
315
|
-
function
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
306
|
+
function readToolName(candidate) {
|
|
307
|
+
if (typeof candidate.name === 'string')
|
|
308
|
+
return candidate.name;
|
|
309
|
+
if (typeof candidate.tool_name === 'string')
|
|
310
|
+
return candidate.tool_name;
|
|
311
|
+
if (typeof candidate.tool === 'string')
|
|
312
|
+
return candidate.tool;
|
|
313
|
+
if (typeof candidate.function === 'object' && candidate.function !== null) {
|
|
314
|
+
const fn = candidate.function;
|
|
315
|
+
if (typeof fn.name === 'string')
|
|
316
|
+
return fn.name;
|
|
317
|
+
}
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
function canonicalToolName(rawName) {
|
|
321
|
+
if (typeof rawName !== 'string')
|
|
322
|
+
return null;
|
|
323
|
+
const trimmed = rawName.trim();
|
|
324
|
+
if (!trimmed)
|
|
325
|
+
return null;
|
|
326
|
+
const byDoubleUnderscore = trimmed.split('__').filter(Boolean).pop() || trimmed;
|
|
327
|
+
return byDoubleUnderscore.split(/[./:]/).filter(Boolean).pop() || null;
|
|
328
|
+
}
|
|
329
|
+
function isFraimTool(rawName, canonicalName) {
|
|
330
|
+
return canonicalToolName(rawName) === canonicalName;
|
|
319
331
|
}
|
|
320
332
|
function readFraimJobFromArgs(rawArgs) {
|
|
321
333
|
const args = normalizeToolArgs(rawArgs);
|
|
@@ -154,6 +154,14 @@ function getHubPersonaForJob(jobName) {
|
|
|
154
154
|
return null;
|
|
155
155
|
return getProtectedPersonaForHubJob(jobName) ?? DEFAULT_UNASSIGNED_PERSONA_KEY;
|
|
156
156
|
}
|
|
157
|
+
// Issue #991: consult custom employee records before falling back to the catalog.
|
|
158
|
+
// Returns the key of the first custom employee whose jobIds array includes jobId,
|
|
159
|
+
// or null if no custom employee owns it.
|
|
160
|
+
function getCustomPersonaForJob(projectPath, jobId) {
|
|
161
|
+
const employees = (0, custom_employees_1.readCustomEmployees)(projectPath);
|
|
162
|
+
const match = employees.find((e) => Array.isArray(e.jobIds) && e.jobIds.includes(jobId));
|
|
163
|
+
return match ? match.key : null;
|
|
164
|
+
}
|
|
157
165
|
const FRAIM_INTERNAL_JOB_IDS = new Set([
|
|
158
166
|
'contribute-to-fraim',
|
|
159
167
|
'create-registry-asset',
|
|
@@ -1237,6 +1245,9 @@ async function configureFraimForHubAgent(hubId) {
|
|
|
1237
1245
|
}
|
|
1238
1246
|
}
|
|
1239
1247
|
function hubCommandVersion(command, extraBinDirs, basePath) {
|
|
1248
|
+
if (process.env.NODE_ENV === 'test' && process.env.FRAIM_TEST_HUB_COMMAND_VERSION_EMPTY === '1') {
|
|
1249
|
+
return null;
|
|
1250
|
+
}
|
|
1240
1251
|
const executable = process.platform === 'win32' ? 'cmd.exe' : command;
|
|
1241
1252
|
const args = process.platform === 'win32'
|
|
1242
1253
|
? ['/d', '/s', '/c', `${command} --version`]
|
|
@@ -1252,6 +1263,9 @@ function hubCommandVersion(command, extraBinDirs, basePath) {
|
|
|
1252
1263
|
return raw || null;
|
|
1253
1264
|
}
|
|
1254
1265
|
function hubRunProcess(command, args, env) {
|
|
1266
|
+
if (process.env.NODE_ENV === 'test' && command === 'npm' && process.env.FRAIM_TEST_HUB_NPM_ERROR) {
|
|
1267
|
+
return Promise.reject(new Error(process.env.FRAIM_TEST_HUB_NPM_ERROR));
|
|
1268
|
+
}
|
|
1255
1269
|
return new Promise((resolve, reject) => {
|
|
1256
1270
|
const [realCmd, realArgs] = process.platform === 'win32'
|
|
1257
1271
|
? ['cmd.exe', ['/d', '/s', '/c', command, ...args]]
|
|
@@ -2446,7 +2460,7 @@ class AiHubServer {
|
|
|
2446
2460
|
id: employeeJob.id,
|
|
2447
2461
|
title: employeeJob.title,
|
|
2448
2462
|
stubPath: employeeJob.stubPath,
|
|
2449
|
-
personaKey: employeeJob.requiredPersonaKey ?? getHubPersonaForJob(employeeJob.id),
|
|
2463
|
+
personaKey: employeeJob.requiredPersonaKey ?? getCustomPersonaForJob(projectPath, employeeJob.id) ?? getHubPersonaForJob(employeeJob.id),
|
|
2450
2464
|
};
|
|
2451
2465
|
}
|
|
2452
2466
|
const managerTemplate = (0, catalog_1.discoverManagerTemplates)(projectPath).find((job) => job.id === jobId);
|
|
@@ -76,6 +76,8 @@ const guiAppDetect = (configSurfaceCheck, appName, options = {}) => {
|
|
|
76
76
|
};
|
|
77
77
|
};
|
|
78
78
|
const availableByVersionProbe = (command) => {
|
|
79
|
+
if (process.env.FRAIM_DETECT_DISABLE_CLI_PROBES === '1')
|
|
80
|
+
return false;
|
|
79
81
|
const result = process.platform === 'win32'
|
|
80
82
|
? (0, child_process_1.spawnSync)('cmd.exe', ['/d', '/s', '/c', `${command} --version`], { encoding: 'utf8', timeout: 1500 })
|
|
81
83
|
: (0, child_process_1.spawnSync)(command, ['--version'], { encoding: 'utf8', timeout: 1500 });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-hub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.233",
|
|
4
4
|
"description": "FRAIM Hub local companion package.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"fraim-hub": "bin/fraim-hub.js"
|
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
"electron": "^41.2.2",
|
|
159
159
|
"electron-updater": "^6.8.9",
|
|
160
160
|
"express": "^5.2.1",
|
|
161
|
-
"fraim": "2.0.
|
|
161
|
+
"fraim": "2.0.233",
|
|
162
162
|
"mongodb": "^7.0.0",
|
|
163
163
|
"node-cron": "4.2.1",
|
|
164
164
|
"node-edge-tts": "^1.2.10",
|
package/public/ai-hub/script.js
CHANGED
|
@@ -73,6 +73,8 @@ const state = {
|
|
|
73
73
|
// the active employee; the user never sees the raw invocation syntax.
|
|
74
74
|
pendingCoachingJobId: null,
|
|
75
75
|
pendingCoachingLabel: null,
|
|
76
|
+
// Issue #989 R1-R6: area preserved when a next-job chip opens step-2 pre-filled.
|
|
77
|
+
pendingNextJobArea: null,
|
|
76
78
|
conversationPersistTimer: null,
|
|
77
79
|
conversationRefreshHandle: null,
|
|
78
80
|
conversationDiskAvailable: true,
|
|
@@ -300,7 +302,11 @@ function tfRefreshPersonaDependentSurfaces() {
|
|
|
300
302
|
else if (tf.area === 'brain') tfRenderBrain();
|
|
301
303
|
if (tf.area === 'projects') {
|
|
302
304
|
if (tf.projectView === 'overview') tfRenderOverview();
|
|
303
|
-
else
|
|
305
|
+
else {
|
|
306
|
+
tfRenderTree();
|
|
307
|
+
// tfRenderTree rebuilds tree chrome only; renderRail rebuilds per-employee accordion groups.
|
|
308
|
+
if (typeof renderRail === 'function') renderRail();
|
|
309
|
+
}
|
|
304
310
|
}
|
|
305
311
|
}
|
|
306
312
|
|
|
@@ -1664,7 +1670,7 @@ function renderRail() {
|
|
|
1664
1670
|
titleSpan.className = 'conv-title';
|
|
1665
1671
|
titleSpan.textContent = conv.title || '';
|
|
1666
1672
|
bodyDiv.appendChild(titleSpan);
|
|
1667
|
-
appendHubTimestamp(bodyDiv, conv
|
|
1673
|
+
appendHubTimestamp(bodyDiv, conversationStartTimestamp(conv), 'conv-time', 'Job started');
|
|
1668
1674
|
btn.appendChild(bodyDiv);
|
|
1669
1675
|
// Issue #566 R7: mark runs of a personalized (taught/customized) job.
|
|
1670
1676
|
if (isTaughtJob(conv.jobId)) {
|
|
@@ -4851,7 +4857,14 @@ function renderNextJobRecommendations(conv) {
|
|
|
4851
4857
|
: conv.scope === 'company' ? 'company'
|
|
4852
4858
|
: 'projects';
|
|
4853
4859
|
const employeeId = conversationAgentName(conv) || (state.selectedEmployeeId) || 'claude';
|
|
4854
|
-
|
|
4860
|
+
// Issue #989 R1-R6: open step-2 with job pre-selected and instructions pre-filled
|
|
4861
|
+
// so the manager can review/edit before starting, instead of calling startRun directly.
|
|
4862
|
+
state.selectedJob = job;
|
|
4863
|
+
state.selectedEmployeeId = employeeId;
|
|
4864
|
+
state.pendingNextJobArea = area;
|
|
4865
|
+
showStep2(job);
|
|
4866
|
+
els['instructions'].value = instructions;
|
|
4867
|
+
els['start'].disabled = instructions.trim().length === 0;
|
|
4855
4868
|
});
|
|
4856
4869
|
}
|
|
4857
4870
|
chips.appendChild(btn);
|
|
@@ -6018,6 +6031,7 @@ function closeModal() {
|
|
|
6018
6031
|
els['modal'].classList.remove('open');
|
|
6019
6032
|
els['modal'].hidden = true;
|
|
6020
6033
|
state.activeFilter = null;
|
|
6034
|
+
state.pendingNextJobArea = null;
|
|
6021
6035
|
}
|
|
6022
6036
|
|
|
6023
6037
|
// Build the unified picker list. Employee jobs are organized by their
|
|
@@ -6707,7 +6721,15 @@ function foldRunIntoConversation(conv, run) {
|
|
|
6707
6721
|
if (run.configuredAgentLabel) conv.configuredAgentLabel = run.configuredAgentLabel;
|
|
6708
6722
|
if (run.baseHostId) conv.baseHostId = run.baseHostId;
|
|
6709
6723
|
if (run.personaKey !== undefined) {
|
|
6710
|
-
|
|
6724
|
+
// Issue #991: do not let a server-side catalog/default key overwrite a custom:*
|
|
6725
|
+
// key the client already holds. The client stamps custom:* at kickoff from the
|
|
6726
|
+
// user's explicit action (cpPersonaOverride); the server resolves it via
|
|
6727
|
+
// getCustomPersonaForJob, but the client guard is defense-in-depth.
|
|
6728
|
+
const isClientCustom = conv.personaKey && conv.personaKey.startsWith('custom:');
|
|
6729
|
+
const isServerCustom = run.personaKey && run.personaKey.startsWith('custom:');
|
|
6730
|
+
if (!isClientCustom || isServerCustom) {
|
|
6731
|
+
conv.personaKey = run.personaKey;
|
|
6732
|
+
}
|
|
6711
6733
|
}
|
|
6712
6734
|
// Issue #347: keep the latest server snapshot under conv.run so the
|
|
6713
6735
|
// tracker / totals renderers can read it without re-doing work. Stages,
|
|
@@ -7561,6 +7583,9 @@ function wireEvents() {
|
|
|
7561
7583
|
if (!job || !text) return;
|
|
7562
7584
|
const employeeId = els['employee-select'].value || state.selectedEmployeeId;
|
|
7563
7585
|
state.selectedEmployeeId = employeeId;
|
|
7586
|
+
// Issue #989 R4: consume pendingNextJobArea so the run lands in the right tab.
|
|
7587
|
+
const invokedArea = state.pendingNextJobArea || undefined;
|
|
7588
|
+
state.pendingNextJobArea = null;
|
|
7564
7589
|
closeModal();
|
|
7565
7590
|
// R2: if no project path is set, show the inline project picker instead.
|
|
7566
7591
|
if (!state.projectPath) {
|
|
@@ -7574,7 +7599,7 @@ function wireEvents() {
|
|
|
7574
7599
|
showHireStrip(job, pendingConvId, text, employeeId);
|
|
7575
7600
|
return;
|
|
7576
7601
|
}
|
|
7577
|
-
await startRun(job, text, employeeId);
|
|
7602
|
+
await startRun(job, text, employeeId, undefined, invokedArea);
|
|
7578
7603
|
});
|
|
7579
7604
|
els['job-search'].addEventListener('input', () => renderJobCatalog(els['job-search'].value));
|
|
7580
7605
|
els['modal'].addEventListener('click', (e) => {
|
|
@@ -10486,7 +10511,7 @@ function tfBuildManagerRunItem(conv) {
|
|
|
10486
10511
|
const body = document.createElement('span'); body.className = 'conv-body';
|
|
10487
10512
|
const title = document.createElement('span'); title.className = 'conv-title';
|
|
10488
10513
|
title.textContent = conv.title || conv.jobTitle || conv.jobId || 'Run';
|
|
10489
|
-
body.appendChild(title); appendHubTimestamp(body, conv
|
|
10514
|
+
body.appendChild(title); appendHubTimestamp(body, conversationStartTimestamp(conv), 'conv-time', 'Job started'); btn.appendChild(body);
|
|
10490
10515
|
const dotClass = conversationStateDotClass(conv);
|
|
10491
10516
|
const dot = document.createElement('span'); dot.className = 'state-dot conv-state-dot dot-' + dotClass;
|
|
10492
10517
|
if (typeof tfDotTitle === 'function') dot.title = tfDotTitle(dotClass);
|