chore: checkpoint existing workspace changes

This commit is contained in:
777genius 2026-05-23 13:48:35 +03:00
parent 74fea9c176
commit 64fdfd2422
8 changed files with 193 additions and 50 deletions

View file

@ -84,6 +84,14 @@ const faqIcons = [
<style scoped> <style scoped>
.faq-section { .faq-section {
position: relative; position: relative;
--faq-title-gradient: linear-gradient(135deg, #e0e6ff 0%, #ffd700 100%);
--faq-subtitle-color: #8892b0;
--faq-panel-bg: rgba(10, 10, 15, 0.8);
--faq-panel-border: rgba(0, 240, 255, 0.08);
--faq-panel-question: #e0e6ff;
--faq-panel-answer: #8892b0;
--faq-panel-hover-shadow: 0 8px 32px rgba(0, 240, 255, 0.06);
--faq-panel-hover-border: rgba(0, 240, 255, 0.2);
} }
.faq-section__header { .faq-section__header {
@ -100,7 +108,7 @@ const faqIcons = [
letter-spacing: -0.03em; letter-spacing: -0.03em;
line-height: 1.15; line-height: 1.15;
margin-bottom: 16px; margin-bottom: 16px;
background: linear-gradient(135deg, #e0e6ff 0%, #ffd700 100%); background: var(--faq-title-gradient);
-webkit-background-clip: text; -webkit-background-clip: text;
background-clip: text; background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
@ -108,7 +116,7 @@ const faqIcons = [
.faq-section__subtitle { .faq-section__subtitle {
font-size: 1.1rem; font-size: 1.1rem;
color: #8892b0; color: var(--faq-subtitle-color);
line-height: 1.6; line-height: 1.6;
margin: 0; margin: 0;
} }
@ -134,9 +142,10 @@ const faqIcons = [
.faq-section__panel { .faq-section__panel {
border-radius: 16px !important; border-radius: 16px !important;
background: rgba(10, 10, 15, 0.8) !important; background: var(--faq-panel-bg) !important;
border: 1px solid rgba(0, 240, 255, 0.08) !important; border: 1px solid var(--faq-panel-border) !important;
backdrop-filter: blur(12px); backdrop-filter: blur(12px);
color: var(--faq-panel-question) !important;
transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease; transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
overflow: hidden; overflow: hidden;
animation: faqFadeIn 0.5s ease both; animation: faqFadeIn 0.5s ease both;
@ -145,8 +154,8 @@ const faqIcons = [
.faq-section__panel:hover { .faq-section__panel:hover {
transform: translateY(-2px); transform: translateY(-2px);
border-color: rgba(0, 240, 255, 0.2) !important; border-color: var(--faq-panel-hover-border) !important;
box-shadow: 0 8px 32px rgba(0, 240, 255, 0.06); box-shadow: var(--faq-panel-hover-shadow);
} }
.faq-section__panel::after { .faq-section__panel::after {
@ -160,6 +169,8 @@ const faqIcons = [
.faq-section__panel-title { .faq-section__panel-title {
padding: 20px 24px !important; padding: 20px 24px !important;
min-height: unset !important; min-height: unset !important;
background: transparent !important;
color: var(--faq-panel-question) !important;
} }
:deep(.faq-section__panel-title .v-expansion-panel-title__overlay) { :deep(.faq-section__panel-title .v-expansion-panel-title__overlay) {
@ -198,17 +209,18 @@ const faqIcons = [
font-size: 1rem; font-size: 1rem;
font-weight: 600; font-weight: 600;
line-height: 1.4; line-height: 1.4;
color: #e0e6ff; color: var(--faq-panel-question);
} }
:deep(.faq-section__panel-text .v-expansion-panel-text__wrapper) { :deep(.faq-section__panel-text .v-expansion-panel-text__wrapper) {
padding: 0 24px 20px 82px !important; padding: 0 24px 20px 82px !important;
background: transparent !important;
} }
.faq-section__answer { .faq-section__answer {
font-size: 0.95rem; font-size: 0.95rem;
line-height: 1.7; line-height: 1.7;
color: #8892b0; color: var(--faq-panel-answer);
} }
.faq-section__answer :deep(a) { .faq-section__answer :deep(a) {
@ -300,32 +312,28 @@ const faqIcons = [
} }
/* Light Theme */ /* Light Theme */
.v-theme--light .faq-section__title { :global(.v-theme--light) .faq-section,
background: linear-gradient(135deg, #1e293b 0%, #d97706 100%); :global(html.light) .faq-section {
-webkit-background-clip: text; --faq-title-gradient: linear-gradient(135deg, #1e293b 0%, #d97706 100%);
background-clip: text; --faq-subtitle-color: #475569;
-webkit-text-fill-color: transparent; --faq-panel-bg: rgba(255, 255, 255, 0.75);
--faq-panel-border: rgba(0, 0, 0, 0.06);
--faq-panel-question: #1e293b;
--faq-panel-answer: #475569;
--faq-panel-hover-shadow: 0 8px 32px rgba(0, 180, 200, 0.08);
--faq-panel-hover-border: rgba(0, 240, 255, 0.2);
} }
.v-theme--light .faq-section__subtitle { :global(.v-theme--dark) .faq-section,
color: #475569; :global(html.dark) .faq-section {
} --faq-title-gradient: linear-gradient(135deg, #e0e6ff 0%, #ffd700 100%);
--faq-subtitle-color: #8892b0;
.v-theme--light .faq-section__panel { --faq-panel-bg: rgba(10, 10, 15, 0.8);
background: rgba(255, 255, 255, 0.75) !important; --faq-panel-border: rgba(0, 240, 255, 0.08);
border-color: rgba(0, 0, 0, 0.06) !important; --faq-panel-question: #e0e6ff;
} --faq-panel-answer: #8892b0;
--faq-panel-hover-shadow: 0 8px 32px rgba(0, 240, 255, 0.06);
.v-theme--light .faq-section__panel:hover { --faq-panel-hover-border: rgba(0, 240, 255, 0.2);
box-shadow: 0 8px 32px rgba(0, 180, 200, 0.08);
}
.v-theme--light .faq-section__panel-question {
color: #1e293b;
}
.v-theme--light .faq-section__answer {
color: #475569;
} }
@media (max-width: 960px) { @media (max-width: 960px) {

View file

@ -42,6 +42,10 @@ export const useBrowserTheme = () => {
const applyDocumentTheme = (name: ThemeName) => { const applyDocumentTheme = (name: ThemeName) => {
if (!import.meta.client) return; if (!import.meta.client) return;
document.documentElement.classList.toggle("dark", name === "dark");
document.documentElement.classList.toggle("light", name === "light");
document.documentElement.style.colorScheme = name;
document.querySelectorAll(".v-application").forEach((app) => { document.querySelectorAll(".v-application").forEach((app) => {
app.classList.toggle("v-theme--dark", name === "dark"); app.classList.toggle("v-theme--dark", name === "dark");
app.classList.toggle("v-theme--light", name === "light"); app.classList.toggle("v-theme--light", name === "light");

View file

@ -311,6 +311,7 @@
"target": [ "target": [
"nsis" "nsis"
], ],
"executableName": "AgentTeamsAI",
"icon": "resources/icons/win/icon.ico" "icon": "resources/icons/win/icon.ico"
}, },
"linux": { "linux": {
@ -348,7 +349,8 @@
"artifactName": "Agent.Teams.AI.Setup.${version}.${ext}", "artifactName": "Agent.Teams.AI.Setup.${version}.${ext}",
"oneClick": false, "oneClick": false,
"perMachine": false, "perMachine": false,
"allowToChangeInstallationDirectory": true "allowToChangeInstallationDirectory": true,
"shortcutName": "Agent Teams AI"
}, },
"publish": [ "publish": [
{ {

View file

@ -92,9 +92,7 @@ function shouldKeepNodePtyPrebuild(entryName, platform, archLabel) {
if (platform === 'darwin' && archLabel === 'universal') { if (platform === 'darwin' && archLabel === 'universal') {
return ( return (
entryName === 'darwin-universal' || entryName === 'darwin-universal' || entryName === 'darwin-arm64' || entryName === 'darwin-x64'
entryName === 'darwin-arm64' ||
entryName === 'darwin-x64'
); );
} }
@ -154,11 +152,85 @@ async function pruneNodePtyArtifacts(appOutDir, platform, archLabel) {
removedPaths.push(absolutePath); removedPaths.push(absolutePath);
} }
} }
const hasTargetPrebuild = await hasNodePtyTargetPrebuild(nodePtyRoot, platform, archLabel);
if (!hasTargetPrebuild) {
continue;
}
for (const buildName of ['Release', 'Debug']) {
const buildDir = path.join(nodePtyRoot, 'build', buildName);
if (!(await directoryExists(buildDir))) {
continue;
}
if (await containsIncompatibleNativeBinary(buildDir, platform, archLabel)) {
await fs.promises.rm(buildDir, { recursive: true, force: true });
removedPaths.push(buildDir);
}
}
} }
return removedPaths; return removedPaths;
} }
async function directoryExists(dirPath) {
try {
const stat = await fs.promises.stat(dirPath);
return stat.isDirectory();
} catch {
return false;
}
}
async function hasNodePtyTargetPrebuild(nodePtyRoot, platform, archLabel) {
const prebuildsDir = path.join(nodePtyRoot, 'prebuilds');
let entries;
try {
entries = await fs.promises.readdir(prebuildsDir, { withFileTypes: true });
} catch {
return false;
}
for (const entry of entries) {
if (!entry.isDirectory() || !shouldKeepNodePtyPrebuild(entry.name, platform, archLabel)) {
continue;
}
const ptyNativePath = path.join(prebuildsDir, entry.name, 'pty.node');
if (await fileExists(ptyNativePath)) {
return true;
}
}
return false;
}
async function fileExists(filePath) {
try {
const stat = await fs.promises.stat(filePath);
return stat.isFile();
} catch {
return false;
}
}
async function containsIncompatibleNativeBinary(rootDir, targetPlatform, targetArch) {
const files = await walkFiles(rootDir);
for (const filePath of files) {
const metadata = await detectBinaryMetadata(filePath);
if (!metadata) {
continue;
}
if (!isBinaryCompatible(metadata.format, metadata.archs, targetPlatform, targetArch)) {
return true;
}
}
return false;
}
function findNodeModulesSequence(segments, sequence) { function findNodeModulesSequence(segments, sequence) {
for (let index = 0; index <= segments.length - sequence.length; index += 1) { for (let index = 0; index <= segments.length - sequence.length; index += 1) {
let matches = true; let matches = true;
@ -332,12 +404,7 @@ function parseElf(buffer) {
if (buffer.length < 20) { if (buffer.length < 20) {
return null; return null;
} }
if ( if (buffer[0] !== 0x7f || buffer[1] !== 0x45 || buffer[2] !== 0x4c || buffer[3] !== 0x46) {
buffer[0] !== 0x7f ||
buffer[1] !== 0x45 ||
buffer[2] !== 0x4c ||
buffer[3] !== 0x46
) {
return null; return null;
} }
@ -454,11 +521,7 @@ async function afterPack(context) {
const removedPaths = [ const removedPaths = [
...(await pruneNodePtyArtifacts(context.appOutDir, targetPlatform, targetArch)), ...(await pruneNodePtyArtifacts(context.appOutDir, targetPlatform, targetArch)),
...(await pruneKnownIncompatibleNativeArtifacts( ...(await pruneKnownIncompatibleNativeArtifacts(context.appOutDir, targetPlatform, targetArch)),
context.appOutDir,
targetPlatform,
targetArch
)),
]; ];
const mismatches = await validateNativeBinaries(context.appOutDir, targetPlatform, targetArch); const mismatches = await validateNativeBinaries(context.appOutDir, targetPlatform, targetArch);

View file

@ -30,6 +30,7 @@ import type { ChildProcessWithoutNullStreams } from 'child_process';
const PROBE_COMMAND_TIMEOUT_MS = 90_000; const PROBE_COMMAND_TIMEOUT_MS = 90_000;
const COMMAND_TIMEOUT_MS = PROBE_COMMAND_TIMEOUT_MS; const COMMAND_TIMEOUT_MS = PROBE_COMMAND_TIMEOUT_MS;
const COMMAND_MAX_BUFFER_BYTES = 8 * 1024 * 1024;
const COMMAND_ERROR_DETAIL_LIMIT = 1_600; const COMMAND_ERROR_DETAIL_LIMIT = 1_600;
const COMMAND_OUTPUT_PREVIEW_LIMIT = 1_200; const COMMAND_OUTPUT_PREVIEW_LIMIT = 1_200;
const ESCAPE_CHARACTER = String.fromCharCode(27); const ESCAPE_CHARACTER = String.fromCharCode(27);
@ -870,8 +871,12 @@ function appendOptionalArg(args: string[], name: string, value: string | null |
function runtimeProviderCommandOptions<T extends { env: NodeJS.ProcessEnv }>( function runtimeProviderCommandOptions<T extends { env: NodeJS.ProcessEnv }>(
options: T, options: T,
projectPath: string | null projectPath: string | null
): T & { cwd?: string } { ): T & { cwd?: string; maxBuffer: number } {
return projectPath ? { ...options, cwd: projectPath } : options; const commandOptions = {
...options,
maxBuffer: COMMAND_MAX_BUFFER_BYTES,
};
return projectPath ? { ...commandOptions, cwd: projectPath } : commandOptions;
} }
async function resolveCliEnv(): Promise<{ async function resolveCliEnv(): Promise<{

View file

@ -13,6 +13,7 @@ import {
SelectValue, SelectValue,
} from '@renderer/components/ui/select'; } from '@renderer/components/ui/select';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs';
import { cn } from '@renderer/lib/utils';
import { import {
compareOpenCodeTeamModelRecommendations, compareOpenCodeTeamModelRecommendations,
getOpenCodeTeamModelRecommendation, getOpenCodeTeamModelRecommendation,
@ -819,7 +820,10 @@ const RuntimeProviderErrorAlert = ({
type="button" type="button"
size="sm" size="sm"
variant="ghost" variant="ghost"
className="h-6 shrink-0 px-2 text-[11px]" className={cn(
'h-6 shrink-0 px-2 text-[11px]',
!copied && 'member-launch-diagnostics-pulse'
)}
title={copied ? 'Diagnostics copied' : 'Copy diagnostics'} title={copied ? 'Diagnostics copied' : 'Copy diagnostics'}
aria-label={copied ? 'Diagnostics copied' : 'Copy diagnostics'} aria-label={copied ? 'Diagnostics copied' : 'Copy diagnostics'}
onClick={(event) => { onClick={(event) => {

View file

@ -134,6 +134,62 @@ describe('electron-builder afterPack', () => {
expect(fs.existsSync(path.join(prebuildsDir, 'darwin-x64'))).toBe(false); expect(fs.existsSync(path.join(prebuildsDir, 'darwin-x64'))).toBe(false);
}); });
it('prunes host node-pty build outputs when a matching target prebuild exists', async () => {
const tempDir = createTempDir();
tempDirs.push(tempDir);
const nodePtyDir = path.join(
tempDir,
'resources',
'app.asar.unpacked',
'node_modules',
'node-pty'
);
const releaseDir = path.join(nodePtyDir, 'build', 'Release');
const prebuildDir = path.join(nodePtyDir, 'prebuilds', 'win32-x64');
writeFile(path.join(tempDir, 'AgentTeamsAI.exe'), createPortableExecutableBuffer('x64'));
writeFile(path.join(releaseDir, 'pty.node'), createMachOBuffer('arm64'));
writeFile(path.join(releaseDir, 'spawn-helper'), createMachOBuffer('arm64'));
writeFile(path.join(prebuildDir, 'pty.node'), createPortableExecutableBuffer('x64'));
writeFile(path.join(prebuildDir, 'conpty.node'), createPortableExecutableBuffer('x64'));
await afterPackModule({
appOutDir: tempDir,
electronPlatformName: 'win32',
arch: 1,
});
expect(fs.existsSync(releaseDir)).toBe(false);
expect(fs.existsSync(path.join(prebuildDir, 'pty.node'))).toBe(true);
});
it('keeps incompatible node-pty build outputs when no target prebuild exists', async () => {
const tempDir = createTempDir();
tempDirs.push(tempDir);
const releaseDir = path.join(
tempDir,
'resources',
'app.asar.unpacked',
'node_modules',
'node-pty',
'build',
'Release'
);
writeFile(path.join(tempDir, 'agent-teams-ai'), createElfBuffer('x64'));
writeFile(path.join(releaseDir, 'pty.node'), createMachOBuffer('arm64'));
await expect(
afterPackModule({
appOutDir: tempDir,
electronPlatformName: 'linux',
arch: 1,
})
).rejects.toThrow('Found incompatible native binaries in linux-x64 bundle after pruning');
expect(fs.existsSync(releaseDir)).toBe(true);
});
it('fails validation when a foreign-arch native binary remains in the bundle', async () => { it('fails validation when a foreign-arch native binary remains in the bundle', async () => {
const tempDir = createTempDir(); const tempDir = createTempDir();
tempDirs.push(tempDir); tempDirs.push(tempDir);

View file

@ -1380,6 +1380,7 @@ describe('AgentTeamsRuntimeProviderManagementCliClient', () => {
], ],
expect.objectContaining({ cwd: '/Users/test/project' }) expect.objectContaining({ cwd: '/Users/test/project' })
); );
expect(execCliMock.mock.calls[0]?.[2]).toMatchObject({ maxBuffer: 8 * 1024 * 1024 });
expect(JSON.stringify(execCliMock.mock.calls[0])).not.toContain('undefined'); expect(JSON.stringify(execCliMock.mock.calls[0])).not.toContain('undefined');
}); });