fraim 2.0.166 → 2.0.168

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.
Files changed (60) hide show
  1. package/dist/src/ai-hub/catalog.js +43 -36
  2. package/dist/src/ai-hub/server.js +28 -5
  3. package/dist/src/cli/commands/init-project.js +1 -98
  4. package/dist/src/cli/commands/manager.js +40 -0
  5. package/dist/src/cli/commands/sync.js +17 -21
  6. package/dist/src/cli/fraim.js +2 -0
  7. package/dist/src/cli/utils/github-workflow-sync.js +12 -146
  8. package/dist/src/cli/utils/manager-pack-sync.js +188 -0
  9. package/dist/src/cli/utils/manager-publish.js +76 -0
  10. package/dist/src/cli/utils/user-config.js +20 -0
  11. package/dist/src/core/config-loader.js +9 -5
  12. package/dist/src/core/fraim-config-schema.generated.js +85 -31
  13. package/dist/src/core/manager-pack.js +26 -0
  14. package/dist/src/core/utils/local-registry-resolver.js +8 -1
  15. package/dist/src/first-run/install-state.js +1 -0
  16. package/dist/src/first-run/server.js +9 -0
  17. package/dist/src/first-run/session-service.js +117 -23
  18. package/dist/src/first-run/types.js +2 -5
  19. package/dist/src/local-mcp-server/learning-context-builder.js +45 -8
  20. package/dist/src/local-mcp-server/stdio-server.js +28 -0
  21. package/index.js +1 -1
  22. package/package.json +4 -1
  23. package/public/ai-hub/powerpoint-taskpane/index.html +236 -236
  24. package/public/ai-hub/powerpoint-taskpane/manifest.xml +29 -29
  25. package/public/ai-hub/review.css +13 -0
  26. package/public/ai-hub/script.js +199 -5
  27. package/public/ai-hub/styles.css +28 -0
  28. package/public/first-run/index.html +1 -1
  29. package/public/first-run/script.js +459 -530
  30. package/public/first-run/styles.css +288 -73
  31. package/public/portfolio/ashley.html +523 -0
  32. package/public/portfolio/auditya.html +83 -0
  33. package/public/portfolio/banke.html +83 -0
  34. package/public/portfolio/beza.html +659 -0
  35. package/public/portfolio/careena.html +632 -0
  36. package/public/portfolio/casey.html +568 -0
  37. package/public/portfolio/celia.html +490 -0
  38. package/public/portfolio/deidre.html +642 -0
  39. package/public/portfolio/gautam.html +597 -0
  40. package/public/portfolio/hari.html +469 -0
  41. package/public/portfolio/huxley.html +1354 -0
  42. package/public/portfolio/index.html +741 -0
  43. package/public/portfolio/maestro.html +518 -0
  44. package/public/portfolio/mandy.html +590 -0
  45. package/public/portfolio/mona.html +597 -0
  46. package/public/portfolio/pam.html +887 -0
  47. package/public/portfolio/procella.html +107 -0
  48. package/public/portfolio/qasm.html +569 -0
  49. package/public/portfolio/ricardo.html +489 -0
  50. package/public/portfolio/sade.html +560 -0
  51. package/public/portfolio/sam.html +654 -0
  52. package/public/portfolio/sechar.html +580 -0
  53. package/public/portfolio/sreya.html +599 -0
  54. package/public/portfolio/swen.html +601 -0
  55. package/dist/src/ai-hub/word-sideload.js +0 -95
  56. package/dist/src/cli/commands/test-mcp.js +0 -171
  57. package/dist/src/cli/setup/first-run.js +0 -242
  58. package/dist/src/core/config-writer.js +0 -75
  59. package/dist/src/core/utils/job-aliases.js +0 -47
  60. package/dist/src/core/utils/workflow-parser.js +0 -174
@@ -756,6 +756,14 @@ function renderRail() {
756
756
  titleSpan.textContent = conv.title || '';
757
757
  bodyDiv.appendChild(titleSpan);
758
758
  btn.appendChild(bodyDiv);
759
+ // Issue #566 R7: mark runs of a personalized (taught/customized) job.
760
+ if (isTaughtJob(conv.jobId)) {
761
+ const taught = document.createElement('span');
762
+ taught.className = 'taught-badge';
763
+ taught.textContent = 'Personalized';
764
+ taught.title = 'Personalized for this project (taught or customized in your fraim/personalized-employee layer)';
765
+ btn.appendChild(taught);
766
+ }
759
767
  const dotClass = conversationStateDotClass(conv);
760
768
  const statusDot = document.createElement('span');
761
769
  statusDot.className = 'state-dot conv-state-dot dot-' + dotClass;
@@ -1420,6 +1428,9 @@ function renderActive() {
1420
1428
  // Issue #512 R7 — review experience: completion card + format artifact strip
1421
1429
  // + unified action bar (review actions sit beside the always-on coaching chips).
1422
1430
  renderReviewExperience(conv);
1431
+ // Issue #566 R4 — after the manager confirms success, offer to teach this as a
1432
+ // job (ad-hoc) or remember the coached steps for the job (structured).
1433
+ renderGrowthOffer(conv);
1423
1434
  syncThreadMessageViewport();
1424
1435
  const m = els['messages'];
1425
1436
  const runningShouldStickToBottom = !!m && conv.status === 'running'
@@ -2833,6 +2844,157 @@ function renderReviewExperience(conv) {
2833
2844
  }
2834
2845
  }
2835
2846
 
2847
+ // ---------------------------------------------------------------------------
2848
+ // Issue #566 — personalize the employee: success-confirmed growth offer.
2849
+ // After the manager confirms a run succeeded (Approve / Mark complete / Good
2850
+ // job), the employee offers to make the work repeatable. Ad-hoc runs with no
2851
+ // matching job → "teach as a job"; structured runs the manager coached →
2852
+ // "remember these steps" (customize). The offer is a thin relay: accepting it
2853
+ // starts the evolve-employee job seeded with the run; that job owns the actual
2854
+ // teaching, dedup, ownership, validation, and security review.
2855
+ // ---------------------------------------------------------------------------
2856
+
2857
+ function jobCatalogEntry(jobId) {
2858
+ const jobs = (state.bootstrap && state.bootstrap.jobs) ? state.bootstrap.jobs : [];
2859
+ return jobs.find((j) => j.id === jobId) || null;
2860
+ }
2861
+
2862
+ // A run's job is personalized when its catalog entry came from the
2863
+ // personalized-employee layer (R7). No author/attribution is tracked.
2864
+ function isTaughtJob(jobId) {
2865
+ const entry = jobCatalogEntry(jobId);
2866
+ return !!(entry && entry.personalized);
2867
+ }
2868
+
2869
+ function growthOfferHost() {
2870
+ const messages = els['messages'];
2871
+ if (!messages) return null;
2872
+ let host = messages.querySelector('#growth-offer');
2873
+ if (!host) {
2874
+ host = document.createElement('div');
2875
+ host.id = 'growth-offer';
2876
+ host.className = 'review-completion';
2877
+ }
2878
+ // Keep it after the latest message / completion card.
2879
+ messages.appendChild(host);
2880
+ return host;
2881
+ }
2882
+
2883
+ function clearGrowthOffer() {
2884
+ const messages = els['messages'];
2885
+ const existing = messages && messages.querySelector('#growth-offer');
2886
+ if (existing) existing.remove();
2887
+ }
2888
+
2889
+ function renderGrowthOffer(conv) {
2890
+ // Gate on the run being successfully done (R4):
2891
+ // - Ad-hoc (watercooler) runs: any *completed* run qualifies. This is how an
2892
+ // existing watercooler conversation already sitting in the rail shows a
2893
+ // teach trigger when the manager opens it (the issue's headline use case) —
2894
+ // ad-hoc runs have no review/approve step, so "done" is the signal.
2895
+ // - Structured runs: require the manager's explicit approval (reviewApproved).
2896
+ // A structured run that merely finished is still awaiting the manager's
2897
+ // review, so we do not offer to customize it until they confirm it was good.
2898
+ // Never offer while a run is still in flight, on the teach run itself, or once
2899
+ // dismissed/acted on for this run.
2900
+ const isAdhoc = !!(conv && conv.jobId === '__freeform__');
2901
+ const succeeded = !!(conv && (conv.reviewApproved || (isAdhoc && conv.status === 'completed')));
2902
+ const isTeachRun = !!(conv && conv.jobId === 'evolve-employee');
2903
+ if (!succeeded || isTeachRun || conv.growthOfferDismissed || conv.growthOfferActed) {
2904
+ clearGrowthOffer();
2905
+ return;
2906
+ }
2907
+ const host = growthOfferHost();
2908
+ if (!host) return;
2909
+ const adhoc = conv.jobId === '__freeform__';
2910
+ host.innerHTML = '';
2911
+
2912
+ const card = document.createElement('div');
2913
+ card.className = 'review-card review-card--offer';
2914
+ card.id = 'growth-offer-card';
2915
+ const heading = document.createElement('div');
2916
+ heading.className = 'rc-heading';
2917
+ heading.textContent = adhoc ? '🎓 Want me to learn this?' : '🎓 Remember these steps?';
2918
+ card.appendChild(heading);
2919
+
2920
+ const rows = adhoc
2921
+ ? [
2922
+ ['What happened', 'You approved an ad-hoc task that has no matching job yet.'],
2923
+ ['Offer', 'I can turn this into a repeatable job so you can ask for it by name next time.'],
2924
+ ]
2925
+ : [
2926
+ ['What happened', `You approved "${conv.jobTitle || conv.title || 'this run'}" and coached it along the way.`],
2927
+ ['Offer', 'I can fold those steps into this job so I do them automatically next time.'],
2928
+ ];
2929
+ for (const [k, v] of rows) {
2930
+ const r = document.createElement('div');
2931
+ r.className = 'rc-row';
2932
+ const ks = document.createElement('span');
2933
+ ks.className = 'rc-k';
2934
+ ks.textContent = k;
2935
+ const vs = document.createElement('span');
2936
+ vs.className = 'rc-v';
2937
+ vs.textContent = v;
2938
+ r.appendChild(ks);
2939
+ r.appendChild(vs);
2940
+ card.appendChild(r);
2941
+ }
2942
+ host.appendChild(card);
2943
+
2944
+ const strip = document.createElement('div');
2945
+ strip.className = 'rc-artifact-strip';
2946
+ const teachBtn = document.createElement('button');
2947
+ teachBtn.type = 'button';
2948
+ teachBtn.className = 'rc-art-btn rc-art-btn--teach';
2949
+ teachBtn.id = 'growth-teach-btn';
2950
+ teachBtn.textContent = adhoc ? '🎓 Teach as a job' : '✨ Remember for this job';
2951
+ teachBtn.addEventListener('click', () => {
2952
+ conv.growthOfferActed = true;
2953
+ upsertConversation(conv);
2954
+ startTeachFlow({ seedConv: conv, mode: adhoc ? 'teach' : 'customize', seedSource: 'run' });
2955
+ });
2956
+ const dismissBtn = document.createElement('button');
2957
+ dismissBtn.type = 'button';
2958
+ dismissBtn.className = 'rc-art-btn ghost';
2959
+ dismissBtn.id = 'growth-dismiss-btn';
2960
+ dismissBtn.textContent = 'Not now';
2961
+ dismissBtn.addEventListener('click', () => {
2962
+ conv.growthOfferDismissed = true;
2963
+ upsertConversation(conv);
2964
+ renderActive();
2965
+ });
2966
+ strip.appendChild(teachBtn);
2967
+ strip.appendChild(dismissBtn);
2968
+ host.appendChild(strip);
2969
+ }
2970
+
2971
+ // Start the evolve-employee job, optionally seeded from a completed run or a
2972
+ // document. This is the single entry point shared by the success-confirmed
2973
+ // offer and the "+ New job" palette teach entries (R1/R4).
2974
+ function startTeachFlow(opts) {
2975
+ const options = opts || {};
2976
+ const employeeId = (state.bootstrap && state.bootstrap.preferences && state.bootstrap.preferences.employeeId) || 'claude';
2977
+ const seedConv = options.seedConv || null;
2978
+ let instructions = '';
2979
+ if (options.seedSource === 'run' && seedConv) {
2980
+ const transcript = (seedConv.messages || [])
2981
+ .map((m) => `${m.role}: ${typeof m.text === 'string' ? m.text : ''}`)
2982
+ .join('\n')
2983
+ .slice(0, 4000);
2984
+ if (options.mode === 'customize') {
2985
+ instructions = `Customize the "${seedConv.jobTitle || seedConv.jobId}" job. I coached this run with steps you should remember next time. Use this run as the seed: pre-fill the intake from the coaching turns and confirm with me before changing anything. Keep the same owning employee and preserve the existing phases.\n\nRun: ${seedConv.title || ''}\n\nTranscript:\n${transcript}`;
2986
+ } else {
2987
+ instructions = `Teach yourself a new repeatable job from this successful run. Use the run as the seed: restate the ask, the steps you performed, and the deliverable; propose a job name; check for an existing job first; and recommend which employee should own it.\n\nRun: ${seedConv.title || ''}\n\nTranscript:\n${transcript}`;
2988
+ }
2989
+ } else if (options.seedSource === 'document') {
2990
+ instructions = 'Teach yourself a new job from an SOP or document. Ask me for the document, then map its sections and steps onto job phases and confirm the mapping with me before drafting. Recommend which employee should own it.';
2991
+ } else {
2992
+ instructions = 'Teach yourself a new job. I will describe the capability I want. Pre-fill the plan, check for an existing job first, and recommend which employee should own it.';
2993
+ }
2994
+ const job = { id: 'evolve-employee', title: options.mode === 'customize' ? 'Improve a job' : 'Teach a new skill' };
2995
+ startRun(job, instructions, employeeId);
2996
+ }
2997
+
2836
2998
  // Comments follow the artifact (R7.7): a PR opens GitHub; local artifacts open
2837
2999
  // from disk; reports view inline. Nothing new is invented - these surface what
2838
3000
  // the read-side already knows and otherwise leave a status hint.
@@ -3188,10 +3350,28 @@ function renderCpRows(searchText) {
3188
3350
  }
3189
3351
  }
3190
3352
 
3191
- // Build flat row list: recent first, then catalog
3353
+ // Issue #566 R4: teach entries start the evolve-employee job. They appear at
3354
+ // the top of the catalog, only when no persona filter is active (teaching is
3355
+ // not persona-scoped), and match a search over their labels.
3356
+ const teachRows = [];
3357
+ if (personaFilter === null) {
3358
+ if (!q || 'teach a new skill'.includes(q)) {
3359
+ teachRows.push({ type: 'teach', teachSeed: 'description', job: { id: 'evolve-employee', title: 'Teach a new skill', intent: 'Teach me a new job in plain language.' } });
3360
+ }
3361
+ if (!q || 'teach from a document'.includes(q) || 'sop'.includes(q)) {
3362
+ teachRows.push({ type: 'teach', teachSeed: 'document', job: { id: 'evolve-employee', title: 'Teach from a document', intent: 'Point me at an SOP or document and I will turn it into a phased job.' } });
3363
+ }
3364
+ }
3365
+
3366
+ // Build flat row list: recent first, then catalog jobs, then teach entries
3367
+ // last. Teach entries go after the catalog jobs so the first runnable job
3368
+ // stays the default keyboard selection (ArrowDown+Enter runs a job, not a
3369
+ // teach flow). The flat order must match the render order below so
3370
+ // click/keyboard indices line up.
3192
3371
  state.cpRows = [
3193
3372
  ...recentRows,
3194
3373
  ...catalogJobs.map((j) => ({ type: 'job', job: j, instructions: '' })),
3374
+ ...teachRows,
3195
3375
  ];
3196
3376
  state.cpHighlightIndex = -1;
3197
3377
 
@@ -3206,13 +3386,17 @@ function renderCpRows(searchText) {
3206
3386
  });
3207
3387
  }
3208
3388
 
3209
- // Render catalog section
3389
+ // Render catalog section: catalog jobs first, then teach entries last.
3390
+ // flatIndex continues from recentRows so it matches state.cpRows order.
3210
3391
  const catalogList = document.getElementById('cp-catalog-list');
3211
3392
  if (catalogList) {
3212
3393
  catalogList.innerHTML = '';
3213
3394
  catalogJobs.forEach((job, i) => {
3214
3395
  catalogList.appendChild(buildCpRow({ type: 'job', job, instructions: '' }, recentRows.length + i));
3215
3396
  });
3397
+ teachRows.forEach((row, i) => {
3398
+ catalogList.appendChild(buildCpRow(row, recentRows.length + catalogJobs.length + i));
3399
+ });
3216
3400
  }
3217
3401
 
3218
3402
  // Update employee footer
@@ -3233,7 +3417,7 @@ function buildCpRow(row, flatIndex) {
3233
3417
 
3234
3418
  const icon = document.createElement('span');
3235
3419
  icon.className = 'cp-row-icon';
3236
- icon.textContent = row.type === 'recent' ? '🕐' : '📋';
3420
+ icon.textContent = row.type === 'recent' ? '🕐' : row.type === 'teach' ? '🎓' : '📋';
3237
3421
 
3238
3422
  const body = document.createElement('span');
3239
3423
  body.className = 'cp-row-body';
@@ -3305,6 +3489,12 @@ function selectCpRow(index) {
3305
3489
 
3306
3490
  if (row.type === 'recent' && row.instructions) {
3307
3491
  instr.value = row.instructions;
3492
+ } else if (row.type === 'teach') {
3493
+ // Issue #566: seed the instruction so the manager confirms rather than
3494
+ // re-authoring. The evolve-employee job reads the seed source from this.
3495
+ instr.value = row.teachSeed === 'document'
3496
+ ? 'Teach yourself a new job from this document: '
3497
+ : 'Teach yourself a new job: ';
3308
3498
  } else {
3309
3499
  instr.value = '';
3310
3500
  }
@@ -3614,7 +3804,7 @@ function renderAgentInstallPanel() {
3614
3804
 
3615
3805
  const skipBtn = document.createElement('button');
3616
3806
  skipBtn.className = 'ghost small';
3617
- skipBtn.textContent = 'Skip for now';
3807
+ skipBtn.textContent = 'Choose another agent';
3618
3808
  skipBtn.style.marginLeft = '6px';
3619
3809
  skipBtn.addEventListener('click', () => {
3620
3810
  delete agentInstallState[emp.id];
@@ -4747,7 +4937,7 @@ function wireEvents() {
4747
4937
  }
4748
4938
 
4749
4939
  if (isFirstRun) {
4750
- renderFirstRunLanding();
4940
+ renderActive();
4751
4941
  return;
4752
4942
  }
4753
4943
 
@@ -7668,4 +7858,8 @@ if (typeof window !== 'undefined') {
7668
7858
  window.persistConversations = persistConversations;
7669
7859
  window.renderRail = renderRail;
7670
7860
  window.renderActive = renderActive;
7861
+ // Issue #566: let the suite open a conversation and read the active one so it
7862
+ // can exercise the success-confirmed growth offer deterministically.
7863
+ window.switchToConversation = switchToConversation;
7864
+ window.activeConversation = activeConversation;
7671
7865
  }
@@ -26,6 +26,10 @@
26
26
  --shadow-lg: 0 0 0 1px rgba(0, 0, 0, 0.10), 0 4px 16px rgba(0, 0, 0, 0.08);
27
27
  --picker-delegation: #5b7eb0;
28
28
  --picker-learning: #8b5fbf;
29
+ /* Issue #566: personalization / "teach" accent (growth offer + taught badge). */
30
+ --teach: #5b3fa8;
31
+ --teach-soft: #f6f2ff;
32
+ --teach-line: #d8cdf2;
29
33
  /* Safe zone for macOS hiddenInset traffic lights (~28px) and
30
34
  Windows titleBarOverlay height (36px). Falls back to 0 in a browser tab. */
31
35
  --titlebar-inset: env(titlebar-area-height, 0px);
@@ -1727,6 +1731,14 @@ button.small { padding: 4px 10px; font-size: 12px; }
1727
1731
  keep the journey visible. */
1728
1732
  @media (max-width: 640px) {
1729
1733
  .tracker .stage .stage-label { display: none; }
1734
+ .tracker .stage { min-width: 24px; }
1735
+ .tracker .stage-circle {
1736
+ width: 20px;
1737
+ height: 20px;
1738
+ border-width: 1px;
1739
+ font-size: 10px;
1740
+ }
1741
+ .tracker .stage::before { top: 10px; }
1730
1742
  .tracker-active-label { display: block; }
1731
1743
  }
1732
1744
 
@@ -2047,6 +2059,22 @@ button.small { padding: 4px 10px; font-size: 12px; }
2047
2059
  flex-shrink: 0;
2048
2060
  }
2049
2061
 
2062
+ /* Issue #566 R7: "Taught by you" marking on runs of a personalized job.
2063
+ Follows the .ab-badge inline-badge pattern with a distinct purple accent. */
2064
+ .taught-badge {
2065
+ margin-left: auto;
2066
+ padding: 1px 6px;
2067
+ font-size: 9.5px;
2068
+ font-weight: 700;
2069
+ letter-spacing: 0.03em;
2070
+ border-radius: 999px;
2071
+ background: var(--teach-soft);
2072
+ color: var(--teach);
2073
+ border: 1px solid var(--teach-line);
2074
+ white-space: nowrap;
2075
+ flex-shrink: 0;
2076
+ }
2077
+
2050
2078
  /* Full-panel A/B split: #conversation becomes a flex row */
2051
2079
  #conversation.ab-mode {
2052
2080
  display: flex;
@@ -20,8 +20,8 @@
20
20
  Replaced wholesale by script.js once /api/first-run/session returns. -->
21
21
  <li class="row skeleton-row" data-row-id="node" data-row-status="pending"><span class="icon" aria-hidden="true">.</span><span class="label">Node.js</span><span class="verb" data-testid="row-verb">checking...</span></li>
22
22
  <li class="row skeleton-row" data-row-id="git" data-row-status="pending"><span class="icon" aria-hidden="true">.</span><span class="label">git</span><span class="verb" data-testid="row-verb">checking...</span></li>
23
+ <li class="row skeleton-row" data-row-id="agent" data-row-status="pending"><span class="icon" aria-hidden="true">.</span><span class="label">Locally installed AI agents</span><span class="verb" data-testid="row-verb">checking...</span></li>
23
24
  <li class="row skeleton-row" data-row-id="fraim" data-row-status="pending"><span class="icon" aria-hidden="true">.</span><span class="label">FRAIM</span><span class="verb" data-testid="row-verb">checking...</span></li>
24
- <li class="row skeleton-row" data-row-id="agent" data-row-status="pending"><span class="icon" aria-hidden="true">.</span><span class="label">Execution surfaces</span><span class="verb" data-testid="row-verb">checking...</span></li>
25
25
  </ul>
26
26
  <div class="actions">
27
27
  <button id="primary-button" class="primary-button" data-testid="primary-button">Set up FRAIM</button>