fraim-hub 2.0.217 → 2.0.218
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/configured-agents.js +24 -2
- package/dist/src/ai-hub/server.js +5 -2
- package/dist/src/cli/commands/add-ide.js +0 -7
- package/dist/src/cli/setup/codex-local-config.js +5 -27
- package/dist/src/config/persona-capability-bundles.js +27 -21
- package/package.json +2 -2
- package/public/ai-hub/script.js +32 -8
- package/public/ai-hub/styles.css +4 -3
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.AiHubConfiguredAgentStore = void 0;
|
|
7
7
|
exports.synthesizeDefaultConfiguredAgents = synthesizeDefaultConfiguredAgents;
|
|
8
8
|
exports.checkConfiguredAgentAvailability = checkConfiguredAgentAvailability;
|
|
9
|
+
exports.checkConfiguredAgentReadiness = checkConfiguredAgentReadiness;
|
|
9
10
|
exports.projectConfiguredAgent = projectConfiguredAgent;
|
|
10
11
|
exports.resolveConfiguredAgentForHost = resolveConfiguredAgentForHost;
|
|
11
12
|
exports.resolveConfiguredAgentEnv = resolveConfiguredAgentEnv;
|
|
@@ -78,7 +79,9 @@ function normalizeSetupScript(value) {
|
|
|
78
79
|
}
|
|
79
80
|
function synthesizeDefaultConfiguredAgents(employees) {
|
|
80
81
|
const timestamp = '1970-01-01T00:00:00.000Z';
|
|
81
|
-
return employees
|
|
82
|
+
return employees
|
|
83
|
+
.filter((employee) => employee.available)
|
|
84
|
+
.map((employee) => ({
|
|
82
85
|
id: `${employee.id}-default`,
|
|
83
86
|
label: employee.label,
|
|
84
87
|
description: 'Default local launch for this agent tool.',
|
|
@@ -161,6 +164,25 @@ function checkConfiguredAgentAvailability(agent, employees, env = process.env) {
|
|
|
161
164
|
}
|
|
162
165
|
return { id: agent.id, label: agent.label, baseHostId: agent.baseHostId, enabled: agent.enabled, available: reasons.length === 0, reasons, warnings };
|
|
163
166
|
}
|
|
167
|
+
function checkConfiguredAgentReadiness(agent, employees, env = process.env) {
|
|
168
|
+
const check = checkConfiguredAgentAvailability(agent, employees, env);
|
|
169
|
+
if (!check.available || !agent.setupScript)
|
|
170
|
+
return check;
|
|
171
|
+
try {
|
|
172
|
+
resolveConfiguredAgentEnv(agent);
|
|
173
|
+
return check;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
return {
|
|
177
|
+
...check,
|
|
178
|
+
available: false,
|
|
179
|
+
reasons: [
|
|
180
|
+
...check.reasons,
|
|
181
|
+
error instanceof Error ? error.message : 'Configured agent setup script failed.',
|
|
182
|
+
],
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
164
186
|
function projectConfiguredAgent(agent, employees) {
|
|
165
187
|
const check = checkConfiguredAgentAvailability(agent, employees);
|
|
166
188
|
return {
|
|
@@ -244,7 +266,7 @@ function runSetupScript(setupScript) {
|
|
|
244
266
|
env: captureEnv,
|
|
245
267
|
});
|
|
246
268
|
if (result.error || result.status !== 0) {
|
|
247
|
-
const detail = result.error?.message ||
|
|
269
|
+
const detail = result.error?.message || `exit ${result.status}`;
|
|
248
270
|
throw new Error(`Configured agent setup script failed: ${detail}`);
|
|
249
271
|
}
|
|
250
272
|
return filterSafeEnv(parseEnvLines(result.stdout || ''));
|
|
@@ -143,10 +143,13 @@ function getProtectedPersonaForHubJob(jobName) {
|
|
|
143
143
|
return loadPersonaCapabilityModule()?.getProtectedPersonaForJob(jobName) ?? null;
|
|
144
144
|
}
|
|
145
145
|
const GENERIC_WORKER_PERSONA_KEY = 'fraimworker';
|
|
146
|
+
// Unprotected jobs default to mandy so unassigned work surfaces
|
|
147
|
+
// under MANdy's employee row rather than the generic FRAIMworker placeholder.
|
|
148
|
+
const DEFAULT_UNASSIGNED_PERSONA_KEY = 'mandy';
|
|
146
149
|
function getHubPersonaForJob(jobName) {
|
|
147
150
|
if (!jobName || jobName === '__freeform__')
|
|
148
151
|
return null;
|
|
149
|
-
return getProtectedPersonaForHubJob(jobName) ??
|
|
152
|
+
return getProtectedPersonaForHubJob(jobName) ?? DEFAULT_UNASSIGNED_PERSONA_KEY;
|
|
150
153
|
}
|
|
151
154
|
const FRAIM_INTERNAL_JOB_IDS = new Set([
|
|
152
155
|
'contribute-to-fraim',
|
|
@@ -3053,7 +3056,7 @@ class AiHubServer {
|
|
|
3053
3056
|
const agent = this.configuredAgentsForCurrentMachine(employees).find((entry) => entry.id === req.params.id);
|
|
3054
3057
|
if (!agent)
|
|
3055
3058
|
return res.status(404).json({ error: 'Configured agent not found.' });
|
|
3056
|
-
return res.json((0, configured_agents_1.
|
|
3059
|
+
return res.json((0, configured_agents_1.checkConfiguredAgentReadiness)(agent, employees));
|
|
3057
3060
|
});
|
|
3058
3061
|
this.app.post('/api/ai-hub/runs', (req, res) => {
|
|
3059
3062
|
try {
|
|
@@ -44,7 +44,6 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
44
44
|
const path_1 = __importDefault(require("path"));
|
|
45
45
|
const ide_detector_1 = require("../setup/ide-detector");
|
|
46
46
|
const mcp_config_generator_1 = require("../setup/mcp-config-generator");
|
|
47
|
-
const codex_local_config_1 = require("../setup/codex-local-config");
|
|
48
47
|
const claude_code_telemetry_1 = require("../setup/claude-code-telemetry");
|
|
49
48
|
const script_sync_utils_1 = require("../utils/script-sync-utils");
|
|
50
49
|
const mcp_server_registry_1 = require("../mcp/mcp-server-registry");
|
|
@@ -254,12 +253,6 @@ const configureIDEMCP = async (ide, fraimKey, tokens, providerConfigs) => {
|
|
|
254
253
|
});
|
|
255
254
|
}
|
|
256
255
|
console.log(chalk_1.default.green(`✅ Updated ${configPath}`));
|
|
257
|
-
// Handle IDE-specific local config (e.g., Codex needs project-level config)
|
|
258
|
-
if (ide.configType === 'codex') {
|
|
259
|
-
const localResult = (0, codex_local_config_1.ensureCodexLocalConfig)(process.cwd());
|
|
260
|
-
const status = localResult.created ? 'Created' : localResult.updated ? 'Updated' : 'Verified';
|
|
261
|
-
console.log(chalk_1.default.green(` ✅ ${status} local ${ide.name} config: ${localResult.path}`));
|
|
262
|
-
}
|
|
263
256
|
// Enable token telemetry for Claude Code via project-level settings
|
|
264
257
|
if (ide.configType === 'claude-code') {
|
|
265
258
|
(0, claude_code_telemetry_1.ensureClaudeCodeTelemetryEnv)();
|
|
@@ -4,34 +4,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.ensureCodexLocalConfig = void 0;
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
7
|
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const mcp_config_generator_1 = require("./mcp-config-generator");
|
|
10
|
-
const escapeTomlString = (value) => value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
11
|
-
const buildFraimCwdBlock = (projectRoot) => `
|
|
12
|
-
[mcp_servers.fraim]
|
|
13
|
-
cwd = "${escapeTomlString(projectRoot)}"
|
|
14
|
-
`;
|
|
15
8
|
const ensureCodexLocalConfig = (projectRoot) => {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
const hadExistingFile = fs_1.default.existsSync(codexConfigPath);
|
|
22
|
-
const existingContent = hadExistingFile ? fs_1.default.readFileSync(codexConfigPath, 'utf8') : '';
|
|
23
|
-
const generatedContent = buildFraimCwdBlock(projectRoot);
|
|
24
|
-
const mergeResult = (0, mcp_config_generator_1.mergeTomlMCPServers)(existingContent, generatedContent, ['fraim']);
|
|
25
|
-
const normalizedExisting = existingContent.replace(/\r\n/g, '\n');
|
|
26
|
-
const normalizedMerged = mergeResult.content.replace(/\r\n/g, '\n');
|
|
27
|
-
const shouldWrite = !hadExistingFile || normalizedExisting !== normalizedMerged;
|
|
28
|
-
if (shouldWrite) {
|
|
29
|
-
fs_1.default.writeFileSync(codexConfigPath, mergeResult.content);
|
|
30
|
-
}
|
|
31
|
-
return {
|
|
32
|
-
path: codexConfigPath,
|
|
33
|
-
created: !hadExistingFile,
|
|
34
|
-
updated: shouldWrite && hadExistingFile
|
|
35
|
-
};
|
|
9
|
+
const codexConfigPath = path_1.default.join(projectRoot, '.codex', 'config.toml');
|
|
10
|
+
// Codex resolves the active workspace from its process cwd. FRAIM's Codex
|
|
11
|
+
// MCP server belongs in machine-level config; project-local partial TOML can
|
|
12
|
+
// shadow a valid profile and break configured Hub launches.
|
|
13
|
+
return { path: codexConfigPath, created: false, updated: false };
|
|
36
14
|
};
|
|
37
15
|
exports.ensureCodexLocalConfig = ensureCodexLocalConfig;
|
|
@@ -8,10 +8,16 @@ exports.listPersonaCapabilityBundles = listPersonaCapabilityBundles;
|
|
|
8
8
|
const persona_hiring_1 = require("./persona-hiring");
|
|
9
9
|
const persona_catalog_routes_1 = require("../routes/persona-catalog-routes");
|
|
10
10
|
exports.FREE_JOBS = new Set([
|
|
11
|
-
'create-architecture',
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
11
|
+
'create-architecture',
|
|
12
|
+
'project-scaffolding',
|
|
13
|
+
'blue-sky-brainstorming',
|
|
14
|
+
'codebase-analysis-and-ideation',
|
|
15
|
+
'domain-registration-research',
|
|
16
|
+
'google-workspace-setup',
|
|
17
|
+
'github-org-setup',
|
|
18
|
+
'contribute-to-fraim',
|
|
19
|
+
'file-fraim-issue',
|
|
20
|
+
'praise-fraim',
|
|
15
21
|
]);
|
|
16
22
|
function isFreeJob(jobName) {
|
|
17
23
|
return exports.FREE_JOBS.has(jobName);
|
|
@@ -39,8 +45,8 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
39
45
|
beza: {
|
|
40
46
|
personaKey: 'beza',
|
|
41
47
|
bundleId: 'persona-beza-core',
|
|
42
|
-
catalogMetadata: buildCatalogMetadata('beza', ['competitive-analysis', 'review-business-strategy', '
|
|
43
|
-
protectedJobs: ['competitive-analysis', 'review-business-strategy', 'business-plan-creation', 'problem-statement-crystallization', 'business-idea-validation-and-scoping', 'founder-market-fit-analysis', '
|
|
48
|
+
catalogMetadata: buildCatalogMetadata('beza', ['competitive-analysis', 'review-business-strategy', 'pricing-strategy-definition']),
|
|
49
|
+
protectedJobs: ['competitive-analysis', 'review-business-strategy', 'business-plan-creation', 'problem-statement-crystallization', 'business-idea-validation-and-scoping', 'founder-market-fit-analysis', 'advisory-board-development', 'advisor-interview', 'advisory-board-selection', 'pricing-strategy-definition'],
|
|
44
50
|
protectedAliases: ['business-strategy', 'company-strategy'],
|
|
45
51
|
defaultHireMode: 'job',
|
|
46
52
|
lockCopy: 'Hire BeZa to unlock business strategy work for this request.'
|
|
@@ -48,8 +54,8 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
48
54
|
pam: {
|
|
49
55
|
personaKey: 'pam',
|
|
50
56
|
bundleId: 'persona-pam-core',
|
|
51
|
-
catalogMetadata: buildCatalogMetadata('pam', ['feature-specification', 'technical-design', '
|
|
52
|
-
protectedJobs: ['feature-specification', 'technical-design', '
|
|
57
|
+
catalogMetadata: buildCatalogMetadata('pam', ['feature-specification', 'technical-design', 'project-plan-creation']),
|
|
58
|
+
protectedJobs: ['feature-specification', 'technical-design', 'experiment-tracking', 'project-plan-creation', 'implementation-feature-review', 'scrum-sprint-planning', 'mvp-validation-plan', 'sprint-planning', 'customer-prospect-discovery', 'interview-preparation', 'participant-recruitment', 'process-interview-notes', 'review-customer-development', 'triage-customer-needs'],
|
|
53
59
|
protectedAliases: ['product-management', 'product-spec'],
|
|
54
60
|
defaultHireMode: 'job',
|
|
55
61
|
lockCopy: 'Hire PaM to unlock product-management work for this request.'
|
|
@@ -57,8 +63,8 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
57
63
|
swen: {
|
|
58
64
|
personaKey: 'swen',
|
|
59
65
|
bundleId: 'persona-swen-core',
|
|
60
|
-
catalogMetadata: buildCatalogMetadata('swen', ['feature-implementation', '
|
|
61
|
-
protectedJobs: ['feature-implementation', 'code-refactoring', 'pr-iteration', 'mobile-app-development', 'mcp-server-creation', 'cloud-application-deployment', 'cloud-cost-optimization', 'cloud-performance-diagnosis', 'route-llm-spend-to-cloud-credits', 'set-up-cloud-cost-alerts', 'gitlabs-to-github', 'system-migration', 'cross-cloud-migration', 'data-pipeline-design', 'data-quality-monitoring', 'data-platform-architecture', 'write-dev-docs', 'database-schema-design', 'create-architecture', 'project-scaffolding', 'codebase-analysis-and-ideation', 'github-org-setup', 'google-workspace-setup', 'mobile-app-rejection-response', 'mobile-app-submission', 'publish-mcp-app', 'application-replication-workflow'],
|
|
66
|
+
catalogMetadata: buildCatalogMetadata('swen', ['feature-implementation', 'technical-design', 'code-refactoring']),
|
|
67
|
+
protectedJobs: ['feature-implementation', 'implementation-design-review', 'code-refactoring', 'pr-iteration', 'mobile-app-development', 'mcp-server-creation', 'cloud-application-deployment', 'cloud-cost-optimization', 'cloud-performance-diagnosis', 'route-llm-spend-to-cloud-credits', 'set-up-cloud-cost-alerts', 'gitlabs-to-github', 'system-migration', 'cross-cloud-migration', 'data-pipeline-design', 'data-quality-monitoring', 'data-platform-architecture', 'write-dev-docs', 'database-schema-design', 'create-architecture', 'project-scaffolding', 'codebase-analysis-and-ideation', 'github-org-setup', 'google-workspace-setup', 'mobile-app-rejection-response', 'mobile-app-submission', 'publish-mcp-app', 'application-replication-workflow'],
|
|
62
68
|
protectedAliases: ['software-engineering', 'implementation'],
|
|
63
69
|
defaultHireMode: 'job',
|
|
64
70
|
lockCopy: 'Hire SWEn to unlock software-engineering delivery for this request.'
|
|
@@ -67,7 +73,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
67
73
|
personaKey: 'qasm',
|
|
68
74
|
bundleId: 'persona-qasm-core',
|
|
69
75
|
catalogMetadata: buildCatalogMetadata('qasm', ['test-execution', 'browser-application-validation', 'ui-polish-validation']),
|
|
70
|
-
protectedJobs: ['test-execution', 'browser-application-validation', 'ui-polish-validation', 'code-quality-assessment', 'test-quality-assessment', 'user-testing-and-bug-bash', 'broken-windows-detection-and-remediation', 'iterative-quality-improvement', '
|
|
76
|
+
protectedJobs: ['test-execution', 'browser-application-validation', 'ui-polish-validation', 'code-quality-assessment', 'test-quality-assessment', 'user-testing-and-bug-bash', 'broken-windows-detection-and-remediation', 'iterative-quality-improvement', 'accessibility-audit', 'api-testing', 'performance-benchmarking'],
|
|
71
77
|
protectedAliases: ['qa', 'quality-assurance'],
|
|
72
78
|
defaultHireMode: 'job',
|
|
73
79
|
lockCopy: 'Hire QAsm to unlock QA validation for this request.'
|
|
@@ -84,8 +90,8 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
84
90
|
gautam: {
|
|
85
91
|
personaKey: 'gautam',
|
|
86
92
|
bundleId: 'persona-gautam-core',
|
|
87
|
-
catalogMetadata: buildCatalogMetadata('gautam', ['analyze-revenue-system', 'build-gtm-motion', '
|
|
88
|
-
protectedJobs: ['
|
|
93
|
+
catalogMetadata: buildCatalogMetadata('gautam', ['analyze-revenue-system', 'build-gtm-motion', 'domain-registration-research']),
|
|
94
|
+
protectedJobs: ['marketing-strategy-definition', 'product-launch-management', 'evangelist-content-development', 'funnel-analysis', 'growth-loop-design', 'analyze-revenue-system', 'design-gtm-system', 'build-gtm-motion', 'build-gtm-stack', 'plan-gtm-team', 'marketing-content-creation', 'ppc-campaign-management', 'paid-social-strategy', 'ad-performance-analysis', 'tracking-and-attribution-setup', 'developer-advocacy', 'seo-strategy', 'marketing-analytics-review', 'linkedin-company-page-setup', 'x-account-setup', 'domain-registration-research', 'social-engagement-campaign', 'thought-leadership-engagement', 'promo-video-creation', 'linkedin-carousel-from-deck', 'notebooklm-infographic', 'notebooklm-podcast', 'notebooklm-presentation', 'notebooklm-report', 'notebooklm-video-overview'],
|
|
89
95
|
protectedAliases: ['gtm', 'marketing'],
|
|
90
96
|
defaultHireMode: 'job',
|
|
91
97
|
lockCopy: 'Hire GauTaM to unlock go-to-market and paid media work for this request.'
|
|
@@ -111,8 +117,8 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
111
117
|
ashley: {
|
|
112
118
|
personaKey: 'ashley',
|
|
113
119
|
bundleId: 'persona-ashley-core',
|
|
114
|
-
catalogMetadata: buildCatalogMetadata('ashley', ['chief-of-staff-briefing', 'executive-assistant', '
|
|
115
|
-
protectedJobs: ['chief-of-staff-briefing', 'calendar-triage', 'meeting-preparation', 'executive-assistant', '
|
|
120
|
+
catalogMetadata: buildCatalogMetadata('ashley', ['chief-of-staff-briefing', 'executive-assistant', 'analyze-transcript']),
|
|
121
|
+
protectedJobs: ['chief-of-staff-briefing', 'calendar-triage', 'meeting-preparation', 'executive-assistant', 'send-newsletter', 'send-thank-you-notes', 'analyze-transcript'],
|
|
116
122
|
protectedAliases: ['executive-assistant', 'operations-assistant'],
|
|
117
123
|
defaultHireMode: 'job',
|
|
118
124
|
lockCopy: 'Hire AshLey to unlock executive-assistant work for this request.'
|
|
@@ -120,8 +126,8 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
120
126
|
mandy: {
|
|
121
127
|
personaKey: 'mandy',
|
|
122
128
|
bundleId: 'persona-mandy-core',
|
|
123
|
-
catalogMetadata: buildCatalogMetadata('mandy', ['fully-delegate', '
|
|
124
|
-
protectedJobs: ['fully-delegate', 'delivery-governance-review', '
|
|
129
|
+
catalogMetadata: buildCatalogMetadata('mandy', ['fully-delegate', 'stakeholder-status-reporting', 'weekly-operating-review']),
|
|
130
|
+
protectedJobs: ['fully-delegate', 'delivery-governance-review', 'issue-preparation', 'issue-retrospective', 'work-completion', 'stakeholder-status-reporting', 'send-stakeholder-update', 'weekly-operating-review', 'cross-functional-dependency-management', 'portfolio-impact-report'],
|
|
125
131
|
protectedAliases: ['manager', 'team-lead', 'orchestrator'],
|
|
126
132
|
defaultHireMode: 'job',
|
|
127
133
|
lockCopy: 'Hire MANdy to unlock autonomous multi-role orchestration — MANdy plans the job sequence, runs sub-agents in parallel, coaches them through verification loops, and hands back a synthesized DRAFT for your approval.'
|
|
@@ -174,8 +180,8 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
174
180
|
casey: {
|
|
175
181
|
personaKey: 'casey',
|
|
176
182
|
bundleId: 'persona-casey-core',
|
|
177
|
-
catalogMetadata: buildCatalogMetadata('casey', ['
|
|
178
|
-
protectedJobs: ['crm-account-health-review', 'crm-case-resolution', 'customer-health-review', 'loyalty-program-management', 'survey-campaign-management', 'support-queue-management', 'support-case-resolution', 'support-sop-operationalization', 'support-system-operationalization', 'support-playbook-evaluation', 'user-survey-management'],
|
|
183
|
+
catalogMetadata: buildCatalogMetadata('casey', ['update-crm', 'support-case-resolution', 'support-system-operationalization']),
|
|
184
|
+
protectedJobs: ['crm-account-health-review', 'crm-case-resolution', 'customer-health-review', 'loyalty-program-management', 'survey-campaign-management', 'update-crm', 'support-queue-management', 'support-case-resolution', 'support-sop-operationalization', 'support-system-operationalization', 'support-playbook-evaluation', 'user-survey-management'],
|
|
179
185
|
protectedAliases: ['customer-success', 'customer-support', 'csm', 'support'],
|
|
180
186
|
defaultHireMode: 'job',
|
|
181
187
|
lockCopy: 'Hire CaSey to unlock customer success and support work for this request.'
|
|
@@ -204,8 +210,8 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
|
|
|
204
210
|
mona: {
|
|
205
211
|
personaKey: 'mona',
|
|
206
212
|
bundleId: 'persona-mona-core',
|
|
207
|
-
catalogMetadata: buildCatalogMetadata('mona', ['financial-analysis', '
|
|
208
|
-
protectedJobs: ['financial-analysis', 'fpa-and-forecasting', 'monthly-close-review', 'tax-strategy-planning', 'aws-activate-credits-application', 'google-cloud-credits-application', 'microsoft-azure-credits-application', 'invoice-generation', 'business-banking-setup'],
|
|
213
|
+
catalogMetadata: buildCatalogMetadata('mona', ['financial-analysis', 'fundraising-prospect-discovery', 'investor-pitch-preparation']),
|
|
214
|
+
protectedJobs: ['financial-analysis', 'fpa-and-forecasting', 'monthly-close-review', 'tax-strategy-planning', 'aws-activate-credits-application', 'community-funding-preparation', 'fundraising-prospect-discovery', 'google-cloud-credits-application', 'investor-pitch-preparation', 'microsoft-azure-credits-application', 'review-funding-preparation', 'invoice-generation', 'business-banking-setup'],
|
|
209
215
|
protectedAliases: ['finance', 'financial-modeling', 'fpa', 'bookkeeping'],
|
|
210
216
|
defaultHireMode: 'job',
|
|
211
217
|
lockCopy: 'Hire MONa to unlock financial modeling, FP&A, and tax strategy work for this request.'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-hub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.218",
|
|
4
4
|
"description": "FRAIM Hub local companion package.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"dotenv": "^16.4.7",
|
|
91
91
|
"electron": "^41.2.2",
|
|
92
92
|
"express": "^5.2.1",
|
|
93
|
-
"fraim": "2.0.
|
|
93
|
+
"fraim": "2.0.218",
|
|
94
94
|
"mongodb": "^7.0.0",
|
|
95
95
|
"node-cron": "4.2.1",
|
|
96
96
|
"node-edge-tts": "^1.2.10",
|
package/public/ai-hub/script.js
CHANGED
|
@@ -82,6 +82,7 @@ const state = {
|
|
|
82
82
|
cpHighlightIndex: -1, // index into cpRows flat list
|
|
83
83
|
cpRows: [], // [{type:'recent'|'job', job, instructions?}]
|
|
84
84
|
lastRun: null, // {job, instructions, employeeId} for Cmd+Shift+R
|
|
85
|
+
configuredAgentCheckResults: {},
|
|
85
86
|
// Issue #540 R10: pending run params captured when hire strip is shown.
|
|
86
87
|
_hireStripPending: null,
|
|
87
88
|
};
|
|
@@ -1803,9 +1804,13 @@ function getConversationPersona(conv) {
|
|
|
1803
1804
|
return personaMap().get(conv.personaKey) || null;
|
|
1804
1805
|
}
|
|
1805
1806
|
|
|
1807
|
+
// Unprotected jobs default to mandy so unassigned work surfaces
|
|
1808
|
+
// under MANdy's employee row rather than the generic FRAIMworker placeholder.
|
|
1809
|
+
const DEFAULT_UNASSIGNED_PERSONA_KEY = 'mandy';
|
|
1810
|
+
|
|
1806
1811
|
function assignedPersonaKeyForJob(job) {
|
|
1807
1812
|
if (!job || job.id === '__freeform__') return null;
|
|
1808
|
-
return job.requiredPersonaKey != null ? job.requiredPersonaKey :
|
|
1813
|
+
return job.requiredPersonaKey != null ? job.requiredPersonaKey : DEFAULT_UNASSIGNED_PERSONA_KEY;
|
|
1809
1814
|
}
|
|
1810
1815
|
|
|
1811
1816
|
function getEmployeeStatus(employeeId) {
|
|
@@ -6577,6 +6582,10 @@ function renderConfiguredAgentsPanel() {
|
|
|
6577
6582
|
return;
|
|
6578
6583
|
}
|
|
6579
6584
|
for (const agent of agents) {
|
|
6585
|
+
const latestCheck = state.configuredAgentCheckResults[agent.id] || null;
|
|
6586
|
+
const renderedAvailable = latestCheck ? latestCheck.available !== false : agent.available !== false;
|
|
6587
|
+
const renderedEnabled = agent.enabled !== false;
|
|
6588
|
+
const renderedReasons = latestCheck?.reasons || agent.reasons || [];
|
|
6580
6589
|
const card = document.createElement('div');
|
|
6581
6590
|
card.className = 'configured-agent-card';
|
|
6582
6591
|
card.dataset.testid = 'configured-agent-card';
|
|
@@ -6585,8 +6594,8 @@ function renderConfiguredAgentsPanel() {
|
|
|
6585
6594
|
const title = document.createElement('strong');
|
|
6586
6595
|
title.textContent = agent.label;
|
|
6587
6596
|
const status = document.createElement('span');
|
|
6588
|
-
status.className = 'configured-agent-status ' + (
|
|
6589
|
-
status.textContent =
|
|
6597
|
+
status.className = 'configured-agent-status ' + (renderedAvailable && renderedEnabled ? 'ok' : 'warn');
|
|
6598
|
+
status.textContent = renderedAvailable && renderedEnabled ? 'Ready' : 'Check setup';
|
|
6590
6599
|
head.appendChild(title);
|
|
6591
6600
|
head.appendChild(status);
|
|
6592
6601
|
|
|
@@ -6599,8 +6608,9 @@ function renderConfiguredAgentsPanel() {
|
|
|
6599
6608
|
check.addEventListener('click', async () => {
|
|
6600
6609
|
try {
|
|
6601
6610
|
const result = await requestJson(`/api/ai-hub/configured-agents/${encodeURIComponent(agent.id)}/check`, { method: 'POST' });
|
|
6611
|
+
state.configuredAgentCheckResults[agent.id] = result;
|
|
6602
6612
|
showStatus(result.available ? `${agent.label} is ready.` : `${agent.label}: ${(result.reasons || []).join(' ') || 'Check setup.'}`, !result.available);
|
|
6603
|
-
|
|
6613
|
+
renderConfiguredAgentsPanel();
|
|
6604
6614
|
} catch (err) {
|
|
6605
6615
|
showStatus(err.message || 'Agent check failed.', true);
|
|
6606
6616
|
}
|
|
@@ -6622,6 +6632,7 @@ function renderConfiguredAgentsPanel() {
|
|
|
6622
6632
|
del.addEventListener('click', async () => {
|
|
6623
6633
|
try {
|
|
6624
6634
|
await requestJson(`/api/ai-hub/configured-agents/${encodeURIComponent(agent.id)}`, { method: 'DELETE' });
|
|
6635
|
+
delete state.configuredAgentCheckResults[agent.id];
|
|
6625
6636
|
if (state.configuredAgentEditingId === agent.id) state.configuredAgentEditingId = null;
|
|
6626
6637
|
await refreshConfiguredAgents();
|
|
6627
6638
|
} catch (err) {
|
|
@@ -6641,7 +6652,9 @@ function renderConfiguredAgentsPanel() {
|
|
|
6641
6652
|
|
|
6642
6653
|
const detail = document.createElement('div');
|
|
6643
6654
|
detail.className = 'configured-agent-detail';
|
|
6644
|
-
detail.textContent =
|
|
6655
|
+
detail.textContent = (latestCheck && renderedReasons.length)
|
|
6656
|
+
? renderedReasons.join(' ')
|
|
6657
|
+
: (agent.description || (renderedReasons.join(' ') || 'Uses local Hub launch settings.'));
|
|
6645
6658
|
|
|
6646
6659
|
card.appendChild(head);
|
|
6647
6660
|
card.appendChild(meta);
|
|
@@ -6766,6 +6779,7 @@ function buildConfiguredAgentForm(agent) {
|
|
|
6766
6779
|
body: JSON.stringify(body),
|
|
6767
6780
|
});
|
|
6768
6781
|
state.configuredAgentEditingId = null;
|
|
6782
|
+
delete state.configuredAgentCheckResults[body.id || agent?.id];
|
|
6769
6783
|
await refreshConfiguredAgents();
|
|
6770
6784
|
showStatus(`${body.label || body.id} saved.`);
|
|
6771
6785
|
} catch (err) {
|
|
@@ -6778,6 +6792,10 @@ function buildConfiguredAgentForm(agent) {
|
|
|
6778
6792
|
async function refreshConfiguredAgents() {
|
|
6779
6793
|
const configuredAgents = await requestJson('/api/ai-hub/configured-agents');
|
|
6780
6794
|
if (state.bootstrap) state.bootstrap.configuredAgents = Array.isArray(configuredAgents) ? configuredAgents : [];
|
|
6795
|
+
const ids = new Set((state.bootstrap?.configuredAgents || []).map((agent) => agent.id));
|
|
6796
|
+
for (const id of Object.keys(state.configuredAgentCheckResults)) {
|
|
6797
|
+
if (!ids.has(id)) delete state.configuredAgentCheckResults[id];
|
|
6798
|
+
}
|
|
6781
6799
|
renderConfiguredAgentsPanel();
|
|
6782
6800
|
renderActiveEmployeeSelect(activeConversation());
|
|
6783
6801
|
}
|
|
@@ -7646,12 +7664,18 @@ function tfLoadClientState() {
|
|
|
7646
7664
|
}
|
|
7647
7665
|
|
|
7648
7666
|
async function tfSaveProjectsToServer(projects, options) {
|
|
7649
|
-
|
|
7667
|
+
// #891: on a true first run there is no recorded/active project (per #866 the
|
|
7668
|
+
// launch directory is never assumed as a project), so state.projectPath is empty.
|
|
7669
|
+
// Creating the FIRST project must still work: fall back to the project path being
|
|
7670
|
+
// created (the folder the manager just chose), passed explicitly by the caller.
|
|
7671
|
+
// state.projectPath stays authoritative whenever a project is already active.
|
|
7672
|
+
const projectPath = (options && options.projectPath) || state.projectPath;
|
|
7673
|
+
if (!projectPath) throw new Error('No active project path available.');
|
|
7650
7674
|
const reviveRemovedPaths = options && Array.isArray(options.reviveRemovedPaths) ? options.reviveRemovedPaths : [];
|
|
7651
7675
|
const payload = await requestJson('/api/ai-hub/projects', {
|
|
7652
7676
|
method: 'PUT',
|
|
7653
7677
|
headers: { 'Content-Type': 'application/json' },
|
|
7654
|
-
body: JSON.stringify({ projectPath
|
|
7678
|
+
body: JSON.stringify({ projectPath, projects: Array.isArray(projects) ? projects : [], reviveRemovedPaths }),
|
|
7655
7679
|
});
|
|
7656
7680
|
tfAdoptServerProjects(payload && payload.projects);
|
|
7657
7681
|
return payload;
|
|
@@ -11430,7 +11454,7 @@ async function tfCreateProject() {
|
|
|
11430
11454
|
};
|
|
11431
11455
|
const removedKey = tfCanonicalProjectPath(folderPath);
|
|
11432
11456
|
try {
|
|
11433
|
-
await tfSaveProjectsToServer([...(tf.projects || []), project], { reviveRemovedPaths: [folderPath] });
|
|
11457
|
+
await tfSaveProjectsToServer([...(tf.projects || []), project], { reviveRemovedPaths: [folderPath], projectPath: state.projectPath || folderPath });
|
|
11434
11458
|
tf.removedPaths.delete(removedKey);
|
|
11435
11459
|
} catch (error) {
|
|
11436
11460
|
showStatus('Could not create project: ' + (error && error.message ? error.message : 'request failed.'), true);
|
package/public/ai-hub/styles.css
CHANGED
|
@@ -1437,6 +1437,7 @@ img.coach-employee-avatar { object-fit: cover; border-radius: 4px; }
|
|
|
1437
1437
|
padding: 8px 12px;
|
|
1438
1438
|
width: 100%;
|
|
1439
1439
|
flex: 1 1 100%;
|
|
1440
|
+
overflow-x: auto;
|
|
1440
1441
|
}
|
|
1441
1442
|
|
|
1442
1443
|
.thread-surface {
|
|
@@ -3039,7 +3040,7 @@ body.hub-shell { display: flex; flex-direction: column; height: 100vh; overflow:
|
|
|
3039
3040
|
.ptb-dismiss { font-size: 13px; color: var(--muted); background: none; border: 1px solid var(--line, rgba(0,0,0,.1)); border-radius: 6px; padding: 6px 12px; cursor: pointer; white-space: nowrap; flex-shrink: 0; }
|
|
3040
3041
|
.ptb-dismiss:hover { color: var(--text); border-color: var(--muted); }
|
|
3041
3042
|
/* #521: project-onboarding input modal (manager's direction before a run). */
|
|
3042
|
-
.obi-input { width: 100%; box-sizing: border-box; resize: vertical; min-height: 96px; font: inherit; font-size: 14px; line-height: 1.5; padding: 11px 13px; border: 1px solid var(--line, #e2e2e2); border-radius: 10px; background: var(--surface, #fff); color: var(--
|
|
3043
|
+
.obi-input { width: 100%; box-sizing: border-box; resize: vertical; min-height: 96px; font: inherit; font-size: 14px; line-height: 1.5; padding: 11px 13px; border: 1px solid var(--line, #e2e2e2); border-radius: 10px; background: var(--surface, #fff); color: var(--text); }
|
|
3043
3044
|
.obi-input:focus { outline: none; border-color: var(--accent, #1f6feb); }
|
|
3044
3045
|
.obi-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 14px; }
|
|
3045
3046
|
.obi-cancel { font-size: 14px; font-weight: 500; color: var(--muted, #666); background: none; border: none; padding: 10px 14px; cursor: pointer; }
|
|
@@ -3623,7 +3624,7 @@ body.hub-shell { display: flex; flex-direction: column; height: 100vh; overflow:
|
|
|
3623
3624
|
font-size: 13px;
|
|
3624
3625
|
font-weight: 600;
|
|
3625
3626
|
text-align: left;
|
|
3626
|
-
color: var(--
|
|
3627
|
+
color: var(--text);
|
|
3627
3628
|
background: var(--surface, #fff);
|
|
3628
3629
|
border: 1px solid var(--line, #e2e2e2);
|
|
3629
3630
|
border-radius: 8px;
|
|
@@ -3660,7 +3661,7 @@ body.hub-shell { display: flex; flex-direction: column; height: 100vh; overflow:
|
|
|
3660
3661
|
padding: 8px 10px;
|
|
3661
3662
|
font-size: 13px;
|
|
3662
3663
|
text-align: left;
|
|
3663
|
-
color: var(--
|
|
3664
|
+
color: var(--text);
|
|
3664
3665
|
background: transparent;
|
|
3665
3666
|
border: none;
|
|
3666
3667
|
border-radius: 7px;
|