mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
Feat: transcript retention (#123)
* feat(retention): prune old agent transcripts + panel-tunable setting
Agents write a {session-id}.jsonl per spawn under ~/.claude/projects; nothing
ever deleted them, so the operator's bind-mounted ~/.claude grew without bound.
Add a throttled orchestrator sweep that prunes agent-owned transcripts (the
shared -app dir + per-workspace dirs) older than a retention window — and ONLY
agent-owned dirs, never the operator's own Claude sessions (proven by the
temp-dir selection tests). The window is panel-tunable: a new system_settings
key-value table (migration 027) holds transcript_retention_days, read via
SettingsService with the roboco.config default (14d) as the fallback, exposed
through GET/PUT /api/settings. Panel wiring follows.
* feat(panel): add a panel-tunable transcript retention control
Wire the settings page to the /api/settings backend: a settings API client and
a self-contained Transcript Retention card (React Query) that loads
transcript_retention_days and saves it back, with client-side validation. The
existing settings controls are unchanged.
---------
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { settingsApi } from "@/lib/api";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { HardDrive, Save } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
const RETENTION_KEY = "transcript_retention_days";
|
||||
const DEFAULT_RETENTION = "14";
|
||||
|
||||
export function TranscriptRetentionCard() {
|
||||
const queryClient = useQueryClient();
|
||||
const [days, setDays] = useState<string>(DEFAULT_RETENTION);
|
||||
|
||||
const { data: settings, isLoading } = useQuery({
|
||||
queryKey: ["settings"],
|
||||
queryFn: settingsApi.getAll,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const stored = settings?.[RETENTION_KEY];
|
||||
if (stored !== undefined) {
|
||||
setDays(stored);
|
||||
}
|
||||
}, [settings]);
|
||||
|
||||
const saveMutation = useMutation({
|
||||
mutationFn: (value: string) => settingsApi.update(RETENTION_KEY, value),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["settings"] });
|
||||
toast.success("Transcript retention updated");
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(
|
||||
`Failed to save: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const handleSave = () => {
|
||||
const parsed = Number(days);
|
||||
if (!Number.isInteger(parsed) || parsed < 1) {
|
||||
toast.error("Retention must be a whole number of days (at least 1)");
|
||||
return;
|
||||
}
|
||||
saveMutation.mutate(String(parsed));
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<HardDrive className="h-5 w-5" />
|
||||
Transcript Retention
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
How long agent transcripts are kept before the background sweep prunes
|
||||
them. Only agent-owned transcripts are pruned — your own Claude
|
||||
sessions are never touched.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="transcript-retention-days">Retention window (days)</Label>
|
||||
<Input
|
||||
id="transcript-retention-days"
|
||||
type="number"
|
||||
min={1}
|
||||
value={days}
|
||||
disabled={isLoading}
|
||||
onChange={(e) => setDays(e.target.value)}
|
||||
className="max-w-[160px]"
|
||||
/>
|
||||
</div>
|
||||
<Button onClick={handleSave} disabled={saveMutation.isPending || isLoading}>
|
||||
<Save className="h-4 w-4 mr-2" />
|
||||
{saveMutation.isPending ? "Saving..." : "Save"}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user