Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ RUST_LOG=buzz_relay=debug,buzz_datastore=info,buzz_db=debug,buzz_auth=debug,buzz
# app launch while keeping the current identity and relay data.
# VITE_BUZZ_FORCE_FRESH_ONBOARDING=true

# Protected internal builds only: selects the module graph that contains the
# default-off Bestie experiment. Official OSS builds must leave this unset.
# VITE_BUZZ_BESTIE=1

# ── Subscription & filtering ─────────────────────────────────────────────────
# Subscribe mode: "mentions" (default), "all", or "config" (rule-based).
# BUZZ_ACP_SUBSCRIBE=mentions
Expand Down
2 changes: 1 addition & 1 deletion desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build": "tsc && node ./scripts/build-protected-feature-artifacts.mjs",
"build:e2e": "tsc && vite build --mode e2e",
"typecheck": "tsc --noEmit",
"check:file-sizes": "node ./scripts/check-file-sizes.mjs",
Expand Down
108 changes: 108 additions & 0 deletions desktop/scripts/build-protected-feature-artifacts.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { spawnSync } from "node:child_process";
import {
mkdtempSync,
readFileSync,
readdirSync,
rmSync,
statSync,
} from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";

const desktopRoot = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
"..",
);
const selectedInternalVariant = process.env.VITE_BUZZ_BESTIE === "1";
const scratchRoot = mkdtempSync(
path.join(tmpdir(), "buzz-protected-feature-artifacts-"),
);
const selectedOutput = path.join(desktopRoot, "dist");
const alternateOutput = path.join(scratchRoot, "alternate");
const viteBinary = process.platform === "win32" ? "vite.cmd" : "vite";

function buildVariant({ internal, output }) {
const env = { ...process.env };
if (internal) {
env.VITE_BUZZ_BESTIE = "1";
} else {
delete env.VITE_BUZZ_BESTIE;
}

const result = spawnSync(
viteBinary,
["build", "--outDir", output, "--emptyOutDir"],
{
cwd: desktopRoot,
env,
stdio: "inherit",
},
);
if (result.error) throw result.error;
if (result.status !== 0) {
throw new Error(
`${internal ? "internal" : "OSS"} desktop build failed with status ${result.status}`,
);
}
}

function emittedText(root) {
const chunks = [];
const visit = (candidate) => {
const stat = statSync(candidate);
if (stat.isDirectory()) {
for (const child of readdirSync(candidate)) {
visit(path.join(candidate, child));
}
return;
}
if (/\.(?:css|html|js|json)$/u.test(candidate)) {
chunks.push(readFileSync(candidate, "utf8"));
}
};
visit(root);
return chunks.join("\n");
}

function assertArtifactContract({ ossOutput, internalOutput }) {
const ossText = emittedText(ossOutput);
const internalText = emittedText(internalOutput);
const protectedContent = /\bbestie\b|chief of staff|builtin:bestie/iu;
const internalManifestMarker =
"Try a personal agent that is always close at hand";

if (protectedContent.test(ossText)) {
throw new Error(
"Official OSS desktop artifact contains protected Bestie/Chief content",
);
}
if (!internalText.includes(internalManifestMarker)) {
throw new Error(
"Protected internal desktop artifact is missing the Bestie manifest",
);
}
}

try {
// Build the unselected variant outside dist first, then leave the requested
// variant in dist for Vite/Tauri's ordinary packaging contract.
buildVariant({
internal: !selectedInternalVariant,
output: alternateOutput,
});
buildVariant({
internal: selectedInternalVariant,
output: selectedOutput,
});

assertArtifactContract({
ossOutput: selectedInternalVariant ? alternateOutput : selectedOutput,
internalOutput: selectedInternalVariant ? selectedOutput : alternateOutput,
});
console.log(
`Protected feature artifact matrix passed; dist contains the ${selectedInternalVariant ? "internal" : "OSS"} variant.`,
);
} finally {
rmSync(scratchRoot, { recursive: true, force: true });
}
9 changes: 6 additions & 3 deletions desktop/src/features/settings/ui/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import {
canManageCommunityMembers,
shouldWarnMissingMembershipSnapshot,
} from "@/shared/api/relayMembers";
import { getFeature } from "@/shared/features/manifest";
import {
getFeature,
resolveEnabled,
useFeatureSnapshot,
} from "@/shared/features/useFeatureEnabled";
} from "@/shared/features";
import { topChromeBackdrop } from "@/shared/layout/chromeLayout";
import { cn } from "@/shared/lib/cn";
import {
Expand Down Expand Up @@ -137,7 +137,10 @@ export function SettingsView({
// stable and renders unconditionally (fail-open).
if (s.featureGate) {
const feature = getFeature(s.featureGate);
if (feature && !resolveEnabled(s.featureGate, featureState)) {
if (
feature &&
!resolveEnabled(s.featureGate, featureState, feature.defaultEnabled)
) {
return false;
}
}
Expand Down
11 changes: 11 additions & 0 deletions desktop/src/protectedFeatures/internal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { FeatureDefinition } from "@/shared/features/types";

/** Definitions available only in the protected internal application build. */
export const protectedFeatureDefinitions: FeatureDefinition[] = [
{
id: "bestie",
name: "Bestie",
description: "Try a personal agent that is always close at hand",
platforms: ["desktop"],
},
];
19 changes: 19 additions & 0 deletions desktop/src/protectedFeatures/protectedFeatures.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";

import { protectedFeatureDefinitions as internalDefinitions } from "./internal.ts";
import { protectedFeatureDefinitions as publicDefinitions } from "./public.ts";

describe("protected feature build variants", () => {
it("keeps protected definitions out of the OSS module", () => {
assert.deepEqual(publicDefinitions, []);
});

it("adds Bestie only through the internal module", () => {
assert.deepEqual(
internalDefinitions.map((feature) => feature.id),
["bestie"],
);
assert.equal(internalDefinitions[0]?.defaultEnabled, undefined);
});
});
7 changes: 7 additions & 0 deletions desktop/src/protectedFeatures/public.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { FeatureDefinition } from "@/shared/features/types";

/**
* Protected feature definitions compiled into the official OSS application.
* Keep this module free of protected product names, metadata, and imports.
*/
export const protectedFeatureDefinitions: FeatureDefinition[] = [];
6 changes: 5 additions & 1 deletion desktop/src/shared/features/manifest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import manifestJson from "@features-manifest";
import { protectedFeatureDefinitions } from "@protected-features";
import { z } from "zod";
import type { FeatureDefinition, FeaturesManifest } from "./types";

Expand All @@ -25,7 +26,10 @@ const FeaturesManifestSchema = z.object({
const EMPTY_MANIFEST: FeaturesManifest = { version: 1, features: [] };

function loadManifest(): FeaturesManifest {
const result = FeaturesManifestSchema.safeParse(manifestJson);
const result = FeaturesManifestSchema.safeParse({
...manifestJson,
features: [...manifestJson.features, ...protectedFeatureDefinitions],
});
if (!result.success) {
console.warn(
"[FeatureFlags] preview-features.json failed schema validation; falling back to empty manifest.",
Expand Down
4 changes: 2 additions & 2 deletions desktop/src/shared/features/useFeatureEnabled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ export function useFeatureEnabled(featureId: string): boolean {
return resolveEnabled(featureId, overrides, feature.defaultEnabled);
}

export { resolveEnabled } from "./resolveEnabled";

/**
* Hook to toggle a feature override. Returns [enabled, toggle].
*/
Expand Down Expand Up @@ -157,5 +159,3 @@ export function usePreviewFeatureWarning(featureId: string): void {
};
}, [feature, enabled]);
}

export { resolveEnabled } from "./resolveEnabled";
6 changes: 6 additions & 0 deletions desktop/test-loader-hooks.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ export function resolve(specifier, context, nextResolve) {
const resolved = path.join(repoRoot, "preview-features.json");
return nextResolve(toFileSpecifier(resolved), context);
}
if (specifier === "@protected-features") {
const variant =
process.env.VITE_BUZZ_BESTIE === "1" ? "internal.ts" : "public.ts";
const resolved = path.join(srcRoot, "protectedFeatures", variant);
return nextResolve(toFileSpecifier(resolved), context);
}
if (specifier === "@model-capabilities-manifest") {
const resolved = path.join(repoRoot, "scripts", "model-capabilities.json");
return nextResolve(toFileSpecifier(resolved), context);
Expand Down
1 change: 1 addition & 0 deletions desktop/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"paths": {
"@/*": ["./src/*"],
"@features-manifest": ["../preview-features.json"],
"@protected-features": ["./src/protectedFeatures/public.ts"],
"@model-capabilities-manifest": ["../scripts/model-capabilities.json"]
},

Expand Down
107 changes: 61 additions & 46 deletions desktop/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,71 @@
import path from "node:path";
import { defineConfig } from "vite";
import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
import { tanstackRouter } from "@tanstack/router-plugin/vite";

const host = process.env.TAURI_DEV_HOST;

// https://vite.dev/config/
export default defineConfig(async () => ({
plugins: [
tanstackRouter({
target: "react",
routesDirectory: "./src/app/routes",
generatedRouteTree: "./src/app/routeTree.gen.ts",
virtualRouteConfig: "./src/app/routes.ts",
quoteStyle: "double",
semicolons: true,
routeTreeFileHeader: [
"// biome-ignore-all lint: generated by TanStack Router",
],
}),
react(),
],
resolve: {
alias: {
"@": "/src",
"@features-manifest": path.resolve(__dirname, "../preview-features.json"),
"@model-capabilities-manifest": path.resolve(
__dirname,
"../scripts/model-capabilities.json",
),
export default defineConfig(async ({ mode }) => {
const modeEnv = loadEnv(mode, __dirname, "");
const protectedFeaturesEnabled =
(process.env.VITE_BUZZ_BESTIE ?? modeEnv.VITE_BUZZ_BESTIE) === "1";

return {
plugins: [
tanstackRouter({
target: "react",
routesDirectory: "./src/app/routes",
generatedRouteTree: "./src/app/routeTree.gen.ts",
virtualRouteConfig: "./src/app/routes.ts",
quoteStyle: "double",
semicolons: true,
routeTreeFileHeader: [
"// biome-ignore-all lint: generated by TanStack Router",
],
}),
react(),
],
resolve: {
alias: {
"@": "/src",
"@features-manifest": path.resolve(
__dirname,
"../preview-features.json",
),
"@protected-features": path.resolve(
__dirname,
protectedFeaturesEnabled
? "./src/protectedFeatures/internal.ts"
: "./src/protectedFeatures/public.ts",
),
"@model-capabilities-manifest": path.resolve(
__dirname,
"../scripts/model-capabilities.json",
),
},
},
},

// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
//
// 1. prevent Vite from obscuring rust errors
clearScreen: false,
// 2. tauri expects a fixed port, fail if that port is not available
server: {
port: parseInt(process.env.VITE_PORT || "1420", 10),
strictPort: true,
host: host || false,
hmr: host
? {
protocol: "ws",
host,
port: parseInt(process.env.VITE_HMR_PORT || "1421", 10),
}
: undefined,
watch: {
// 3. tell Vite to ignore watching `src-tauri`
ignored: ["**/src-tauri/**"],
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
//
// 1. prevent Vite from obscuring rust errors
clearScreen: false,
// 2. tauri expects a fixed port, fail if that port is not available
server: {
port: parseInt(process.env.VITE_PORT || "1420", 10),
strictPort: true,
host: host || false,
hmr: host
? {
protocol: "ws",
host,
port: parseInt(process.env.VITE_HMR_PORT || "1421", 10),
}
: undefined,
watch: {
// 3. tell Vite to ignore watching `src-tauri`
ignored: ["**/src-tauri/**"],
},
},
},
}));
};
});
Loading