ci / test (push) Successful in 25m2s
Tasks gain kind='main'|'side' (NULL=main). A side thread hangs under a parent task, defaults to the local executor, never builds a worktree, branch or container unless explicitly asked, and writes its conclusion back to the parent via task_followups on terminal transition. - server: kind validation + side-thread gates (parent required, no coordinator_parts/upload_id/environment_id, parent must not be a coordinator), local executor default, GET /tasks kind filter, /worktree 404 and /fork /merge /pr 409 for side threads. - worker: side-default shunt in _run_attempt (no-worktree scratch cwd, in-memory plan=1 when a repo is present, seed/checkpoint skipped), _writeback_side_thread, marker task-create kind field + inheritance, side threads excluded from marker spawn counts. - host/sandbox: --no-worktree plumbing for dispatch/goal/popen (Local + Docker), scratch cwd work/side-threads/<task-id>. - console (classic only): kind filter + [旁聊] rows + '开旁聊' entry, side-thread dispatch mode, side-thread list in detail, merge/PR/fork hidden for side threads; build exit 0, console.html sha df39db5a. - tests: new test_sidethread.py (29 tests) + kind backfill tests in test_p2_migrations.py; full cloud suite 3900 tests OK (skipped=13). Design: verification/codex-muse-role-alignment-design-20261108.md Summary: verification/codex-muse-role-align-p1p2-20261108.md Local only: not pushed, not merged, not deployed.
40 lines
2.3 KiB
TypeScript
40 lines
2.3 KiB
TypeScript
import { useState } from "react";
|
||
import { useConsole } from "../../context";
|
||
import { Bar } from "../../components/ui";
|
||
import { TRACE_TERMINAL } from "../../api/types";
|
||
|
||
// Detail actions + fork only. Continue/followup input lives in
|
||
// ContinuePanel (shared ChatInput), not in a second form here.
|
||
export default function DetailActions() {
|
||
const ctx = useConsole();
|
||
const t = ctx.currentTask;
|
||
const [forkMsg, setForkMsg] = useState("");
|
||
if (!t) return null;
|
||
const terminal = TRACE_TERMINAL.includes(t.status);
|
||
const retryOk = ["failed", "interrupted", "merge-failed"].includes(t.status);
|
||
// 旁聊(Muse 角色对齐 P2):无 worktree/分支,后端 /fork /merge
|
||
// /pr 一律 409,这里只留重试/取消/查成本,不渲染那三处入口。
|
||
const isSide = t.kind === "side";
|
||
return (
|
||
<>
|
||
<Bar>
|
||
<button disabled={!retryOk} title={retryOk ? "" : "仅 failed/interrupted/merge-failed 可重试"} onClick={() => ctx.doAction("retry")}>重试</button>
|
||
<button className="danger" disabled={terminal} title={terminal ? "终态任务不可取消" : ""} onClick={() => ctx.doAction("cancel")}>取消</button>
|
||
{isSide ? null : (<>
|
||
<button disabled={t.status !== "completed"} title={t.status !== "completed" ? "仅 completed 可合并" : ""} onClick={() => ctx.doAction("merge")}>合并(completed → merged)</button>
|
||
<button disabled={t.status !== "merged"} title={t.status !== "merged" ? "仅 merged 可提 PR" : ""} onClick={() => ctx.doAction("pr")}>提 PR(须 merged)</button>
|
||
</>)}
|
||
{terminal && !isSide ? <button onClick={() => ctx.setForkVisible(true)}>分支</button> : null}
|
||
<button onClick={ctx.taskCost}>查成本</button>
|
||
<span className="muted">{ctx.actionMsg} {ctx.actionReasons}</span>
|
||
</Bar>
|
||
{ctx.forkVisible && !isSide ? (
|
||
<div>
|
||
<input type="text" placeholder="分支首轮消息(必填,带历史的新会话)" style={{ width: "100%", boxSizing: "border-box", margin: "6px 0" }} value={forkMsg} onChange={(e) => setForkMsg(e.target.value)} />
|
||
<Bar><button className="seal" onClick={async () => { await ctx.forkTask(forkMsg); setForkMsg(""); }}>创建分支任务</button><span className="muted">{ctx.forkMsg}</span></Bar>
|
||
</div>
|
||
) : null}
|
||
</>
|
||
);
|
||
}
|