Files
roboco/panel/src/components/settings/feature-flags-card.tsx
T
5612375cba Feat/v0.13.0 (#270)
* feat(release): add release-manager feature flag (default off)

* feat(release): change classification + semver-bump derivation

* feat(release): readiness audit (changelog/version-ref/docs/migration/gate)

* feat(release): release-manager engine proposes a gated release

* feat(release): fail-closed release executor (bump, gate, publish)

* feat(release): CEO approve/reject release-proposal surface

* docs(release): document the gated release manager

* feat(memory): add org-memory feature flags (default off)

* feat(memory): add playbooks table + status enum + migration

* feat(memory): playbook service with auditor curation transitions

* feat(memory): playbooks RAG index plugin

* feat(memory): index a playbook into RAG on approval

* feat(memory): distill a high-signal lesson at task completion

* feat(memory): keep private journal reflections out of the shared RAG corpus

* feat(memory): draft_playbook verb + auditor curation verbs

* fix(ci): resolve mypy tests/ errors blocking the gate (UUID casts, annotations)

* feat(memory): auto-inject similar lessons/playbooks into the briefing

* feat(memory): auditor playbook review queue (api + panel)

* docs(memory): document the org-memory loop + playbook verbs

* fix(provisioning): idempotent pitch provisioning (reuse product/project by slug on re-approval)

* fix(memory): add chunks_playbooks to the chunk schema + isolate release route tests

- Migration 030's CHUNK_TABLES was missing chunks_playbooks, breaking the
  IndexType<->migration parity guard once the PLAYBOOKS index landed. The
  upgrade is ALTER ... IF EXISTS so adding it is safe on any DB shape.
- The release-route fixture's approve/reject paths call db.commit() (real
  behavior), so a held proposal outlived the per-test rollback and leaked
  into engine tests that read the global list_open_release_proposals().
  Tear down source=release_manager rows after each test.
- Make the gather_snapshot real-repo smoke version-agnostic (semver match)
  so it stops pinning the literal repo version.

* chore(release): 0.13.0

* ++

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
2026-06-26 01:43:08 +02:00

118 lines
5.1 KiB
TypeScript

"use client";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { settingsApi } from "@/lib/api";
import type { FeatureFlag } from "@/lib/api/settings";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Separator } from "@/components/ui/separator";
import { Flag } from "lucide-react";
import { toast } from "sonner";
// One-line blurb per flag so the operator knows what each master switch gates.
const FLAG_DESCRIPTIONS: Record<string, string> = {
external_pr_enabled: "Discover and review inbound external/fork pull requests.",
internal_pr_enabled: "Run the read-only safety reviewer on internal branch PRs.",
research_enabled: "Let the Board and PMs run web research.",
strategy_engine_enabled: "Generate and maintain company strategy artifacts.",
self_heal_enabled: "Watch RoboCo's own CI and notify you when it regresses.",
self_heal_originate_enabled:
"Also open a PENDING fix task for a detected regression (needs Self-healing on; the task waits for your approval).",
provisioning_enabled: "Auto-provision projects from approved pitches.",
toolchain_match_enabled:
"Provision each agent workspace with the target project's Python (not RoboCo's) and block delivery gates when its test suite can't be executed.",
conventions_enabled:
"Enforce a per-project architectural standard (.roboco/conventions.yml): inject the map, attach baseline constraints, and block i_am_done / pr_pass on misplaced definitions or lint suppressions.",
rag_auto_update_enabled: "Keep the knowledge base index refreshed automatically.",
transcript_prune_enabled: "Run the background sweep that prunes old transcripts.",
gateway_health_enabled:
"Recover an agent whose MCP gateway has broken (it can run no tools) while its container stays up — kill + respawn it instead of shielding it from the reaper forever.",
ci_watch_enabled:
"Watch every opted-in project's CI and open a fix task when its default branch goes red (per-project opt-in; never auto-merges).",
dep_update_enabled:
"Periodically probe opted-in projects for dependency updates and open an update task when a lockfile would change (per-project opt-in; never auto-merges).",
release_manager_enabled:
"Run the deterministic release-readiness sweep and propose a release for you to approve or reject — it never publishes without your approval, and the executor is fail-closed on a red gate.",
org_memory_enabled:
"Close the learn→reuse loop: distill a lesson at task completion, index journal reflections, and auto-inject similar past lessons + approved playbooks into an agent's briefing on claim.",
};
export function FeatureFlagsCard() {
const queryClient = useQueryClient();
const { data, isLoading } = useQuery({
queryKey: ["feature-flags"],
queryFn: settingsApi.getFeatureFlags,
});
const toggleMutation = useMutation({
mutationFn: ({ key, enabled }: { key: string; enabled: boolean }) =>
settingsApi.setFeatureFlag(key, enabled),
onSuccess: (_data, { enabled }) => {
queryClient.invalidateQueries({ queryKey: ["feature-flags"] });
toast.success(
`Feature ${enabled ? "enabled" : "disabled"} — takes effect on next restart`,
);
},
onError: (error) => {
toast.error(
`Failed to update: ${error instanceof Error ? error.message : "Unknown error"}`,
);
},
});
const flags: FeatureFlag[] = data?.flags ?? [];
const note = data?.note ?? "Changes take effect on the next backend restart.";
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Flag className="h-5 w-5" />
Feature Flags
</CardTitle>
<CardDescription>
Master switches for optional subsystems. Unset flags fall back to the
environment default. {note}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{isLoading && (
<p className="text-sm text-muted-foreground">Loading feature flags</p>
)}
{!isLoading && flags.length === 0 && (
<p className="text-sm text-muted-foreground">No feature flags available.</p>
)}
{flags.map((flag, i) => (
<div key={flag.key}>
{i > 0 && <Separator className="mb-4" />}
<div className="flex items-center justify-between gap-4">
<div className="min-w-0">
<Label htmlFor={`flag-${flag.key}`}>{flag.label}</Label>
<p className="text-sm text-muted-foreground">
{FLAG_DESCRIPTIONS[flag.key] ?? ""}
</p>
</div>
<Switch
id={`flag-${flag.key}`}
checked={flag.enabled}
disabled={toggleMutation.isPending}
onCheckedChange={(checked) =>
toggleMutation.mutate({ key: flag.key, enabled: checked })
}
/>
</div>
</div>
))}
</CardContent>
</Card>
);
}