fraim-hub 2.0.284 → 2.0.286

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.
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runMandatoryDesktopUpdateCheck = runMandatoryDesktopUpdateCheck;
4
+ let updateCheckInFlight = null;
5
+ function runMandatoryDesktopUpdateCheck(options) {
6
+ if (!options.isPackaged)
7
+ return Promise.resolve({ action: 'skipped-unpackaged' });
8
+ if (updateCheckInFlight)
9
+ return updateCheckInFlight;
10
+ updateCheckInFlight = runMandatoryDesktopUpdateCheckOnce(options)
11
+ .finally(() => {
12
+ updateCheckInFlight = null;
13
+ });
14
+ return updateCheckInFlight;
15
+ }
16
+ async function runMandatoryDesktopUpdateCheckOnce(options) {
17
+ const { updater, prompt, currentVersion, logger = console } = options;
18
+ updater.autoDownload = false;
19
+ updater.autoInstallOnAppQuit = false;
20
+ const checkResult = await checkForDesktopUpdate(updater, prompt, currentVersion, logger);
21
+ if (checkResult.action === 'check-failed')
22
+ return checkResult;
23
+ if (!checkResult.update?.isUpdateAvailable) {
24
+ logger.info('[fraim] desktop update check found no newer version');
25
+ return { action: 'current', availableVersion: checkResult.update?.updateInfo?.version };
26
+ }
27
+ const availableVersion = checkResult.update.updateInfo?.version;
28
+ try {
29
+ logger.info(`[fraim] desktop update ${availableVersion ?? 'unknown'} available; downloading`);
30
+ if (checkResult.update.downloadPromise) {
31
+ await checkResult.update.downloadPromise;
32
+ }
33
+ else {
34
+ await updater.downloadUpdate();
35
+ }
36
+ }
37
+ catch (error) {
38
+ const message = errorMessage(error);
39
+ logger.error(`[fraim] desktop update download failed: ${message}`);
40
+ await promptUpdateFailure(prompt, 'FRAIM Hub update download failed', 'FRAIM Hub found an update but could not download it. Please restart FRAIM Hub or reinstall from the latest installer.', message);
41
+ return { action: 'download-failed', availableVersion, error: message };
42
+ }
43
+ try {
44
+ logger.info(`[fraim] desktop update ${availableVersion ?? 'unknown'} downloaded; installing`);
45
+ updater.quitAndInstall(false, true);
46
+ return { action: 'installing', availableVersion };
47
+ }
48
+ catch (error) {
49
+ const message = errorMessage(error);
50
+ logger.error(`[fraim] desktop update install failed: ${message}`);
51
+ await promptUpdateFailure(prompt, 'FRAIM Hub update install failed', 'FRAIM Hub downloaded an update but could not start the installer. Please restart FRAIM Hub or reinstall from the latest installer.', message);
52
+ return { action: 'install-failed', availableVersion, error: message };
53
+ }
54
+ }
55
+ async function checkForDesktopUpdate(updater, prompt, currentVersion, logger) {
56
+ try {
57
+ logger.info(`[fraim] checking for desktop update from ${currentVersion}`);
58
+ return { action: 'checked', update: await updater.checkForUpdates() };
59
+ }
60
+ catch (error) {
61
+ const message = errorMessage(error);
62
+ logger.warn(`[fraim] desktop update check failed: ${message}`);
63
+ await promptUpdateFailure(prompt, 'FRAIM Hub update check failed', 'FRAIM Hub could not check for updates. It will continue starting, but this installed app may be stale.', message);
64
+ return { action: 'check-failed', error: message };
65
+ }
66
+ }
67
+ function promptUpdateFailure(prompt, title, message, detail) {
68
+ return prompt.showErrorBox(title, message, detail);
69
+ }
70
+ function errorMessage(error) {
71
+ if (error instanceof Error)
72
+ return error.message;
73
+ return String(error);
74
+ }
@@ -19,6 +19,7 @@ const bundled_asset_resolver_1 = require("./bundled-asset-resolver");
19
19
  const server_2 = require("../first-run/server");
20
20
  const session_service_1 = require("../first-run/session-service");
21
21
  const fraim_mcp_latest_launcher_1 = require("../cli/mcp/fraim-mcp-latest-launcher");
22
+ const desktop_auto_updater_1 = require("./desktop-auto-updater");
22
23
  // Keep installed, running, and user-pinned Windows shortcuts grouped under the
23
24
  // stable identity declared in packages/fraim-hub/package.json.
24
25
  electron_1.app.setAppUserModelId('ai.fraim.hub');
@@ -110,18 +111,33 @@ function ensureLoginItem() {
110
111
  fs_1.default.mkdirSync(path_1.default.dirname(flagPath), { recursive: true });
111
112
  fs_1.default.writeFileSync(flagPath, '1');
112
113
  }
113
- function configureAutoUpdater() {
114
+ async function configureAutoUpdater() {
114
115
  if (!electron_1.app.isPackaged)
115
- return;
116
+ return false;
116
117
  // #1110: electron-updater compiles ~114 files (js-yaml, builder-util-runtime, ...) that a
117
118
  // non-packaged `npx fraim-hub` launch never uses, and this whole function returns early
118
119
  // there. Requiring it lazily keeps those file reads off the cold-start path, which is what
119
120
  // dominates time-to-ready on a freshly unpacked install.
120
121
  // eslint-disable-next-line @typescript-eslint/no-require-imports
121
122
  const { autoUpdater } = require('electron-updater');
122
- autoUpdater.autoDownload = true;
123
- autoUpdater.checkForUpdatesAndNotify().catch((err) => {
124
- console.warn('[fraim] auto-update check failed:', err);
123
+ const result = await (0, desktop_auto_updater_1.runMandatoryDesktopUpdateCheck)({
124
+ isPackaged: electron_1.app.isPackaged,
125
+ updater: autoUpdater,
126
+ currentVersion: electron_1.app.getVersion(),
127
+ logger: console,
128
+ prompt: {
129
+ showErrorBox: (title, message, detail) => {
130
+ electron_1.dialog.showErrorBox(title, detail ? `${message}\n\n${detail}` : message);
131
+ },
132
+ },
133
+ });
134
+ return result.action === 'installing';
135
+ }
136
+ function checkForUpdateAfterSecondInstance() {
137
+ if (!electron_1.app.isPackaged || process.env.FRAIM_INSTALLER_LIFECYCLE_TEST === '1')
138
+ return;
139
+ void configureAutoUpdater().catch((err) => {
140
+ console.warn('[fraim] second-instance update check failed:', err);
125
141
  });
126
142
  }
127
143
  // ---------------------------------------------------------------------------
@@ -467,6 +483,7 @@ async function bootstrap() {
467
483
  return;
468
484
  }
469
485
  electron_1.app.on('second-instance', () => {
486
+ void electron_1.app.whenReady().then(checkForUpdateAfterSecondInstance);
470
487
  if (mainWindow) {
471
488
  mainWindow.show();
472
489
  mainWindow.focus();
@@ -486,7 +503,9 @@ async function bootstrap() {
486
503
  // First-launch housekeeping (idempotent, fast on subsequent runs)
487
504
  if (process.env.FRAIM_INSTALLER_LIFECYCLE_TEST !== '1') {
488
505
  ensureLoginItem();
489
- configureAutoUpdater();
506
+ const installingUpdate = await configureAutoUpdater();
507
+ if (installingUpdate)
508
+ return;
490
509
  }
491
510
  electron_1.app.on('activate', () => {
492
511
  // macOS: clicking dock icon re-shows the window
@@ -2071,6 +2071,29 @@ class CliHostRuntime {
2071
2071
  return null;
2072
2072
  return active.pending.length + 1;
2073
2073
  }
2074
+ stopActiveSession(hostId, sessionId) {
2075
+ const key = `${hostId}::${sessionId}`;
2076
+ const active = this.activeContinueRuns.get(key);
2077
+ if (!active)
2078
+ return false;
2079
+ active.pending.splice(0);
2080
+ this.activeContinueRuns.delete(key);
2081
+ if (active.child.pid == null)
2082
+ return false;
2083
+ try {
2084
+ this.killTree(active.child.pid, 'SIGTERM');
2085
+ return true;
2086
+ }
2087
+ catch (error) {
2088
+ console.warn('[ai-hub] failed to stop active host session process tree:', {
2089
+ hostId,
2090
+ sessionId,
2091
+ pid: active.child.pid,
2092
+ error: error instanceof Error ? error.message : String(error),
2093
+ });
2094
+ return false;
2095
+ }
2096
+ }
2074
2097
  guardedContinue(hostId, sessionId, entry) {
2075
2098
  const key = `${hostId}::${sessionId}`;
2076
2099
  const active = this.activeContinueRuns.get(key);
@@ -417,7 +417,12 @@ class AiHubRunRegistry {
417
417
  (0, tree_kill_1.default)(child.pid, 'SIGTERM');
418
418
  return true;
419
419
  }
420
- catch {
420
+ catch (error) {
421
+ console.warn('[ai-hub] failed to stop run process tree:', {
422
+ runId,
423
+ pid: child.pid,
424
+ error: error instanceof Error ? error.message : String(error),
425
+ });
421
426
  return false;
422
427
  }
423
428
  }
@@ -5945,7 +5950,11 @@ class AiHubServer {
5945
5950
  return res.json(this.enrichRunForResponse(run));
5946
5951
  }
5947
5952
  this.runRegistry.update(run.id, (current) => { current.stoppedByUser = true; });
5948
- const killed = this.runRegistry.stop(run.id);
5953
+ const killedRunChild = this.runRegistry.stop(run.id);
5954
+ const killedHostSession = run.sessionId
5955
+ ? this.hostRuntime.stopActiveSession?.(run.hostId, run.sessionId) === true
5956
+ : false;
5957
+ const killed = killedRunChild || killedHostSession;
5949
5958
  // Park it immediately (don't wait for onExit, which may lag or not fire on a
5950
5959
  // host that already detached). onExit, if it fires, keeps this same state.
5951
5960
  this.runRegistry.update(run.id, (current) => {
@@ -7309,7 +7318,7 @@ class AiHubServer {
7309
7318
  const delay = recoveryBackoffMs(attempt);
7310
7319
  const tid = setTimeout(() => {
7311
7320
  const current = this.runRegistry.get(runId);
7312
- if (!current || current.status !== 'running')
7321
+ if (!current || current.status !== 'running' || current.stoppedByUser)
7313
7322
  return;
7314
7323
  const message = classification.recoveryKind === 'compaction'
7315
7324
  ? buildHubCompactionRecoveryContinueMessage(current, exitCode, attempt)
@@ -15,11 +15,12 @@ const child_process_1 = require("child_process");
15
15
  const path_1 = __importDefault(require("path"));
16
16
  const managed_agent_paths_1 = require("../../utils/managed-agent-paths");
17
17
  const command_resolution_1 = require("../../mcp/command-resolution");
18
- // Codex is FRAIM's first Hub-compatible CLI with a managed-install fallback
19
- // (npm install -g into FRAIM's portable Node when no system install is
20
- // found). Extend this list as claude/gemini/copilot gain the same fallback.
18
+ // CLIs with a managed-install fallback (npm install -g into FRAIM's portable
19
+ // Node when no system install is found). Add gemini/copilot here when their
20
+ // installManagedAgent wiring lands.
21
21
  const MANAGED_CLIS = [
22
22
  { id: 'codex', label: 'Codex', command: 'codex' },
23
+ { id: 'claude-code', label: 'Claude Code', command: 'claude' },
23
24
  ];
24
25
  // Windows cannot CreateProcess a `.cmd`/`.bat` file directly (spawnSync on a
25
26
  // resolved absolute `.cmd` path throws EINVAL) — it must go through cmd.exe,
@@ -83,6 +83,13 @@ function cleanupOrphanedManagedShims() {
83
83
  const removed = [];
84
84
  for (const basename of basenames) {
85
85
  const candidate = path_1.default.join(nodeRoot, basename);
86
+ // Guard: only delete the flat-dir shim once the versioned dir already has
87
+ // an equivalent. Without this, upgrading users lose their shims before a
88
+ // reinstall has had a chance to place them in the versioned dir — the
89
+ // agent CLI disappears entirely until the next explicit reinstall.
90
+ const versionedEquiv = path_1.default.join(versionedDir, basename);
91
+ if (!fs_1.default.existsSync(versionedEquiv))
92
+ continue;
86
93
  try {
87
94
  if (fs_1.default.statSync(candidate).isFile()) {
88
95
  fs_1.default.unlinkSync(candidate);
@@ -31,6 +31,7 @@ const GENERALIST_PROFILE = {
31
31
  };
32
32
  /** Role key -> the human manager best suited to manage that AI employee. Mirror of registry/scripts/ai-manager-hiring.ts. */
33
33
  exports.HUMAN_MANAGER_PROFILES = {
34
+ aida: { humanTitle: 'Head of AI Engineering', keywords: ['"Head of AI Engineering"', '"Director of AI"', '"AI Engineering Manager"', '"VP Engineering"'] },
34
35
  maestro: { humanTitle: 'Co-Founder / General Manager', keywords: ['"Co-Founder"', '"General Manager"', '"Chief of Staff"', '"Founder"'] },
35
36
  beza: { humanTitle: 'Head of Strategy', keywords: ['"Head of Strategy"', '"Strategy Director"', '"Chief of Staff"'] },
36
37
  pam: { humanTitle: 'Head of Product', keywords: ['"Head of Product"', '"Group Product Manager"', '"Director of Product"'] },
@@ -61,6 +61,7 @@ exports.JOB_DOMAIN_MAP = {
61
61
  'security': 'engineering',
62
62
  'delivery-ops': 'engineering',
63
63
  'salesforce': 'engineering',
64
+ 'ai-engineering': 'engineering',
64
65
  // product
65
66
  'product-management': 'product',
66
67
  'customer-development': 'product',
@@ -63,7 +63,7 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
63
63
  personaKey: 'swen',
64
64
  bundleId: 'persona-swen-core',
65
65
  catalogMetadata: buildCatalogMetadata('swen', ['feature-implementation', 'technical-design', 'code-refactoring']),
66
- protectedJobs: ['feature-implementation', 'technical-design', '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'],
66
+ protectedJobs: ['feature-implementation', 'technical-design', 'implementation-design-review', 'code-refactoring', 'pr-iteration', 'mobile-app-development', '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', 'application-replication-workflow'],
67
67
  protectedAliases: ['software-engineering', 'implementation'],
68
68
  defaultHireMode: 'job',
69
69
  lockCopy: 'Hire SWEn to unlock software-engineering delivery for this request.'
@@ -262,6 +262,23 @@ exports.PERSONA_CAPABILITY_BUNDLES = {
262
262
  protectedAliases: ['banking-audit', 'kyc-audit', 'audit'],
263
263
  defaultHireMode: 'job',
264
264
  lockCopy: 'Hire AUDITya to unlock banking evidence audit work for this request.'
265
+ },
266
+ aida: {
267
+ personaKey: 'aida',
268
+ bundleId: 'persona-aida-core',
269
+ catalogMetadata: buildCatalogMetadata('aida', ['create-ai-agent', 'author-ai-evals', 'enable-web-mcp']),
270
+ protectedJobs: [
271
+ 'create-ai-agent',
272
+ 'author-ai-evals',
273
+ 'enable-web-mcp',
274
+ 'evaluate-ai-agent',
275
+ 'mcp-server-creation',
276
+ 'publish-mcp-app',
277
+ 'create-hub-configured-agent',
278
+ ],
279
+ protectedAliases: ['ai-engineering', 'agent-engineering', 'ai-agents'],
280
+ defaultHireMode: 'job',
281
+ lockCopy: 'Hire AIda to unlock AI agent design, MCP enablement, eval authoring, and agent evaluation work for this request.'
265
282
  }
266
283
  };
267
284
  const PROTECTED_JOB_TO_PERSONA = new Map();
@@ -275,7 +292,6 @@ for (const bundle of Object.values(exports.PERSONA_CAPABILITY_BUNDLES)) {
275
292
  // as "free") so the Hub attributes them to FRAIMworker, but they are never
276
293
  // hire-gated because FRAIMworker is not a purchasable persona.
277
294
  const GENERIC_WORKER_OWNED_JOBS = new Set([
278
- 'create-hub-configured-agent',
279
295
  'contribute-to-fraim',
280
296
  'file-fraim-issue',
281
297
  'praise-fraim',
@@ -19,32 +19,14 @@ exports.getPersonaHireAmountCents = getPersonaHireAmountCents;
19
19
  * GET /api/personas/catalog.
20
20
  */
21
21
  exports.PERSONA_HIRE_CATALOG = {
22
- maestro: {
23
- displayName: 'MAESTRO',
24
- role: 'Full-Brained AI Employee',
25
- emoji: '',
26
- gradient: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #d946ef 100%)',
27
- blurb: 'One AI employee who can take a job from any function and ship it back with evidence. You set the direction. You sign off on what ships. Maestro does the work.',
28
- jobPriceCents: 24900, // $249full-time only; job mode maps to same price
29
- fulltimePriceCents: 24900, // $249/mo
30
- },
31
- beza: {
32
- displayName: 'BeZa',
33
- role: 'AI Business Strategist',
34
- emoji: '🧭',
35
- gradient: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
36
- blurb: 'Turns ideas into structured business plans, validates founder-market fit, and pressure-tests strategy.',
37
- jobPriceCents: 990,
38
- fulltimePriceCents: 4990,
39
- },
40
- pam: {
41
- displayName: 'PaM',
42
- role: 'AI Product Manager',
43
- emoji: '📋',
44
- gradient: 'linear-gradient(135deg, #8b5cf6 0%, #d946ef 100%)',
45
- blurb: 'Owns specs, PRDs, technical design, issue prep, and the path from idea to shippable artifact.',
46
- jobPriceCents: 790,
47
- fulltimePriceCents: 4990,
22
+ aida: {
23
+ displayName: 'AIda',
24
+ role: 'AI Engineer',
25
+ emoji: '\u{1F9E0}',
26
+ gradient: 'linear-gradient(135deg, #4f46e5 0%, #06b6d4 50%, #10b981 100%)',
27
+ blurb: 'Designs production AI agents, connects them to tools and data, and proves their behavior with evals before deployment.',
28
+ jobPriceCents: 1290, // placeholderproduct owner approval required before launch
29
+ fulltimePriceCents: 5990, // placeholder — product owner approval required before launch
48
30
  },
49
31
  swen: {
50
32
  displayName: 'SWEn',
@@ -64,33 +46,6 @@ exports.PERSONA_HIRE_CATALOG = {
64
46
  jobPriceCents: 590,
65
47
  fulltimePriceCents: 3990,
66
48
  },
67
- huxley: {
68
- displayName: 'hUXley',
69
- role: 'AI UX / Brand Designer',
70
- emoji: '🎨',
71
- gradient: 'linear-gradient(135deg, #ec4899 0%, #f472b6 100%)',
72
- blurb: 'Builds design systems, prototypes polished user-facing surfaces, and carries brand decisions into shipped product experiences.',
73
- jobPriceCents: 1490,
74
- fulltimePriceCents: 5490,
75
- },
76
- gautam: {
77
- displayName: 'GauTaM',
78
- role: 'AI GTM & Marketing Manager',
79
- emoji: '📣',
80
- gradient: 'linear-gradient(135deg, #f97316 0%, #f59e0b 100%)',
81
- blurb: 'Defines marketing strategy, ships content, runs launches, and owns the brand voice in market.',
82
- jobPriceCents: 890,
83
- fulltimePriceCents: 4990,
84
- },
85
- cela: {
86
- displayName: 'CELiA',
87
- role: 'AI Legal Counsel',
88
- emoji: '⚖️',
89
- gradient: 'linear-gradient(135deg, #475569 0%, #6366f1 100%)',
90
- blurb: 'Drafts and reviews contracts, NDAs, patents, trademarks, and the SaaS legal stack.',
91
- jobPriceCents: 1990,
92
- fulltimePriceCents: 6990,
93
- },
94
49
  sekhar: {
95
50
  displayName: 'SEChar',
96
51
  role: 'AI Security Engineer',
@@ -100,51 +55,15 @@ exports.PERSONA_HIRE_CATALOG = {
100
55
  jobPriceCents: 1790,
101
56
  fulltimePriceCents: 6490,
102
57
  },
103
- ashley: {
104
- displayName: 'AshLey',
105
- role: 'AI Executive Assistant',
106
- emoji: '📅',
107
- gradient: 'linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%)',
108
- blurb: 'Owns executive coordination, weekly operating reviews, and portfolio reporting across the workforce.',
109
- jobPriceCents: 490,
110
- fulltimePriceCents: 2990,
111
- },
112
- mandy: {
113
- displayName: 'MANdy',
114
- role: 'AI Manager',
115
- emoji: '🎯',
116
- gradient: 'linear-gradient(135deg, #7c3aed 0%, #4338ca 100%)',
117
- blurb: 'Plans the job sequence, runs sub-agents in parallel, coaches them through verification loops, and hands back a synthesized DRAFT for your approval.',
118
- jobPriceCents: 1490,
58
+ sreya: {
59
+ displayName: 'SREya',
60
+ role: 'AI Site Reliability Engineer',
61
+ emoji: '☁️',
62
+ gradient: 'linear-gradient(135deg, #2563eb 0%, #10b981 100%)',
63
+ blurb: 'Manages deployments, monitors uptime, optimizes cloud cost, and keeps infrastructure resilient and observable.',
64
+ jobPriceCents: 1290,
119
65
  fulltimePriceCents: 5990,
120
66
  },
121
- ricardo: {
122
- displayName: 'RECardo',
123
- role: 'AI Recruiter',
124
- emoji: '🤝',
125
- gradient: 'linear-gradient(135deg, #6366f1 0%, #ec4899 100%)',
126
- blurb: 'Sources candidates, writes job descriptions, screens pipelines, and manages the hiring loop end-to-end.',
127
- jobPriceCents: 790,
128
- fulltimePriceCents: 4490,
129
- },
130
- hari: {
131
- displayName: 'HaRi',
132
- role: 'AI HR Manager',
133
- emoji: '👥',
134
- gradient: 'linear-gradient(135deg, #0d9488 0%, #059669 100%)',
135
- blurb: 'Manages onboarding, performance reviews, benefits analysis, payroll coordination, and HR business-partner advisory.',
136
- jobPriceCents: 790,
137
- fulltimePriceCents: 4490,
138
- },
139
- careena: {
140
- displayName: 'CAREEna',
141
- role: 'AI Career Coach',
142
- emoji: '🎓',
143
- gradient: 'linear-gradient(135deg, #0ea5e9 0%, #6366f1 100%)',
144
- blurb: 'Runs the candidate-side search loop: role sourcing, application execution, networking, interview prep, and close-stage offer strategy.',
145
- jobPriceCents: 890,
146
- fulltimePriceCents: 4990,
147
- },
148
67
  sade: {
149
68
  displayName: 'SADE',
150
69
  role: 'AI Salesforce Developer',
@@ -154,6 +73,33 @@ exports.PERSONA_HIRE_CATALOG = {
154
73
  jobPriceCents: 1490,
155
74
  fulltimePriceCents: 6490,
156
75
  },
76
+ pam: {
77
+ displayName: 'PaM',
78
+ role: 'AI Product Manager',
79
+ emoji: '📋',
80
+ gradient: 'linear-gradient(135deg, #8b5cf6 0%, #d946ef 100%)',
81
+ blurb: 'Owns specs, PRDs, technical design, issue prep, and the path from idea to shippable artifact.',
82
+ jobPriceCents: 790,
83
+ fulltimePriceCents: 4990,
84
+ },
85
+ huxley: {
86
+ displayName: 'hUXley',
87
+ role: 'AI UX / Brand Designer',
88
+ emoji: '🎨',
89
+ gradient: 'linear-gradient(135deg, #ec4899 0%, #f472b6 100%)',
90
+ blurb: 'Builds design systems, prototypes polished user-facing surfaces, and carries brand decisions into shipped product experiences.',
91
+ jobPriceCents: 1490,
92
+ fulltimePriceCents: 5490,
93
+ },
94
+ gautam: {
95
+ displayName: 'GauTaM',
96
+ role: 'AI GTM & Marketing Manager',
97
+ emoji: '📣',
98
+ gradient: 'linear-gradient(135deg, #f97316 0%, #f59e0b 100%)',
99
+ blurb: 'Defines marketing strategy, ships content, runs launches, and owns the brand voice in market.',
100
+ jobPriceCents: 890,
101
+ fulltimePriceCents: 4990,
102
+ },
157
103
  sam: {
158
104
  displayName: 'SAM',
159
105
  role: 'AI Sales Account Manager',
@@ -172,15 +118,6 @@ exports.PERSONA_HIRE_CATALOG = {
172
118
  jobPriceCents: 990,
173
119
  fulltimePriceCents: 4990,
174
120
  },
175
- deidre: {
176
- displayName: 'DEIdre',
177
- role: 'AI Inclusion Leader',
178
- emoji: '🌍',
179
- gradient: 'linear-gradient(135deg, #9333ea 0%, #d946ef 100%)',
180
- blurb: 'Audits equity gaps, designs bias-aware AI governance, builds ERG toolkits, and creates inclusion-fluency programs.',
181
- jobPriceCents: 890,
182
- fulltimePriceCents: 4490,
183
- },
184
121
  mona: {
185
122
  displayName: 'MONa',
186
123
  role: 'AI Finance Manager',
@@ -190,14 +127,32 @@ exports.PERSONA_HIRE_CATALOG = {
190
127
  jobPriceCents: 990,
191
128
  fulltimePriceCents: 4990,
192
129
  },
193
- sreya: {
194
- displayName: 'SREya',
195
- role: 'AI Site Reliability Engineer',
196
- emoji: '☁️',
197
- gradient: 'linear-gradient(135deg, #2563eb 0%, #10b981 100%)',
198
- blurb: 'Manages deployments, monitors uptime, optimizes cloud cost, and keeps infrastructure resilient and observable.',
199
- jobPriceCents: 1290,
200
- fulltimePriceCents: 5990,
130
+ hari: {
131
+ displayName: 'HaRi',
132
+ role: 'AI HR Manager',
133
+ emoji: '👥',
134
+ gradient: 'linear-gradient(135deg, #0d9488 0%, #059669 100%)',
135
+ blurb: 'Manages onboarding, performance reviews, benefits analysis, payroll coordination, and HR business-partner advisory.',
136
+ jobPriceCents: 790,
137
+ fulltimePriceCents: 4490,
138
+ },
139
+ ricardo: {
140
+ displayName: 'RECardo',
141
+ role: 'AI Recruiter',
142
+ emoji: '🤝',
143
+ gradient: 'linear-gradient(135deg, #6366f1 0%, #ec4899 100%)',
144
+ blurb: 'Sources candidates, writes job descriptions, screens pipelines, and manages the hiring loop end-to-end.',
145
+ jobPriceCents: 790,
146
+ fulltimePriceCents: 4490,
147
+ },
148
+ cela: {
149
+ displayName: 'CELiA',
150
+ role: 'AI Legal Counsel',
151
+ emoji: '⚖️',
152
+ gradient: 'linear-gradient(135deg, #475569 0%, #6366f1 100%)',
153
+ blurb: 'Drafts and reviews contracts, NDAs, patents, trademarks, and the SaaS legal stack.',
154
+ jobPriceCents: 1990,
155
+ fulltimePriceCents: 6990,
201
156
  },
202
157
  procella: {
203
158
  displayName: 'PROCella',
@@ -226,6 +181,60 @@ exports.PERSONA_HIRE_CATALOG = {
226
181
  jobPriceCents: 1490,
227
182
  fulltimePriceCents: 6490,
228
183
  },
184
+ deidre: {
185
+ displayName: 'DEIdre',
186
+ role: 'AI Inclusion Leader',
187
+ emoji: '🌍',
188
+ gradient: 'linear-gradient(135deg, #9333ea 0%, #d946ef 100%)',
189
+ blurb: 'Audits equity gaps, designs bias-aware AI governance, builds ERG toolkits, and creates inclusion-fluency programs.',
190
+ jobPriceCents: 890,
191
+ fulltimePriceCents: 4490,
192
+ },
193
+ careena: {
194
+ displayName: 'CAREEna',
195
+ role: 'AI Career Coach',
196
+ emoji: '🎓',
197
+ gradient: 'linear-gradient(135deg, #0ea5e9 0%, #6366f1 100%)',
198
+ blurb: 'Runs the candidate-side search loop: role sourcing, application execution, networking, interview prep, and close-stage offer strategy.',
199
+ jobPriceCents: 890,
200
+ fulltimePriceCents: 4990,
201
+ },
202
+ ashley: {
203
+ displayName: 'AshLey',
204
+ role: 'AI Executive Assistant',
205
+ emoji: '📅',
206
+ gradient: 'linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%)',
207
+ blurb: 'Owns executive coordination, weekly operating reviews, and portfolio reporting across the workforce.',
208
+ jobPriceCents: 490,
209
+ fulltimePriceCents: 2990,
210
+ },
211
+ mandy: {
212
+ displayName: 'MANdy',
213
+ role: 'AI Manager',
214
+ emoji: '🎯',
215
+ gradient: 'linear-gradient(135deg, #7c3aed 0%, #4338ca 100%)',
216
+ blurb: 'Plans the job sequence, runs sub-agents in parallel, coaches them through verification loops, and hands back a synthesized DRAFT for your approval.',
217
+ jobPriceCents: 1490,
218
+ fulltimePriceCents: 5990,
219
+ },
220
+ beza: {
221
+ displayName: 'BeZa',
222
+ role: 'AI Business Strategist',
223
+ emoji: '🧭',
224
+ gradient: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)',
225
+ blurb: 'Turns ideas into structured business plans, validates founder-market fit, and pressure-tests strategy.',
226
+ jobPriceCents: 990,
227
+ fulltimePriceCents: 4990,
228
+ },
229
+ maestro: {
230
+ displayName: 'MAESTRO',
231
+ role: 'Full-Brained AI Employee',
232
+ emoji: '★',
233
+ gradient: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #d946ef 100%)',
234
+ blurb: 'One AI employee who can take a job from any function and ship it back with evidence. You set the direction. You sign off on what ships. Maestro does the work.',
235
+ jobPriceCents: 24900, // $249 — full-time only; job mode maps to same price
236
+ fulltimePriceCents: 24900, // $249/mo
237
+ },
229
238
  };
230
239
  exports.PERSONA_AVATAR_CATALOG = {
231
240
  maestro: { seed: 'MAESTRO-founder-mode', bg: 'fde68a', style: 'notionists' },
@@ -251,6 +260,7 @@ exports.PERSONA_AVATAR_CATALOG = {
251
260
  procella: { seed: 'PROCELLA-procurement', bg: 'ccfbf1', style: 'notionists' },
252
261
  banke: { seed: 'BANKe-banking-kyc', bg: 'ccfbf1', style: 'notionists' },
253
262
  auditya: { seed: 'AUDITya-banking-audit', bg: 'e9d5ff', style: 'notionists' },
263
+ aida: { seed: 'AIda-ai-engineer', bg: 'c7d2fe', style: 'notionists' },
254
264
  };
255
265
  function buildPersonaAvatarUrl(personaKey) {
256
266
  const avatar = exports.PERSONA_AVATAR_CATALOG[personaKey];