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.
- package/dist/src/ai-hub/catalog.js +43 -36
- package/dist/src/ai-hub/server.js +28 -5
- package/dist/src/cli/commands/init-project.js +1 -98
- package/dist/src/cli/commands/manager.js +40 -0
- package/dist/src/cli/commands/sync.js +17 -21
- package/dist/src/cli/fraim.js +2 -0
- package/dist/src/cli/utils/github-workflow-sync.js +12 -146
- package/dist/src/cli/utils/manager-pack-sync.js +188 -0
- package/dist/src/cli/utils/manager-publish.js +76 -0
- package/dist/src/cli/utils/user-config.js +20 -0
- package/dist/src/core/config-loader.js +9 -5
- package/dist/src/core/fraim-config-schema.generated.js +85 -31
- package/dist/src/core/manager-pack.js +26 -0
- package/dist/src/core/utils/local-registry-resolver.js +8 -1
- package/dist/src/first-run/install-state.js +1 -0
- package/dist/src/first-run/server.js +9 -0
- package/dist/src/first-run/session-service.js +117 -23
- package/dist/src/first-run/types.js +2 -5
- package/dist/src/local-mcp-server/learning-context-builder.js +45 -8
- package/dist/src/local-mcp-server/stdio-server.js +28 -0
- package/index.js +1 -1
- package/package.json +4 -1
- package/public/ai-hub/powerpoint-taskpane/index.html +236 -236
- package/public/ai-hub/powerpoint-taskpane/manifest.xml +29 -29
- package/public/ai-hub/review.css +13 -0
- package/public/ai-hub/script.js +199 -5
- package/public/ai-hub/styles.css +28 -0
- package/public/first-run/index.html +1 -1
- package/public/first-run/script.js +459 -530
- package/public/first-run/styles.css +288 -73
- package/public/portfolio/ashley.html +523 -0
- package/public/portfolio/auditya.html +83 -0
- package/public/portfolio/banke.html +83 -0
- package/public/portfolio/beza.html +659 -0
- package/public/portfolio/careena.html +632 -0
- package/public/portfolio/casey.html +568 -0
- package/public/portfolio/celia.html +490 -0
- package/public/portfolio/deidre.html +642 -0
- package/public/portfolio/gautam.html +597 -0
- package/public/portfolio/hari.html +469 -0
- package/public/portfolio/huxley.html +1354 -0
- package/public/portfolio/index.html +741 -0
- package/public/portfolio/maestro.html +518 -0
- package/public/portfolio/mandy.html +590 -0
- package/public/portfolio/mona.html +597 -0
- package/public/portfolio/pam.html +887 -0
- package/public/portfolio/procella.html +107 -0
- package/public/portfolio/qasm.html +569 -0
- package/public/portfolio/ricardo.html +489 -0
- package/public/portfolio/sade.html +560 -0
- package/public/portfolio/sam.html +654 -0
- package/public/portfolio/sechar.html +580 -0
- package/public/portfolio/sreya.html +599 -0
- package/public/portfolio/swen.html +601 -0
- package/dist/src/ai-hub/word-sideload.js +0 -95
- package/dist/src/cli/commands/test-mcp.js +0 -171
- package/dist/src/cli/setup/first-run.js +0 -242
- package/dist/src/core/config-writer.js +0 -75
- package/dist/src/core/utils/job-aliases.js +0 -47
- package/dist/src/core/utils/workflow-parser.js +0 -174
package/public/ai-hub/script.js
CHANGED
|
@@ -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
|
-
//
|
|
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 = '
|
|
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
|
-
|
|
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
|
}
|
package/public/ai-hub/styles.css
CHANGED
|
@@ -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>
|