feat(console): CHV executor option in dispatch/env selectors + experimental note in detail/list

- DispatchSection: add cloud-hypervisor option (labelled experimental,
  default off); default stays docker; hint states a KVM-host CHV worker
  must claim the task (otherwise it stays queued; attachments are
  rejected by the backend at creation) and that the frontend only passes
  the executor value through, never bypassing the backend allowlist.
- EnvSection: same option, because the backend env-create allowlist
  (local/docker/cloud-hypervisor) does allow it; note states env
  allowlist excludes cloud.
- TaskListSection: add cloud-hypervisor to the executor filter so CHV
  tasks are findable; list rows already showed executor.
- TaskDetailPanel: pure display note for cloud-hypervisor tasks
  (experimental, default off, no VM controls); detail already showed
  executor from the task API.
- README: document the CHV console entry, experimental marking, and
  verbatim backend-error display.
- Rebuilt cloud/console.html from src (tsc + vite single-file build);
  no backend change: executor is already returned by _task_to_dict.
This commit is contained in:
host committed 2026-11-08 09:41:42 +00:00
1 parent 7320d693ba
commit 3cf1562176
6 files changed
+26 -16

No files matched your search

@@ -106,12 +106,13 @@ export default function DispatchSection() {
</select>
) : (
<select aria-label="执行器" value={executor} onChange={(e) => setExecutor(e.target.value)}>
<option value="docker">docker(默认,多用户下隔离最强)</option><option value="local">local</option><option value="cloud">cloud</option>
<option value="docker">docker(默认,多用户下隔离最强)</option><option value="local">local</option><option value="cloud">cloud</option><option value="cloud-hypervisor">cloud-hypervisor(实验,默认关闭)</option>
</select>
)}
<label><input type="checkbox" checked={goalMode} onChange={(e) => setGoalMode(e.target.checked)} /> goal 模式</label>
<label><input type="checkbox" checked={planMode} onChange={(e) => setPlanMode(e.target.checked)} /> 计划模式(只读)</label>
</Bar>
{executor === "cloud-hypervisor" ? <div className="muted">实验执行器,默认关闭:需后端已开启 CHV worker 且宿主有 KVM,否则任务无人认领、一直排队;带附件时建任务即被后端拒绝(错误文案原样显示在下方)。前端只传 executor 值,不绕过后端白名单;默认仍是 docker。</div> : null}
<textarea className="prompt stacked" placeholder="任务 prompt(必填)" value={prompt} onChange={(e) => setPrompt(e.target.value)} />
{isSide ? null : (<>
<textarea className="coord stacked" placeholder="协调员子任务 JSON(可选),留空为普通任务" value={coordParts} onChange={(e) => setCoordParts(e.target.value)} />
@@ -31,10 +31,11 @@ export default function EnvSection() {
<input type="text" placeholder="新环境名" value={name} onChange={(e) => setName(e.target.value)} />
<input type="text" placeholder="分支(可选,空=默认分支)" value={branch} onChange={(e) => setBranch(e.target.value)} />
<select aria-label="新环境执行器" value={executor} onChange={(e) => setExecutor(e.target.value)}>
<option value="">执行器:不限</option><option value="local">local</option><option value="docker">docker</option>
<option value="">执行器:不限</option><option value="local">local</option><option value="docker">docker</option><option value="cloud-hypervisor">cloud-hypervisor(实验,默认关闭)</option>
</select>
<button onClick={async () => { await ctx.createEnv(name, branch, executor); setName(""); setBranch(""); }}>新建环境</button>
</Bar>
{executor === "cloud-hypervisor" ? <div className="muted">实验执行器,默认关闭:环境创建白名单同样允许 cloud-hypervisor(后端环境接口 allowlist:local / docker / cloud-hypervisor,不含 cloud);仍需后端有 KVM 宿主上的 CHV worker 才会认领,否则任务排队。前端只传值,不绕过后端白名单。</div> : null}
<span className="muted">{ctx.envMsg}</span>
</Card>
);
@@ -126,7 +126,7 @@ export default function TaskDetailPanel() {
<div><dt>任务</dt><dd>{t.id}</dd></div>
<div><dt>turn</dt><dd>{shortId(t.turn_id)}</dd></div>
<div><dt>root turn</dt><dd>{shortId(t.root_turn_id)}</dd></div>
<div><dt>executor</dt><dd>{t.executor || ""}</dd></div>
<div><dt>executor</dt><dd>{t.executor || ""}{t.executor === "cloud-hypervisor" ? "(实验执行器,默认关闭;仅展示,无 VM 控制按钮)" : ""}</dd></div>
<div><dt>主机</dt><dd>{t.worktree_host || ""}</dd></div>
<div><dt>模型</dt><dd>{t.model || "默认"}</dd></div>
<div><dt>强度</dt><dd>{t.effort || "默认"}</dd></div>
@@ -45,7 +45,7 @@ export default function TaskListSection({ onNew }: { onNew: () => void }) {
{["queued", "running", "completed", "failed", "interrupted", "merged", "merge-failed"].map((s) => <option key={s} value={s}>{s}</option>)}
</select>
<select aria-label="执行器筛选" value={ctx.executorFilter} onChange={(e) => ctx.setExecutorFilter(e.target.value)}>
<option value="">执行器:全部</option><option value="local">local</option><option value="docker">docker</option><option value="cloud">cloud</option>
<option value="">执行器:全部</option><option value="local">local</option><option value="docker">docker</option><option value="cloud">cloud</option><option value="cloud-hypervisor">cloud-hypervisor(实验)</option>
</select>
<select aria-label="类型筛选" value={ctx.kindFilter} onChange={(e) => ctx.setKindFilter(e.target.value)}>
<option value="">类型:全部</option><option value="main">主聊</option><option value="side">旁聊</option>