用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/vm0-ai/vm0 --skill feature-switch命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
| name | feature-switch |
| description | Feature switch system guide for gating new user-facing features behind feature flags |
This skill documents the feature switch system and provides step-by-step instructions for adding new feature switches. All new user-facing features must be gated behind a feature switch for gradual rollout.
A feature switch is required when adding:
A feature switch is not required for:
File: turbo/packages/core/src/feature-switch-key.ts
Add a new entry to FeatureSwitchKey:
export enum FeatureSwitchKey {
// ... existing keys
MyFeature = "myFeature",
}
File: turbo/packages/core/src/feature-switch.ts
Add an entry to the FEATURE_SWITCHES record:
[FeatureSwitchKey.MyFeature]: {
maintainer: "you@vm0.ai",
enabled: false,
enabledOrgIdHashes: STAFF_ORG_ID_HASHES, // optional: staff-only access
},
Configuration options:
| Field | Type | Description |
|---|---|---|
maintainer | string | Email of the responsible person |
enabled | boolean | true = on for everyone, false = off by default |
enabledUserHashes | string[] | FNV-1a hashes of allowed user IDs |
enabledEmailHashes | string[] | FNV-1a hashes of allowed emails |
enabledOrgIdHashes | string[] | FNV-1a hashes of allowed org IDs |
Common default states:
enabled: false — fully hidden until manually enabled via Lab pageenabled: false + enabledOrgIdHashes: STAFF_ORG_ID_HASHES — staff-only (most common for new features)enabled: true — on for everyone (use when feature is ready for GA)Choose the pattern that matches where your feature is consumed.
import { isFeatureEnabled, FeatureSwitchKey } from "@okouai/core";
// In route handler:
if (!isFeatureEnabled(FeatureSwitchKey.MyFeature, { userId, orgId })) {
return createErrorResponse("FORBIDDEN", "Feature not available");
}
import { FeatureSwitchKey } from "@okouai/core";
import { featureSwitch$ } from "../../signals/external/feature-switch.ts";
// In component:
const features = useLastResolved(featureSwitch$);
const showMyFeature = features?.[FeatureSwitchKey.MyFeature] ?? false;
// Conditional rendering:
{showMyFeature && <MyFeatureComponent />}
In turbo/apps/platform/src/views/zero-page/zero-sidebar.tsx, add a featureGate to the sidebar item:
{
id: "my-feature",
label: "My Feature",
icon: MyIcon,
featureGate: FeatureSwitchKey.MyFeature,
}
In turbo/packages/core/src/contracts/connectors.ts, add featureFlag to the connector config:
myConnector: {
label: "My Connector",
featureFlag: FeatureSwitchKey.MyConnector,
// ...
}
In turbo/apps/api/src/signals/auth/tokens.ts, add to CONDITIONAL_CAPABILITIES:
const CONDITIONAL_CAPABILITIES: ReadonlyMap<ZeroCapability, FeatureSwitchKey> =
new Map([
// ... existing entries
["my-feature:write", FeatureSwitchKey.MyFeature],
]);
| File | Role |
|---|---|
turbo/packages/core/src/feature-switch-key.ts | Enum of all feature switch keys |
turbo/packages/core/src/feature-switch.ts | Registry and evaluation logic |
turbo/apps/platform/src/signals/external/feature-switch.ts | Client-side reactive state with override layers |
turbo/apps/platform/src/views/zero-page/zero-sidebar.tsx | Sidebar nav items with featureGate |
turbo/apps/api/src/signals/services/connector-catalog-artifacts/source.ts | External connector auth-method schema with featureSwitch field |
turbo/apps/api/src/signals/auth/tokens.ts | Token capability gating |
Evaluation has two layers (lowest to highest priority):
userId / email / orgId hashes.user_feature_switches keyed by (orgId, userId). Some switches are
org-scoped and stored under the org sentinel user id (__org__); currently
ChatErrorRecovery and PiLoop are org-scoped. Written via the Lab page
toggles or window._vm0.featureSwitches.myFeature = true (both call
POST /api/zero/feature-switches). Cleared via the Lab page "Reset all"
button (DELETE /api/zero/feature-switches).The same two-layer resolution applies on the server: route handlers that call
isFeatureEnabled(..., { userId, orgId, overrides }) pass overrides loaded via
loadFeatureSwitchOverrides(orgId, userId).
There is no client-only layer. window._vm0.featureSwitches requires auth and persists across refreshes; there is no device-local override.