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.
This commit is contained in:
1 parent
f6df76ee8f
commit
7983567491
24 files changed
+1244
-100
No files matched your search
@@ -23,6 +23,10 @@ export function useConsoleStore(): ConsoleCtx {
|
||||
const onTasksReload = useCallback(() => { void taskList.refreshTasks(undefined, projectIdRef.current); }, [taskList]);
|
||||
const detail = useTaskDetail(getRole, onTasksReload);
|
||||
const [refreshStamp, setRefreshStamp] = useState("--:--:--");
|
||||
// 旁聊(Muse 角色对齐 P2):派发器的旁聊父任务。列表“开旁聊”
|
||||
// 点选主聊后填这里,派发器读到即进旁聊模式;提交成功或点
|
||||
// “取消旁聊模式”后清空。放胶水层,不进 App.tsx。
|
||||
const [sideParentId, setSideParentId] = useState<string | null>(null);
|
||||
|
||||
const selectProject = useCallback((id: string) => {
|
||||
catalog.setProjectId(id);
|
||||
@@ -126,6 +130,7 @@ export function useConsoleStore(): ConsoleCtx {
|
||||
refreshTasks: () => void taskList.refreshTasks(undefined, projectIdRef.current),
|
||||
statusFilter: taskList.statusFilter, setStatusFilter: taskList.setStatusFilter,
|
||||
executorFilter: taskList.executorFilter, setExecutorFilter: taskList.setExecutorFilter,
|
||||
kindFilter: taskList.kindFilter, setKindFilter: taskList.setKindFilter,
|
||||
searchText: taskList.searchText, setSearchText: taskList.setSearchText,
|
||||
filteredTasks: taskList.filteredTasks,
|
||||
taskPage: taskList.page, taskTotalPages: taskList.totalPages, taskTotal: taskList.taskTotal,
|
||||
@@ -135,6 +140,7 @@ export function useConsoleStore(): ConsoleCtx {
|
||||
doAction: detail.doAction, taskCost: detail.taskCost, costBox: detail.costBox, costSummary: detail.costSummary,
|
||||
forkVisible: detail.forkVisible, setForkVisible: detail.setForkVisible, forkTask: detail.forkTask, forkMsg: detail.forkMsg,
|
||||
coordChildren: detail.coordChildren, coordText: "", agents: detail.agents, agentsNote: detail.agentsNote,
|
||||
sideThreads: detail.sideThreads, sideParentId, setSideParentId,
|
||||
reviews: detail.reviews, reviewMsg: detail.reviewMsg, reviewError: detail.reviewError,
|
||||
refreshReviews: detail.refreshReviews, postReview: detail.postReview, submitFindings: detail.submitFindings,
|
||||
traceEntries: detail.traceEntries, traceNote: detail.traceNote, traceDone: detail.traceDone,
|
||||
|
||||
@@ -19,6 +19,9 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
|
||||
const [forkVisible, setForkVisible] = useState(false);
|
||||
const [forkMsg, setForkMsg] = useState("");
|
||||
const [coordChildren, setCoordChildren] = useState<Task[]>([]);
|
||||
// 旁聊(Muse 角色对齐 P2):当前任务挂着的旁聊线程(kind=side
|
||||
// 且 parent_task_id=本任务),与协调员子任务同款拉取方式。
|
||||
const [sideThreads, setSideThreads] = useState<Task[]>([]);
|
||||
const [agents, setAgents] = useState<Agent[]>([]);
|
||||
const [agentsNote, setAgentsNote] = useState("");
|
||||
const [reviews, setReviews] = useState<Review[]>([]);
|
||||
@@ -71,6 +74,13 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
|
||||
} catch { setCoordChildren([]); }
|
||||
}, []);
|
||||
|
||||
const loadSideThreads = useCallback(async (task: Task) => {
|
||||
try {
|
||||
const d = await api<{ tasks: Task[] }>(paths.tasksByParent(task.project_id || "", task.id, "side"));
|
||||
setSideThreads(d.tasks || []);
|
||||
} catch { setSideThreads([]); }
|
||||
}, []);
|
||||
|
||||
const loadCost = useCallback(async (taskId: string) => {
|
||||
try {
|
||||
const c = await api<TaskCost>(paths.taskCost(taskId));
|
||||
@@ -248,6 +258,7 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
|
||||
void startTrace(taskId, t.status);
|
||||
void loadAgents(taskId);
|
||||
void loadCoordChildren(t);
|
||||
void loadSideThreads(t);
|
||||
void loadChatHistory(taskId);
|
||||
void loadFollowups(taskId);
|
||||
void loadPeerMessages(taskId);
|
||||
@@ -259,7 +270,7 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
|
||||
setCurrentTask(null);
|
||||
setDetailBody("加载失败:" + e.message);
|
||||
}
|
||||
}, [stopTrace, startTrace, loadAgents, loadCoordChildren, loadChatHistory, loadFollowups, loadPeerMessages, loadThread, loadThreads, loadReviews, loadApprovals, loadCost, getRole]);
|
||||
}, [stopTrace, startTrace, loadAgents, loadCoordChildren, loadSideThreads, loadChatHistory, loadFollowups, loadPeerMessages, loadThread, loadThreads, loadReviews, loadApprovals, loadCost, getRole]);
|
||||
|
||||
const refreshDetailLists = useCallback(async () => {
|
||||
const t = currentTaskRef.current;
|
||||
@@ -277,12 +288,13 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
|
||||
void loadReviews(t.id);
|
||||
void loadApprovals(t.id);
|
||||
void loadCoordChildren(t);
|
||||
void loadSideThreads(t);
|
||||
void loadAgents(t.id);
|
||||
void loadPeerMessages(t.id);
|
||||
void loadThread(t.id);
|
||||
void loadThreads(t.id);
|
||||
void loadCost(t.id);
|
||||
}, [loadFollowups, loadChatHistory, loadReviews, loadApprovals, loadCoordChildren, loadAgents, loadPeerMessages, loadThread, loadThreads, loadCost]);
|
||||
}, [loadFollowups, loadChatHistory, loadReviews, loadApprovals, loadCoordChildren, loadSideThreads, loadAgents, loadPeerMessages, loadThread, loadThreads, loadCost]);
|
||||
|
||||
const doAction = useCallback(async (kind: string) => {
|
||||
const t = currentTaskRef.current;
|
||||
@@ -423,7 +435,7 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
|
||||
|
||||
return {
|
||||
currentTask, detailBody, actionMsg, actionReasons, costBox, costSummary, forkVisible, setForkVisible, forkMsg,
|
||||
coordChildren, agents, agentsNote, reviews, reviewMsg, reviewError, traceEntries, traceNote, traceDone,
|
||||
coordChildren, sideThreads, agents, agentsNote, reviews, reviewMsg, reviewError, traceEntries, traceNote, traceDone,
|
||||
traceKind, setTraceKind, traceMsg, followups, approvals, approvalMsg, peerMessages, peerMsg,
|
||||
threadText, threadGateNote, threadMsg, threadList, chatMsg, chatHistory,
|
||||
showDetail, refreshDetailLists, doAction, taskCost, forkTask, postReview, submitFindings,
|
||||
|
||||
@@ -21,13 +21,14 @@ export function useTaskList() {
|
||||
const [taskError, setTaskError] = useState("");
|
||||
const [statusFilter, setStatusFilter] = useState("");
|
||||
const [executorFilter, setExecutorFilter] = useState("");
|
||||
const [kindFilter, setKindFilter] = useState("");
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [page, setPageState] = useState(0);
|
||||
const [taskTotal, setTaskTotal] = useState(0);
|
||||
const [taskStatusCounts, setTaskStatusCounts] = useState<Record<string, number>>({});
|
||||
const pageRef = useRef(0);
|
||||
const filterRef = useRef({ status: "", executor: "", q: "" });
|
||||
filterRef.current = { status: statusFilter, executor: executorFilter, q: searchText };
|
||||
const filterRef = useRef({ status: "", executor: "", q: "", kind: "" });
|
||||
filterRef.current = { status: statusFilter, executor: executorFilter, q: searchText, kind: kindFilter };
|
||||
const lastProjectRef = useRef("");
|
||||
|
||||
const refreshTasks = useCallback(async (pid?: string, currentPid?: string, pageOverride?: number) => {
|
||||
@@ -38,7 +39,7 @@ export function useTaskList() {
|
||||
const f = filterRef.current;
|
||||
try {
|
||||
const d = await api<{ tasks: Task[]; total?: number; status_counts?: Record<string, number> }>(
|
||||
paths.tasksPage(id, TASK_PAGE_SIZE, p * TASK_PAGE_SIZE, f.status, f.executor, f.q));
|
||||
paths.tasksPage(id, TASK_PAGE_SIZE, p * TASK_PAGE_SIZE, f.status, f.executor, f.q, f.kind));
|
||||
setTasks(d.tasks || []);
|
||||
setTaskTotal(d.total ?? (d.tasks || []).length);
|
||||
setTaskStatusCounts(d.status_counts || {});
|
||||
@@ -66,13 +67,14 @@ export function useTaskList() {
|
||||
const t = setTimeout(() => { void refreshTasks(lastProjectRef.current, undefined, 0); }, 300);
|
||||
return () => clearTimeout(t);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [statusFilter, executorFilter, searchText]);
|
||||
}, [statusFilter, executorFilter, searchText, kindFilter]);
|
||||
|
||||
// Filter/project changes reset to page 1: the old page may not
|
||||
// exist under the new filter, and staying on it would show an
|
||||
// empty page with a misleading "共 N 个" count.
|
||||
const setStatusFilterPage = useCallback((v: string) => { setStatusFilter(v); setPage(0); }, [setPage]);
|
||||
const setExecutorFilterPage = useCallback((v: string) => { setExecutorFilter(v); setPage(0); }, [setPage]);
|
||||
const setKindFilterPage = useCallback((v: string) => { setKindFilter(v); setPage(0); }, [setPage]);
|
||||
const setSearchTextPage = useCallback((v: string) => { setSearchText(v); setPage(0); }, [setPage]);
|
||||
const refreshTasksFirstPage = useCallback(async (pid?: string) => {
|
||||
setPage(0);
|
||||
@@ -99,6 +101,7 @@ export function useTaskList() {
|
||||
return {
|
||||
tasks, taskError, statusFilter, setStatusFilter: setStatusFilterPage,
|
||||
executorFilter, setExecutorFilter: setExecutorFilterPage,
|
||||
kindFilter, setKindFilter: setKindFilterPage,
|
||||
searchText, setSearchText: setSearchTextPage,
|
||||
filteredTasks, taskCountText, refreshTasks, refreshTasksFirstPage,
|
||||
page, totalPages, taskTotal, taskStatusCounts, goToPage, dispatch,
|
||||
|
||||
Reference in new issue
Block a user