mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
docs: add evolution config controls (#2852)
* docs: add evolution config controls * docs: address evolution config review
This commit is contained in:
+1
-1
@@ -55,7 +55,7 @@ The current frontend exposes these major pages and flows:
|
||||
- `/agent/tools`
|
||||
- View tool availability and enable or disable tool switches through config-backed APIs.
|
||||
- `/config`
|
||||
- Edit agent defaults, exec controls, cron controls, heartbeat, device monitoring, launcher networking, and launch-at-login settings.
|
||||
- Edit agent defaults, self-evolution, exec controls, cron controls, heartbeat, device monitoring, launcher networking, and launch-at-login settings.
|
||||
- `/logs`
|
||||
- View the in-memory gateway log buffer and clear it.
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
AgentDefaultsSection,
|
||||
CronSection,
|
||||
DevicesSection,
|
||||
EvolutionSection,
|
||||
ExecSection,
|
||||
LauncherSection,
|
||||
MCPSection,
|
||||
@@ -33,6 +34,7 @@ import {
|
||||
type MCPServerForm,
|
||||
buildFormFromConfig,
|
||||
parseCIDRText,
|
||||
parseFloatField,
|
||||
parseIntField,
|
||||
parseJSONObjectField,
|
||||
parseMultilineList,
|
||||
@@ -281,6 +283,16 @@ export function ConfigPage() {
|
||||
"Cron exec timeout",
|
||||
{ min: 0 },
|
||||
)
|
||||
const evolutionMinTaskCount = parseIntField(
|
||||
form.evolutionMinTaskCount,
|
||||
"Evolution minimum task count",
|
||||
{ min: 1 },
|
||||
)
|
||||
const evolutionMinSuccessRatio = parseFloatField(
|
||||
form.evolutionMinSuccessRatio,
|
||||
"Evolution minimum success ratio",
|
||||
{ min: 0.01, max: 1 },
|
||||
)
|
||||
const mcpDiscoveryValidationEnabled =
|
||||
form.mcpEnabled && form.mcpDiscoveryEnabled
|
||||
const mcpDiscoveryPatch: Record<string, unknown> = {
|
||||
@@ -500,6 +512,20 @@ export function ConfigPage() {
|
||||
session: {
|
||||
dm_scope: dmScope,
|
||||
},
|
||||
evolution: {
|
||||
enabled: form.evolutionEnabled,
|
||||
mode: form.evolutionMode,
|
||||
state_dir:
|
||||
form.evolutionStateDir.trim() === ""
|
||||
? null
|
||||
: form.evolutionStateDir.trim(),
|
||||
min_task_count: evolutionMinTaskCount,
|
||||
min_success_ratio: evolutionMinSuccessRatio,
|
||||
cold_path_trigger: form.evolutionColdPathTrigger,
|
||||
cold_path_times: parseMultilineList(
|
||||
form.evolutionColdPathTimesText,
|
||||
),
|
||||
},
|
||||
tools: {
|
||||
cron: {
|
||||
allow_command: form.allowCommand,
|
||||
@@ -661,6 +687,8 @@ export function ConfigPage() {
|
||||
|
||||
<RuntimeSection form={form} onFieldChange={updateField} />
|
||||
|
||||
<EvolutionSection form={form} onFieldChange={updateField} />
|
||||
|
||||
<MCPSection
|
||||
form={form}
|
||||
onFieldChange={updateField}
|
||||
|
||||
@@ -236,6 +236,152 @@ interface MCPSectionProps {
|
||||
) => void
|
||||
}
|
||||
|
||||
interface EvolutionSectionProps {
|
||||
form: CoreConfigForm
|
||||
onFieldChange: UpdateCoreField
|
||||
}
|
||||
|
||||
export function EvolutionSection({
|
||||
form,
|
||||
onFieldChange,
|
||||
}: EvolutionSectionProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<ConfigSectionCard
|
||||
title={t("pages.config.sections.evolution")}
|
||||
description={t("pages.config.evolution_section_hint")}
|
||||
>
|
||||
<SwitchCardField
|
||||
label={t("pages.config.evolution_enabled")}
|
||||
hint={t("pages.config.evolution_enabled_hint")}
|
||||
layout="setting-row"
|
||||
checked={form.evolutionEnabled}
|
||||
onCheckedChange={(checked) =>
|
||||
onFieldChange("evolutionEnabled", checked)
|
||||
}
|
||||
/>
|
||||
|
||||
<Field
|
||||
label={t("pages.config.evolution_mode")}
|
||||
hint={t("pages.config.evolution_mode_hint")}
|
||||
layout="setting-row"
|
||||
>
|
||||
<Select
|
||||
value={form.evolutionMode}
|
||||
onValueChange={(value) => onFieldChange("evolutionMode", value)}
|
||||
>
|
||||
<SelectTrigger aria-label={t("pages.config.evolution_mode")}>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="observe">
|
||||
{t("pages.config.evolution_mode_observe")}
|
||||
</SelectItem>
|
||||
<SelectItem value="draft">
|
||||
{t("pages.config.evolution_mode_draft")}
|
||||
</SelectItem>
|
||||
<SelectItem value="apply">
|
||||
{t("pages.config.evolution_mode_apply")}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={t("pages.config.evolution_state_dir")}
|
||||
hint={t("pages.config.evolution_state_dir_hint")}
|
||||
layout="setting-row"
|
||||
>
|
||||
<Input
|
||||
value={form.evolutionStateDir}
|
||||
onChange={(e) => onFieldChange("evolutionStateDir", e.target.value)}
|
||||
placeholder="e.g. /var/lib/picoclaw/evolution"
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={t("pages.config.evolution_min_task_count")}
|
||||
hint={t("pages.config.evolution_min_task_count_hint")}
|
||||
layout="setting-row"
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={form.evolutionMinTaskCount}
|
||||
onChange={(e) =>
|
||||
onFieldChange("evolutionMinTaskCount", e.target.value)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={t("pages.config.evolution_min_success_ratio")}
|
||||
hint={t("pages.config.evolution_min_success_ratio_hint")}
|
||||
layout="setting-row"
|
||||
>
|
||||
<Input
|
||||
type="number"
|
||||
min={0.01}
|
||||
max={1}
|
||||
step="0.05"
|
||||
value={form.evolutionMinSuccessRatio}
|
||||
onChange={(e) =>
|
||||
onFieldChange("evolutionMinSuccessRatio", e.target.value)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
label={t("pages.config.evolution_cold_path_trigger")}
|
||||
hint={t("pages.config.evolution_cold_path_trigger_hint")}
|
||||
layout="setting-row"
|
||||
>
|
||||
<Select
|
||||
value={form.evolutionColdPathTrigger}
|
||||
onValueChange={(value) =>
|
||||
onFieldChange("evolutionColdPathTrigger", value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger
|
||||
aria-label={t("pages.config.evolution_cold_path_trigger")}
|
||||
>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="after_turn">
|
||||
{t("pages.config.evolution_cold_path_after_turn")}
|
||||
</SelectItem>
|
||||
<SelectItem value="scheduled">
|
||||
{t("pages.config.evolution_cold_path_scheduled")}
|
||||
</SelectItem>
|
||||
<SelectItem value="manual">
|
||||
{t("pages.config.evolution_cold_path_manual")}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
|
||||
{form.evolutionColdPathTrigger === "scheduled" && (
|
||||
<Field
|
||||
label={t("pages.config.evolution_cold_path_times")}
|
||||
hint={t("pages.config.evolution_cold_path_times_hint")}
|
||||
layout="setting-row"
|
||||
>
|
||||
<Textarea
|
||||
value={form.evolutionColdPathTimesText}
|
||||
placeholder={"03:00\n15:30"}
|
||||
className="min-h-[88px] font-mono text-xs"
|
||||
onChange={(e) =>
|
||||
onFieldChange("evolutionColdPathTimesText", e.target.value)
|
||||
}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
</ConfigSectionCard>
|
||||
)
|
||||
}
|
||||
|
||||
export function MCPSection({
|
||||
form,
|
||||
onFieldChange,
|
||||
|
||||
@@ -32,6 +32,13 @@ export interface CoreConfigForm {
|
||||
mcpDiscoveryUseBM25: boolean
|
||||
mcpDiscoveryUseRegex: boolean
|
||||
mcpServers: MCPServerForm[]
|
||||
evolutionEnabled: boolean
|
||||
evolutionMode: string
|
||||
evolutionStateDir: string
|
||||
evolutionMinTaskCount: string
|
||||
evolutionMinSuccessRatio: string
|
||||
evolutionColdPathTrigger: string
|
||||
evolutionColdPathTimesText: string
|
||||
}
|
||||
|
||||
export type MCPServerType = "http" | "sse" | "stdio"
|
||||
@@ -121,6 +128,13 @@ export const EMPTY_FORM: CoreConfigForm = {
|
||||
mcpDiscoveryUseBM25: true,
|
||||
mcpDiscoveryUseRegex: false,
|
||||
mcpServers: [],
|
||||
evolutionEnabled: false,
|
||||
evolutionMode: "observe",
|
||||
evolutionStateDir: "",
|
||||
evolutionMinTaskCount: "2",
|
||||
evolutionMinSuccessRatio: "0.7",
|
||||
evolutionColdPathTrigger: "after_turn",
|
||||
evolutionColdPathTimesText: "",
|
||||
}
|
||||
|
||||
export const EMPTY_LAUNCHER_FORM: LauncherForm = {
|
||||
@@ -215,6 +229,7 @@ export function buildFormFromConfig(config: unknown): CoreConfigForm {
|
||||
const session = asRecord(root.session)
|
||||
const heartbeat = asRecord(root.heartbeat)
|
||||
const devices = asRecord(root.devices)
|
||||
const evolution = asRecord(root.evolution)
|
||||
const tools = asRecord(root.tools)
|
||||
const mcp = asRecord(tools.mcp)
|
||||
const mcpDiscovery = asRecord(mcp.discovery)
|
||||
@@ -335,6 +350,29 @@ export function buildFormFromConfig(config: unknown): CoreConfigForm {
|
||||
? EMPTY_FORM.mcpDiscoveryUseRegex
|
||||
: asBool(mcpDiscovery.use_regex),
|
||||
mcpServers: mapMCPServers(mcp.servers),
|
||||
evolutionEnabled:
|
||||
evolution.enabled === undefined
|
||||
? EMPTY_FORM.evolutionEnabled
|
||||
: asBool(evolution.enabled),
|
||||
evolutionMode: asString(evolution.mode) || EMPTY_FORM.evolutionMode,
|
||||
evolutionStateDir:
|
||||
asString(evolution.state_dir) || EMPTY_FORM.evolutionStateDir,
|
||||
evolutionMinTaskCount: asNumberString(
|
||||
evolution.min_task_count,
|
||||
EMPTY_FORM.evolutionMinTaskCount,
|
||||
),
|
||||
evolutionMinSuccessRatio: asNumberString(
|
||||
evolution.min_success_ratio,
|
||||
EMPTY_FORM.evolutionMinSuccessRatio,
|
||||
),
|
||||
evolutionColdPathTrigger:
|
||||
asString(evolution.cold_path_trigger) ||
|
||||
EMPTY_FORM.evolutionColdPathTrigger,
|
||||
evolutionColdPathTimesText: Array.isArray(evolution.cold_path_times)
|
||||
? evolution.cold_path_times
|
||||
.filter((value): value is string => typeof value === "string")
|
||||
.join("\n")
|
||||
: EMPTY_FORM.evolutionColdPathTimesText,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -356,6 +394,24 @@ export function parseIntField(
|
||||
return value
|
||||
}
|
||||
|
||||
export function parseFloatField(
|
||||
rawValue: string,
|
||||
label: string,
|
||||
options: { min?: number; max?: number } = {},
|
||||
): number {
|
||||
const value = Number(rawValue)
|
||||
if (!Number.isFinite(value)) {
|
||||
throw new Error(`${label} must be a number.`)
|
||||
}
|
||||
if (options.min !== undefined && value < options.min) {
|
||||
throw new Error(`${label} must be >= ${options.min}.`)
|
||||
}
|
||||
if (options.max !== undefined && value > options.max) {
|
||||
throw new Error(`${label} must be <= ${options.max}.`)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
export function parseCIDRText(raw: string): string[] {
|
||||
if (!raw.trim()) {
|
||||
return []
|
||||
|
||||
@@ -802,6 +802,27 @@
|
||||
"allowed_cidrs": "Allowed Network CIDRs",
|
||||
"allowed_cidrs_hint": "Only clients from these CIDR ranges can access the service. One per line or comma-separated. Leave empty to allow all.",
|
||||
"allowed_cidrs_placeholder": "192.168.1.0/24\n10.0.0.0/8",
|
||||
"evolution_section_hint": "Let the agent learn from completed turns and prepare skill improvements.",
|
||||
"evolution_enabled": "Enable Evolution",
|
||||
"evolution_enabled_hint": "Record learning data for completed turns. Draft and apply modes can also generate skill updates.",
|
||||
"evolution_mode": "Evolution Mode",
|
||||
"evolution_mode_hint": "Observe only records data, Draft prepares candidate skills, Apply can write accepted drafts into workspace skills.",
|
||||
"evolution_mode_observe": "Observe",
|
||||
"evolution_mode_draft": "Draft",
|
||||
"evolution_mode_apply": "Apply",
|
||||
"evolution_state_dir": "State Directory",
|
||||
"evolution_state_dir_hint": "Optional directory for evolution state. Leave empty to use the workspace default.",
|
||||
"evolution_min_task_count": "Minimum Task Count",
|
||||
"evolution_min_task_count_hint": "Minimum related tasks required before a pattern can produce a draft.",
|
||||
"evolution_min_success_ratio": "Minimum Success Ratio",
|
||||
"evolution_min_success_ratio_hint": "Required success ratio for clustered tasks. Use a value greater than 0 and up to 1.",
|
||||
"evolution_cold_path_trigger": "Cold Path Trigger",
|
||||
"evolution_cold_path_trigger_hint": "Choose when draft generation runs for eligible learning records.",
|
||||
"evolution_cold_path_after_turn": "After each turn",
|
||||
"evolution_cold_path_scheduled": "Scheduled",
|
||||
"evolution_cold_path_manual": "Off",
|
||||
"evolution_cold_path_times": "Scheduled Times",
|
||||
"evolution_cold_path_times_hint": "Run times for scheduled cold-path processing. Enter one HH:MM value per line.",
|
||||
"mcp_section_hint": "Configure MCP servers without editing config.json manually.",
|
||||
"mcp_enabled": "Enable MCP",
|
||||
"mcp_enabled_hint": "Turn MCP server integration on or off.",
|
||||
@@ -835,6 +856,7 @@
|
||||
"sections": {
|
||||
"agent": "Agent",
|
||||
"runtime": "Runtime",
|
||||
"evolution": "Evolution",
|
||||
"mcp": "MCP",
|
||||
"exec": "Run Commands",
|
||||
"cron": "Cron Tasks",
|
||||
|
||||
@@ -700,9 +700,31 @@
|
||||
"allowed_cidrs": "CIDRs de Rede Permitidos",
|
||||
"allowed_cidrs_hint": "Apenas clientes destes intervalos CIDR podem acessar o serviço. Um por linha ou separados por vírgula. Deixe vazio para permitir todos.",
|
||||
"allowed_cidrs_placeholder": "192.168.1.0/24\n10.0.0.0/8",
|
||||
"evolution_section_hint": "Permite que o agente aprenda com turnos concluídos e prepare melhorias de skills.",
|
||||
"evolution_enabled": "Habilitar Evolução",
|
||||
"evolution_enabled_hint": "Registrar dados de aprendizado para turnos concluídos. Os modos Draft e Apply também podem gerar atualizações de skills.",
|
||||
"evolution_mode": "Modo de Evolução",
|
||||
"evolution_mode_hint": "Observe apenas registra dados, Draft prepara skills candidatas, Apply pode gravar drafts aceitos nas skills do workspace.",
|
||||
"evolution_mode_observe": "Observe",
|
||||
"evolution_mode_draft": "Draft",
|
||||
"evolution_mode_apply": "Apply",
|
||||
"evolution_state_dir": "Diretório de Estado",
|
||||
"evolution_state_dir_hint": "Diretório opcional para o estado de evolução. Deixe vazio para usar o padrão do workspace.",
|
||||
"evolution_min_task_count": "Contagem Mínima de Tarefas",
|
||||
"evolution_min_task_count_hint": "Número mínimo de tarefas relacionadas antes que um padrão possa produzir um draft.",
|
||||
"evolution_min_success_ratio": "Taxa Mínima de Sucesso",
|
||||
"evolution_min_success_ratio_hint": "Taxa de sucesso exigida para tarefas agrupadas. Use um valor maior que 0 e até 1.",
|
||||
"evolution_cold_path_trigger": "Acionador Cold Path",
|
||||
"evolution_cold_path_trigger_hint": "Escolha quando a geração de drafts roda para registros de aprendizado elegíveis.",
|
||||
"evolution_cold_path_after_turn": "Após cada turno",
|
||||
"evolution_cold_path_scheduled": "Agendado",
|
||||
"evolution_cold_path_manual": "Desligado",
|
||||
"evolution_cold_path_times": "Horários Agendados",
|
||||
"evolution_cold_path_times_hint": "Horários para o processamento cold-path agendado. Insira um valor HH:MM por linha.",
|
||||
"sections": {
|
||||
"agent": "Agente",
|
||||
"runtime": "Runtime",
|
||||
"evolution": "Evolução",
|
||||
"exec": "Execução de Comandos",
|
||||
"cron": "Tarefas Agendadas",
|
||||
"launcher": "Launcher",
|
||||
|
||||
@@ -803,6 +803,27 @@
|
||||
"allowed_cidrs": "允许访问网段",
|
||||
"allowed_cidrs_hint": "仅允许这些 CIDR 网段的客户端访问服务。可按行或逗号分隔;留空表示允许所有来源",
|
||||
"allowed_cidrs_placeholder": "192.168.1.0/24\n10.0.0.0/8",
|
||||
"evolution_section_hint": "让 Agent 从已完成的回合中学习,并准备技能改进。",
|
||||
"evolution_enabled": "启用自进化",
|
||||
"evolution_enabled_hint": "记录已完成回合的学习数据。Draft 和 Apply 模式还可以生成技能更新。",
|
||||
"evolution_mode": "自进化模式",
|
||||
"evolution_mode_hint": "Observe 只记录数据,Draft 生成候选技能,Apply 可将接受的草稿写入工作区技能。",
|
||||
"evolution_mode_observe": "observe",
|
||||
"evolution_mode_draft": "draft",
|
||||
"evolution_mode_apply": "apply",
|
||||
"evolution_state_dir": "状态目录",
|
||||
"evolution_state_dir_hint": "自进化状态的可选目录。留空时使用工作区默认位置。",
|
||||
"evolution_min_task_count": "最小任务数",
|
||||
"evolution_min_task_count_hint": "一个模式能生成草稿前,至少需要多少个相关任务。",
|
||||
"evolution_min_success_ratio": "最小成功率",
|
||||
"evolution_min_success_ratio_hint": "聚类任务所需的成功率。取值需大于 0,且不超过 1。",
|
||||
"evolution_cold_path_trigger": "冷路径触发方式",
|
||||
"evolution_cold_path_trigger_hint": "选择何时为符合条件的学习记录运行草稿生成。",
|
||||
"evolution_cold_path_after_turn": "每轮结束后",
|
||||
"evolution_cold_path_scheduled": "定时运行",
|
||||
"evolution_cold_path_manual": "关闭自动运行",
|
||||
"evolution_cold_path_times": "定时运行时间",
|
||||
"evolution_cold_path_times_hint": "定时冷路径处理的运行时间。每行填写一个 HH:MM。",
|
||||
"mcp_section_hint": "通过可视化界面配置 MCP 服务器,无需手动编辑 config.json",
|
||||
"mcp_enabled": "启用 MCP",
|
||||
"mcp_enabled_hint": "开启或关闭 MCP 服务集成",
|
||||
@@ -836,6 +857,7 @@
|
||||
"sections": {
|
||||
"agent": "智能体",
|
||||
"runtime": "运行时",
|
||||
"evolution": "自进化",
|
||||
"mcp": "MCP",
|
||||
"exec": "运行命令",
|
||||
"cron": "定时任务",
|
||||
|
||||
Reference in New Issue
Block a user