fraim-framework 2.0.193 → 2.0.194
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/ai-hub/hosts.js
CHANGED
|
@@ -23,6 +23,7 @@ const child_process_1 = require("child_process");
|
|
|
23
23
|
const fs_1 = __importDefault(require("fs"));
|
|
24
24
|
const os_1 = __importDefault(require("os"));
|
|
25
25
|
const path_1 = __importDefault(require("path"));
|
|
26
|
+
const manager_turns_1 = require("./manager-turns");
|
|
26
27
|
const managed_agent_paths_1 = require("../cli/utils/managed-agent-paths");
|
|
27
28
|
const mcp_config_generator_1 = require("../cli/setup/mcp-config-generator");
|
|
28
29
|
const agent_token_prices_1 = require("../local-mcp-server/agent-token-prices");
|
|
@@ -702,11 +703,11 @@ function transformHeadlessFraimMessage(message, kind) {
|
|
|
702
703
|
if (!parsed)
|
|
703
704
|
return message;
|
|
704
705
|
if (!parsed.jobId) {
|
|
706
|
+
// Issue #732: single-source the same-job continue wording via
|
|
707
|
+
// buildSameJobContinueMessage so the server (prepareContinueMessage) and
|
|
708
|
+
// this headless transform stay in lock-step.
|
|
705
709
|
if (kind === 'continue') {
|
|
706
|
-
|
|
707
|
-
return `Continue the active FRAIM job with this manager coaching:\n\n${parsed.remainder}`;
|
|
708
|
-
}
|
|
709
|
-
return 'Continue the active FRAIM job using the current session context.';
|
|
710
|
+
return (0, manager_turns_1.buildSameJobContinueMessage)(parsed.remainder);
|
|
710
711
|
}
|
|
711
712
|
return message;
|
|
712
713
|
}
|
|
@@ -1351,6 +1352,10 @@ class FakeHostRuntime {
|
|
|
1351
1352
|
{ id: 'gemini', label: 'Gemini CLI', available: true, detail: 'Test double employee.', supportsRaw: true },
|
|
1352
1353
|
{ id: 'copilot', label: 'GitHub Copilot CLI', available: true, detail: 'Test double agent tool.', supportsRaw: true },
|
|
1353
1354
|
];
|
|
1355
|
+
// Remembered across turns like a resumed agent session: the job label from the
|
|
1356
|
+
// start turn. Issue #732 — a same-job continue no longer carries a /fraim <job>
|
|
1357
|
+
// invocation, so the fake falls back to this to stay job-aware on continue.
|
|
1358
|
+
this.lastJobLabel = null;
|
|
1354
1359
|
}
|
|
1355
1360
|
detectEmployees() {
|
|
1356
1361
|
return this.employees;
|
|
@@ -1407,9 +1412,12 @@ class FakeHostRuntime {
|
|
|
1407
1412
|
}
|
|
1408
1413
|
fakeEmployeeReply(kind, message) {
|
|
1409
1414
|
const parsed = parseFraimInvocation(message);
|
|
1410
|
-
const
|
|
1415
|
+
const parsedLabel = parsed?.jobId
|
|
1411
1416
|
? parsed.jobId.split('-').map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(' ')
|
|
1412
1417
|
: null;
|
|
1418
|
+
if (parsedLabel)
|
|
1419
|
+
this.lastJobLabel = parsedLabel;
|
|
1420
|
+
const label = parsedLabel || (kind === 'continue' ? this.lastJobLabel : null);
|
|
1413
1421
|
if (kind === 'continue') {
|
|
1414
1422
|
return label
|
|
1415
1423
|
? `Understood. I'm taking another pass with ${label}.`
|
|
@@ -1442,6 +1450,9 @@ class ScriptedHostRuntime {
|
|
|
1442
1450
|
// Tracks runDiscriminant per sessionId so consecutive emitPhase calls
|
|
1443
1451
|
// can resolve onSuccess routing without each test having to repeat it.
|
|
1444
1452
|
this.discriminantBySession = new Map();
|
|
1453
|
+
// Issue #732: capture the exact message the Hub sent on the most recent
|
|
1454
|
+
// continue turn so tests can assert whether it would re-trigger get_fraim_job.
|
|
1455
|
+
this.lastContinueMessage = null;
|
|
1445
1456
|
}
|
|
1446
1457
|
detectEmployees() {
|
|
1447
1458
|
return this.employees;
|
|
@@ -1452,7 +1463,8 @@ class ScriptedHostRuntime {
|
|
|
1452
1463
|
handlers.onEvent({ sessionId, raw: 'scripted-session-start' }, 'system');
|
|
1453
1464
|
return this.spawnDouble();
|
|
1454
1465
|
}
|
|
1455
|
-
continueRun(_hostId, _projectPath, sessionId,
|
|
1466
|
+
continueRun(_hostId, _projectPath, sessionId, message, handlers) {
|
|
1467
|
+
this.lastContinueMessage = message;
|
|
1456
1468
|
this.handlersBySession.set(sessionId, handlers);
|
|
1457
1469
|
handlers.onEvent({ sessionId, raw: 'scripted-session-resume' }, 'system');
|
|
1458
1470
|
return this.spawnDouble();
|
|
@@ -1517,6 +1529,20 @@ class ScriptedHostRuntime {
|
|
|
1517
1529
|
},
|
|
1518
1530
|
}, 'stdout');
|
|
1519
1531
|
}
|
|
1532
|
+
// Test API — emit a same-job seekMentoring signal that carries BOTH the
|
|
1533
|
+
// stable jobName slug and a per-invocation UUID jobId, exactly as a real
|
|
1534
|
+
// agent does after get_fraim_job (which mints a fresh randomUUID each call).
|
|
1535
|
+
// Issue #732: the tracker must key off jobName, not the UUID jobId.
|
|
1536
|
+
emitPhaseWithJobIds(runId, phaseId, status, jobName, jobId, findingsText) {
|
|
1537
|
+
const target = this.resolveSession(runId);
|
|
1538
|
+
if (!target)
|
|
1539
|
+
return;
|
|
1540
|
+
target.handlers.onEvent({
|
|
1541
|
+
sessionId: target.sessionId,
|
|
1542
|
+
raw: `scripted-seekMentoring-ids:${phaseId}:${status}`,
|
|
1543
|
+
seekMentoring: { phaseId, phaseStatus: status, jobName, jobId, findingsText },
|
|
1544
|
+
}, 'stdout');
|
|
1545
|
+
}
|
|
1520
1546
|
// Test API — emit the structured job-load signal that get_fraim_job
|
|
1521
1547
|
// produces before the first seekMentoring phase call.
|
|
1522
1548
|
emitFraimJobLoad(runId, jobId) {
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.extractExplicitFraimInvocation = extractExplicitFraimInvocation;
|
|
4
4
|
exports.fraimInvocationFor = fraimInvocationFor;
|
|
5
5
|
exports.buildCommunicationStyleNote = buildCommunicationStyleNote;
|
|
6
|
+
exports.buildSameJobContinueMessage = buildSameJobContinueMessage;
|
|
6
7
|
exports.buildManagerMessage = buildManagerMessage;
|
|
7
8
|
function extractExplicitFraimInvocation(text) {
|
|
8
9
|
const raw = String(text || '');
|
|
@@ -36,6 +37,18 @@ function buildCommunicationStyleNote() {
|
|
|
36
37
|
'[How to talk to me] In your messages to me, report ONLY on the job and its outcome — what you found, what you changed, the decisions you made, blockers, and what you need from me. Do NOT narrate the FRAIM machinery: don\'t announce that you are talking to FRAIM, asking your mentor, following the process, moving between phases, or calling tools (git, playwright, etc.). I can see the raw tool activity separately if I want it. Keep your updates short and about the work, not the process.',
|
|
37
38
|
].join('\n');
|
|
38
39
|
}
|
|
40
|
+
// Issue #732: a plain continue of the SAME active job must not re-load the job.
|
|
41
|
+
// Continue turns resume the existing agent session, which already has the job
|
|
42
|
+
// loaded, so this message carries NO `/fraim <job>` invocation — the headless
|
|
43
|
+
// transform therefore does not re-instruct get_fraim_job (which is slow and
|
|
44
|
+
// mints a fresh UUID job id every call). The manager coaching still reaches the
|
|
45
|
+
// agent, which advances the workflow via seekMentoring using its resumed context.
|
|
46
|
+
function buildSameJobContinueMessage(coaching) {
|
|
47
|
+
const remainder = String(coaching || '').trim();
|
|
48
|
+
return remainder
|
|
49
|
+
? `Continue the active FRAIM job with this manager coaching:\n\n${remainder}`
|
|
50
|
+
: 'Continue the active FRAIM job using the current session context.';
|
|
51
|
+
}
|
|
39
52
|
function buildManagerMessage(employeeId, jobId, kind, instructions, stubPath) {
|
|
40
53
|
const trimmed = String(instructions || '').trim();
|
|
41
54
|
const explicit = extractExplicitFraimInvocation(trimmed);
|
|
@@ -726,12 +726,17 @@ function applySeekMentoringSignal(run, signal) {
|
|
|
726
726
|
// Issue #347: filter cross-job pollution. The agent may call
|
|
727
727
|
// seekMentoring for jobs OTHER than the one this run is tracking
|
|
728
728
|
// (e.g., consulting `organizational-learning-synthesis` mid-run).
|
|
729
|
-
// Only apply signals whose
|
|
730
|
-
//
|
|
731
|
-
//
|
|
729
|
+
// Only apply signals whose job identity matches this run's.
|
|
730
|
+
// Issue #732: match on `jobName` — the stable job-name slug that equals
|
|
731
|
+
// run.jobId — NOT `jobId`. get_fraim_job mints a fresh randomUUID as the
|
|
732
|
+
// job id on every call and the agent echoes that UUID into
|
|
733
|
+
// seekMentoring.jobId, so a UUID never equals run.jobId. Comparing the UUID
|
|
734
|
+
// discarded every real phase signal as if it were foreign-job pollution and
|
|
735
|
+
// froze the tracker at "no phases done". The UUID cannot positively identify
|
|
736
|
+
// this run's job, so it must not participate in the match.
|
|
732
737
|
const targetJobId = run.jobId;
|
|
733
|
-
const
|
|
734
|
-
if (
|
|
738
|
+
const callJobName = signal.jobName;
|
|
739
|
+
if (callJobName && targetJobId && callJobName !== targetJobId)
|
|
735
740
|
return;
|
|
736
741
|
if (signal.reviewHandoff) {
|
|
737
742
|
const normalizedReviewHandoff = normalizeReviewHandoff(signal.reviewHandoff);
|
|
@@ -1862,7 +1867,10 @@ class AiHubServer {
|
|
|
1862
1867
|
return null;
|
|
1863
1868
|
}
|
|
1864
1869
|
applySeekMentoringSignalToRun(run, signal) {
|
|
1865
|
-
|
|
1870
|
+
// Issue #732: promote using the stable jobName slug, not the per-call UUID
|
|
1871
|
+
// jobId (resolveHubJob would never match a UUID, leaving a freeform run
|
|
1872
|
+
// unpromoted and then dropping its phase signals).
|
|
1873
|
+
this.maybePromoteFreeformRunToJob(run, signal.jobName || signal.jobId);
|
|
1866
1874
|
applySeekMentoringSignal(run, signal);
|
|
1867
1875
|
}
|
|
1868
1876
|
applyFraimJobSignalToRun(run, signal) {
|
|
@@ -1890,15 +1898,17 @@ class AiHubServer {
|
|
|
1890
1898
|
if (!resolvedJobId) {
|
|
1891
1899
|
throw new Error('Choose a FRAIM job before starting a run, or start with /fraim <job-id>.');
|
|
1892
1900
|
}
|
|
1893
|
-
//
|
|
1894
|
-
//
|
|
1895
|
-
|
|
1901
|
+
// `display` is the manager conversation bubble. It keeps the user's full
|
|
1902
|
+
// coaching text with the host-specific FRAIM invocation. The agent payload
|
|
1903
|
+
// still receives Hub-only helper blocks such as `[FRAIM shared browser]`
|
|
1904
|
+
// and `[How to talk to me]`; those are omitted from the visible bubble.
|
|
1896
1905
|
// #521: the shared-browser guidance is injected HERE, at the Hub layer — never
|
|
1897
1906
|
// baked into the registry job/skill. It only appears when a shared browser is
|
|
1898
1907
|
// available (env published at boot).
|
|
1899
1908
|
const browserNote = (0, managed_browser_1.buildBrowserContextNote)(process.env.FRAIM_BROWSER_CDP_ENDPOINT, process.env.FRAIM_HUB_BASE_URL);
|
|
1900
1909
|
const styleNote = (0, manager_turns_1.buildCommunicationStyleNote)();
|
|
1901
1910
|
if (resolvedJobId === '__freeform__') {
|
|
1911
|
+
const display = (0, manager_turns_1.buildManagerMessage)(hostId, resolvedJobId, 'start', instructions);
|
|
1902
1912
|
return {
|
|
1903
1913
|
jobId: resolvedJobId,
|
|
1904
1914
|
message: (0, manager_turns_1.buildManagerMessage)(hostId, resolvedJobId, 'start', instructions) + browserNote + styleNote,
|
|
@@ -1909,6 +1919,7 @@ class AiHubServer {
|
|
|
1909
1919
|
const absoluteStubPath = resolvedJob?.stubPath
|
|
1910
1920
|
? [projectPath, resolvedJob.stubPath].join('/').replace(/\\/g, '/').replace(/\/+/g, '/')
|
|
1911
1921
|
: undefined;
|
|
1922
|
+
const display = (0, manager_turns_1.buildManagerMessage)(hostId, resolvedJobId, 'start', instructions);
|
|
1912
1923
|
return {
|
|
1913
1924
|
jobId: resolvedJobId,
|
|
1914
1925
|
message: (0, manager_turns_1.buildManagerMessage)(hostId, resolvedJobId, 'start', instructions, absoluteStubPath) + browserNote + styleNote,
|
|
@@ -1923,14 +1934,27 @@ class AiHubServer {
|
|
|
1923
1934
|
// passes raw invocation syntax.
|
|
1924
1935
|
const explicit = (0, manager_turns_1.extractExplicitFraimInvocation)(instructions);
|
|
1925
1936
|
const effectiveJobId = explicit?.jobId || coachingJobId || run.jobId;
|
|
1926
|
-
// #
|
|
1927
|
-
//
|
|
1937
|
+
// #730: the manager thread shows the coaching turn with high fidelity — the
|
|
1938
|
+
// correct FRAIM command in front plus what the manager actually said — built
|
|
1939
|
+
// from the shared normalizer, exactly like the start path. With no
|
|
1940
|
+
// instructions (e.g. a plain coaching-template click) buildManagerMessage
|
|
1941
|
+
// collapses to just the invocation. The employee additionally receives the
|
|
1942
|
+
// communication-style note (and, per #732 below, a lighter same-job payload);
|
|
1943
|
+
// those operational details stay out of the visible bubble.
|
|
1928
1944
|
const userText = (explicit?.remainder || instructions || '').trim();
|
|
1929
|
-
const display = (0, manager_turns_1.
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1945
|
+
const display = (0, manager_turns_1.buildManagerMessage)(run.hostId, effectiveJobId, 'continue', instructions);
|
|
1946
|
+
// Issue #732: a plain continue of the SAME active real job must not re-load
|
|
1947
|
+
// the job via get_fraim_job on every coaching turn — the host session is
|
|
1948
|
+
// resumed with the job already in context, so re-fetching is wasted latency
|
|
1949
|
+
// and tokens (the reported hub slowness) and mints a fresh UUID job id each
|
|
1950
|
+
// time. Only load the job when the continue SWITCHES jobs (a coaching
|
|
1951
|
+
// template or an explicit /fraim <other>). Freeform/adhoc runs keep the
|
|
1952
|
+
// existing buildManagerMessage path (which already emits no invocation).
|
|
1953
|
+
const switchesJob = effectiveJobId !== run.jobId;
|
|
1954
|
+
const message = (switchesJob || run.jobId === '__freeform__')
|
|
1955
|
+
? (0, manager_turns_1.buildManagerMessage)(run.hostId, effectiveJobId, 'continue', instructions) + (0, manager_turns_1.buildCommunicationStyleNote)()
|
|
1956
|
+
: (0, manager_turns_1.buildSameJobContinueMessage)(userText) + (0, manager_turns_1.buildCommunicationStyleNote)();
|
|
1957
|
+
return { message, display };
|
|
1934
1958
|
}
|
|
1935
1959
|
async computePersonas(apiKey) {
|
|
1936
1960
|
const allBundles = listHubPersonaBundles();
|
|
@@ -1052,6 +1052,27 @@ class FraimLocalMCPServer {
|
|
|
1052
1052
|
this.collectStrings(response.result, strings);
|
|
1053
1053
|
return strings.some(s => /\{\{proxy\.delivery\.[^}]+\}\}/.test(s));
|
|
1054
1054
|
}
|
|
1055
|
+
getDeliveryPlaceholderKeys(response) {
|
|
1056
|
+
const keys = new Set();
|
|
1057
|
+
if (!response.result)
|
|
1058
|
+
return keys;
|
|
1059
|
+
const strings = [];
|
|
1060
|
+
this.collectStrings(response.result, strings);
|
|
1061
|
+
for (const text of strings) {
|
|
1062
|
+
for (const match of text.matchAll(/\{\{proxy\.delivery\.([^}]+)\}\}/g)) {
|
|
1063
|
+
keys.add(`proxy.delivery.${match[1].trim()}`);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
return keys;
|
|
1067
|
+
}
|
|
1068
|
+
deliveryTemplatesCoverPlaceholders(templates, response) {
|
|
1069
|
+
const requiredKeys = this.getDeliveryPlaceholderKeys(response);
|
|
1070
|
+
for (const key of requiredKeys) {
|
|
1071
|
+
if (!(key in templates))
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
return true;
|
|
1075
|
+
}
|
|
1055
1076
|
responseHasPotentialProviderPlaceholders(response) {
|
|
1056
1077
|
if (!response.result)
|
|
1057
1078
|
return false;
|
|
@@ -1123,15 +1144,17 @@ class FraimLocalMCPServer {
|
|
|
1123
1144
|
if (!this.responseContainsDeliveryPlaceholders(response))
|
|
1124
1145
|
return;
|
|
1125
1146
|
const engine = this.ensureEngine();
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1147
|
+
const currentTemplates = engine.loadDeliveryTemplates();
|
|
1148
|
+
// Normal proxy.delivery substitution is correct once the provider contains the
|
|
1149
|
+
// key. Older local registry caches can predate newly added delivery keys, so
|
|
1150
|
+
// treat missing required keys as stale cache and refresh from the server.
|
|
1151
|
+
if (currentTemplates && this.deliveryTemplatesCoverPlaceholders(currentTemplates, response))
|
|
1129
1152
|
return;
|
|
1130
1153
|
const filename = this.getWorkingStyle() === 'Conversation'
|
|
1131
1154
|
? 'delivery-conversation.json'
|
|
1132
1155
|
: 'delivery-pr.json';
|
|
1133
1156
|
const cached = this.readCachedTemplateFile(filename);
|
|
1134
|
-
if (cached) {
|
|
1157
|
+
if (cached && this.deliveryTemplatesCoverPlaceholders(cached, response)) {
|
|
1135
1158
|
engine.setDeliveryTemplates(cached);
|
|
1136
1159
|
return;
|
|
1137
1160
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-framework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.194",
|
|
4
4
|
"description": "FRAIM: AI Workforce Infrastructure — the organizational capability that turns AI agents into an accountable workforce, their operators into capable AI managers, and executives into leaders with clear optics on AI proficiency.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
package/public/ai-hub/script.js
CHANGED
|
@@ -2937,11 +2937,19 @@ function humanizeSlug(slug) {
|
|
|
2937
2937
|
.join(' ');
|
|
2938
2938
|
}
|
|
2939
2939
|
|
|
2940
|
-
function stripStubReference(text) {
|
|
2941
|
-
|
|
2940
|
+
function stripStubReference(text, options = {}) {
|
|
2941
|
+
const cleaned = String(text || '')
|
|
2942
2942
|
.replace(/\n?\[Job stub:[^\]]+\]/gi, '')
|
|
2943
|
-
.replace(/\[Thought:\s*true\]\s*/gi, '')
|
|
2944
|
-
|
|
2943
|
+
.replace(/\[Thought:\s*true\]\s*/gi, '');
|
|
2944
|
+
if (options.preserveWhitespace) {
|
|
2945
|
+
return cleaned.replace(/\n{3,}/g, '\n\n').trim();
|
|
2946
|
+
}
|
|
2947
|
+
return cleaned.replace(/\s+/g, ' ').trim();
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
function managerBubbleText(text) {
|
|
2951
|
+
return stripHubInjectedNotes(stripStubReference(text, { preserveWhitespace: true }))
|
|
2952
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
2945
2953
|
.trim();
|
|
2946
2954
|
}
|
|
2947
2955
|
|
|
@@ -3048,8 +3056,7 @@ function appendMessageDom(role, text, conv) {
|
|
|
3048
3056
|
const content = useSurfaced ? surfaced : raw;
|
|
3049
3057
|
bubble.innerHTML = formatEmployeeText(content || (conv.status === 'completed' ? 'Done — please review.' : 'Working on it…'));
|
|
3050
3058
|
} else {
|
|
3051
|
-
|
|
3052
|
-
bubble.textContent = surfaced || text;
|
|
3059
|
+
bubble.textContent = role === 'manager' ? managerBubbleText(text) : (surfaceText(role, text, conv) || text);
|
|
3053
3060
|
}
|
|
3054
3061
|
if (role === 'manager') {
|
|
3055
3062
|
const raw = document.createElement('span');
|
package/public/ai-hub/styles.css
CHANGED
|
@@ -1503,6 +1503,7 @@ img.coach-employee-avatar { object-fit: cover; border-radius: 4px; }
|
|
|
1503
1503
|
.message.manager .bubble {
|
|
1504
1504
|
background: var(--accent);
|
|
1505
1505
|
color: #fff;
|
|
1506
|
+
white-space: pre-wrap;
|
|
1506
1507
|
}
|
|
1507
1508
|
.message.employee .bubble {
|
|
1508
1509
|
background: var(--surface);
|
|
@@ -2752,7 +2753,7 @@ body.hub-shell { display: flex; flex-direction: column; height: 100vh; overflow:
|
|
|
2752
2753
|
.am-av { width: 32px; height: 32px; border-radius: 50%; background: var(--accent); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; flex-shrink: 0; }
|
|
2753
2754
|
.am-name { font-size: 13px; font-weight: 700; }
|
|
2754
2755
|
.am-email { font-size: 11px; color: var(--muted); }
|
|
2755
|
-
.am-item { display: flex; align-items: flex-start; gap: 10px; width: 100%; padding: 9px 16px; cursor: pointer; font-size: 13px; color: var(--text); text-align: left; text-decoration: none; border: 0; background: none; }
|
|
2756
|
+
.am-item { display: flex; align-items: flex-start; gap: 10px; width: 100%; padding: 9px 16px; cursor: pointer; font-size: 13px; color: var(--text); text-align: left; text-decoration: none; border: 0; background: none; }
|
|
2756
2757
|
.am-item:hover { background: var(--bg); }
|
|
2757
2758
|
.am-ico { font-size: 14px; width: 18px; text-align: center; flex-shrink: 0; margin-top: 1px; }
|
|
2758
2759
|
.am-label { font-weight: 600; }
|
|
@@ -2768,75 +2769,75 @@ body.hub-shell { display: flex; flex-direction: column; height: 100vh; overflow:
|
|
|
2768
2769
|
.am-theme[aria-checked="true"] .am-switch-knob { transform: translateX(16px); }
|
|
2769
2770
|
|
|
2770
2771
|
/* Hub areas */
|
|
2771
|
-
.hub-area { display: none; flex: 1; overflow: hidden; }
|
|
2772
|
-
.hub-area.on { display: flex; flex-direction: column; }
|
|
2773
|
-
.hub-area-page { max-width: 860px; margin: 0 auto; padding: 28px 24px 48px; overflow-y: auto; flex: 1; /* authoritative rule — see also Round-2 override below which is now removed */ }
|
|
2774
|
-
|
|
2775
|
-
.connected-shell {
|
|
2776
|
-
display: flex;
|
|
2777
|
-
flex: 1;
|
|
2778
|
-
min-height: 0;
|
|
2779
|
-
flex-direction: column;
|
|
2780
|
-
background: var(--bg);
|
|
2781
|
-
}
|
|
2782
|
-
.connected-toolbar {
|
|
2783
|
-
display: flex;
|
|
2784
|
-
align-items: center;
|
|
2785
|
-
gap: 14px;
|
|
2786
|
-
min-height: 52px;
|
|
2787
|
-
padding: 8px 16px;
|
|
2788
|
-
background: var(--surface);
|
|
2789
|
-
border-bottom: 1px solid var(--line);
|
|
2790
|
-
flex-shrink: 0;
|
|
2791
|
-
}
|
|
2792
|
-
.connected-title-block {
|
|
2793
|
-
min-width: 0;
|
|
2794
|
-
display: flex;
|
|
2795
|
-
flex-direction: column;
|
|
2796
|
-
gap: 1px;
|
|
2797
|
-
}
|
|
2798
|
-
.connected-kicker {
|
|
2799
|
-
font-size: 10px;
|
|
2800
|
-
font-weight: 700;
|
|
2801
|
-
letter-spacing: .04em;
|
|
2802
|
-
text-transform: uppercase;
|
|
2803
|
-
color: var(--muted);
|
|
2804
|
-
}
|
|
2805
|
-
.connected-title {
|
|
2806
|
-
font-size: 14px;
|
|
2807
|
-
font-weight: 700;
|
|
2808
|
-
color: var(--text);
|
|
2809
|
-
}
|
|
2810
|
-
.connected-origin {
|
|
2811
|
-
min-width: 0;
|
|
2812
|
-
flex: 1;
|
|
2813
|
-
color: var(--muted);
|
|
2814
|
-
font-size: 12px;
|
|
2815
|
-
overflow: hidden;
|
|
2816
|
-
text-overflow: ellipsis;
|
|
2817
|
-
white-space: nowrap;
|
|
2818
|
-
}
|
|
2819
|
-
.connected-close {
|
|
2820
|
-
border: 1px solid var(--line);
|
|
2821
|
-
background: var(--surface);
|
|
2822
|
-
color: var(--text);
|
|
2823
|
-
border-radius: 8px;
|
|
2824
|
-
padding: 6px 12px;
|
|
2825
|
-
font-size: 13px;
|
|
2826
|
-
font-weight: 600;
|
|
2827
|
-
flex-shrink: 0;
|
|
2828
|
-
}
|
|
2829
|
-
.connected-close:hover { background: var(--bg); }
|
|
2830
|
-
.connected-frame {
|
|
2831
|
-
flex: 1;
|
|
2832
|
-
min-height: 0;
|
|
2833
|
-
width: 100%;
|
|
2834
|
-
border: 0;
|
|
2835
|
-
background: var(--surface);
|
|
2836
|
-
}
|
|
2837
|
-
|
|
2838
|
-
/* #521 R6: Company/Manager use the same rail + main shell as the project workspace. */
|
|
2839
|
-
.area-shell { display: flex; flex: 1; min-height: 0; }
|
|
2772
|
+
.hub-area { display: none; flex: 1; overflow: hidden; }
|
|
2773
|
+
.hub-area.on { display: flex; flex-direction: column; }
|
|
2774
|
+
.hub-area-page { max-width: 860px; margin: 0 auto; padding: 28px 24px 48px; overflow-y: auto; flex: 1; /* authoritative rule — see also Round-2 override below which is now removed */ }
|
|
2775
|
+
|
|
2776
|
+
.connected-shell {
|
|
2777
|
+
display: flex;
|
|
2778
|
+
flex: 1;
|
|
2779
|
+
min-height: 0;
|
|
2780
|
+
flex-direction: column;
|
|
2781
|
+
background: var(--bg);
|
|
2782
|
+
}
|
|
2783
|
+
.connected-toolbar {
|
|
2784
|
+
display: flex;
|
|
2785
|
+
align-items: center;
|
|
2786
|
+
gap: 14px;
|
|
2787
|
+
min-height: 52px;
|
|
2788
|
+
padding: 8px 16px;
|
|
2789
|
+
background: var(--surface);
|
|
2790
|
+
border-bottom: 1px solid var(--line);
|
|
2791
|
+
flex-shrink: 0;
|
|
2792
|
+
}
|
|
2793
|
+
.connected-title-block {
|
|
2794
|
+
min-width: 0;
|
|
2795
|
+
display: flex;
|
|
2796
|
+
flex-direction: column;
|
|
2797
|
+
gap: 1px;
|
|
2798
|
+
}
|
|
2799
|
+
.connected-kicker {
|
|
2800
|
+
font-size: 10px;
|
|
2801
|
+
font-weight: 700;
|
|
2802
|
+
letter-spacing: .04em;
|
|
2803
|
+
text-transform: uppercase;
|
|
2804
|
+
color: var(--muted);
|
|
2805
|
+
}
|
|
2806
|
+
.connected-title {
|
|
2807
|
+
font-size: 14px;
|
|
2808
|
+
font-weight: 700;
|
|
2809
|
+
color: var(--text);
|
|
2810
|
+
}
|
|
2811
|
+
.connected-origin {
|
|
2812
|
+
min-width: 0;
|
|
2813
|
+
flex: 1;
|
|
2814
|
+
color: var(--muted);
|
|
2815
|
+
font-size: 12px;
|
|
2816
|
+
overflow: hidden;
|
|
2817
|
+
text-overflow: ellipsis;
|
|
2818
|
+
white-space: nowrap;
|
|
2819
|
+
}
|
|
2820
|
+
.connected-close {
|
|
2821
|
+
border: 1px solid var(--line);
|
|
2822
|
+
background: var(--surface);
|
|
2823
|
+
color: var(--text);
|
|
2824
|
+
border-radius: 8px;
|
|
2825
|
+
padding: 6px 12px;
|
|
2826
|
+
font-size: 13px;
|
|
2827
|
+
font-weight: 600;
|
|
2828
|
+
flex-shrink: 0;
|
|
2829
|
+
}
|
|
2830
|
+
.connected-close:hover { background: var(--bg); }
|
|
2831
|
+
.connected-frame {
|
|
2832
|
+
flex: 1;
|
|
2833
|
+
min-height: 0;
|
|
2834
|
+
width: 100%;
|
|
2835
|
+
border: 0;
|
|
2836
|
+
background: var(--surface);
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
/* #521 R6: Company/Manager use the same rail + main shell as the project workspace. */
|
|
2840
|
+
.area-shell { display: flex; flex: 1; min-height: 0; }
|
|
2840
2841
|
.area-rail { width: 244px; flex-shrink: 0; background: var(--surface); border-right: 1px solid var(--line); overflow-y: auto; padding: 6px 0; }
|
|
2841
2842
|
.area-main { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
|
|
2842
2843
|
.area-rail-head { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); padding: 14px 16px 6px; }
|