Merge pull request #126 from openqj/feat-chat-history

chat: 持久化继续消息,chat-history 交替历史
This commit is contained in:
openqj authored and GitHub committed 2026-10-29 07:07:34 +08:00
commit a13762cfbe
9 files changed
+524 -40

No files matched your search

+1
View File
@@ -38,6 +38,7 @@ export const paths = {
taskApprovals: (id: string) => "/tasks/" + enc(id) + "/approvals",
taskApprovalDecision: (id: string, aid: string, verb: string) => "/tasks/" + enc(id) + "/approvals/" + enc(aid) + "/" + verb,
taskFollowups: (id: string) => "/tasks/" + enc(id) + "/followups",
taskChatHistory: (id: string) => "/tasks/" + enc(id) + "/chat-history",
taskMessages: (id: string) => "/tasks/" + enc(id) + "/messages",
taskMessagesTo: (target: string) => "/tasks/" + enc(target) + "/messages",
taskThread: (id: string) => "/tasks/" + enc(id) + "/thread",
+7
View File
@@ -17,6 +17,13 @@ export interface Task {
export interface Review { id: string; target_type?: string; branch?: string; sha?: string; instructions?: string; status?: string; created_at?: number; findings?: string }
export interface Approval { id: string; kind?: string; status?: string; summary?: string; decision?: string; decided_by?: string; decided_at?: number }
export interface Followup { mode?: string; status?: string; sender_task_id?: string; body?: string }
// Chat history from GET /tasks/:id/chat-history: server-merges the task
// prompt, persisted continue messages, followups and trace entries in
// timestamp order (see server _task_chat_history). ts_ms is null for
// trace entries that carried no ts; the server order is authoritative,
// the client must not re-sort.
export interface ChatHistoryMessage { id: string; role: "user" | "assistant" | "tool" | "status"; source?: string; label?: string; text: string; ts_ms?: number | null }
export interface ChatHistory { task_id?: string; status?: string; messages: ChatHistoryMessage[]; continue_messages_stored?: number; legacy_continue_count?: number | null; trace_note?: string }
export interface PeerMessage { task_id?: string; mode?: string; status?: string; body?: string }
export interface Automation { id: string; name?: string; status?: string; due?: boolean; interval_seconds?: number; next_run_at?: number }
export interface Member { user_id: string; username?: string; role?: string }
+2 -1
View File
@@ -1,5 +1,5 @@
import { createContext, useContext } from "react";
import type { Automation, Env, Member, Project, Review, Approval, Followup, PeerMessage, Task, ThreadInfo, Triage, Worker, SkillItem, McpServer, Agent, DispatchBody, ReviewBody, TraceEntry } from "./api/types";
import type { Automation, Env, Member, Project, Review, Approval, Followup, PeerMessage, Task, ThreadInfo, Triage, Worker, SkillItem, McpServer, Agent, DispatchBody, ReviewBody, TraceEntry, ChatHistory } from "./api/types";
export interface ConsoleCtx {
keySaved: boolean; keyState: string;
@@ -30,6 +30,7 @@ export interface ConsoleCtx {
reviews: Review[]; reviewMsg: string; reviewError: string; refreshReviews: () => void; postReview: (body: ReviewBody) => Promise<void>; submitFindings: (id: string) => void;
traceEntries: TraceEntry[]; traceNote: string; traceDone: string; traceKind: string; setTraceKind: (v: string) => void; downloadTrace: () => void; traceMsg: string;
sendChatMessage: (text: string) => Promise<void>; chatMsg: string;
chatHistory: ChatHistory | null;
followups: Followup[];
approvals: Approval[]; approvalMsg: string; refreshApprovals: () => void; decideApproval: (id: string, decision: string) => void;
peerMessages: PeerMessage[]; peerMsg: string; postPeer: (mode: string, target: string, text: string) => Promise<void>;
@@ -126,6 +126,7 @@ export function useConsoleStore(): ConsoleCtx {
traceEntries: detail.traceEntries, traceNote: detail.traceNote, traceDone: detail.traceDone,
traceKind: detail.traceKind, setTraceKind: detail.setTraceKind, downloadTrace: detail.downloadTrace, traceMsg: detail.traceMsg,
sendChatMessage: detail.sendChatMessage, chatMsg: detail.chatMsg,
chatHistory: detail.chatHistory,
followups: detail.followups,
approvals: detail.approvals, approvalMsg: detail.approvalMsg, refreshApprovals: detail.refreshApprovals, decideApproval: detail.decideApproval,
peerMessages: detail.peerMessages, peerMsg: detail.peerMsg, postPeer: detail.postPeer,
+18 -5
View File
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
import { api, getKey } from "../api/client";
import { paths } from "../api/paths";
import { TRACE_TERMINAL } from "../api/types";
import type { Agent, Approval, Followup, PeerMessage, Review, Task, ThreadInfo, TraceEntry } from "../api/types";
import type { Agent, Approval, ChatHistory, Followup, PeerMessage, Review, Task, ThreadInfo, TraceEntry } from "../api/types";
// Task detail state: the selected task, its trace SSE/one-shot stream,
// and every structured sub-list. Chat sending lives here too:
@@ -36,6 +36,7 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
const [threadMsg, setThreadMsg] = useState("");
const [threadList, setThreadList] = useState<ThreadInfo[]>([]);
const [chatMsg, setChatMsg] = useState("");
const [chatHistory, setChatHistory] = useState<ChatHistory | null>(null);
const traceAbortRef = useRef<AbortController | null>(null);
const agentTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const currentTaskRef = useRef<Task | null>(null);
@@ -72,6 +73,15 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
catch { setFollowups([]); }
}, []);
// True alternating history: server-merged prompt + persisted
// continues + followups + trace, in timestamp order. On failure
// (old server without /chat-history) keep null and ContinuePanel
// falls back to its previous prompt + trace + followups view.
const loadChatHistory = useCallback(async (taskId: string) => {
try { const d = await api<ChatHistory>(paths.taskChatHistory(taskId)); setChatHistory(d); }
catch { setChatHistory(null); }
}, []);
const loadReviews = useCallback(async (taskId?: string) => {
const id = taskId || currentTaskRef.current?.id;
if (!id) return;
@@ -226,6 +236,7 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
void startTrace(taskId, t.status);
void loadAgents(taskId);
void loadCoordChildren(t);
void loadChatHistory(taskId);
void loadFollowups(taskId);
void loadPeerMessages(taskId);
void loadThread(taskId);
@@ -236,7 +247,7 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
setCurrentTask(null);
setDetailBody("加载失败:" + e.message);
}
}, [stopTrace, startTrace, loadAgents, loadCoordChildren, loadFollowups, loadPeerMessages, loadThread, loadThreads, loadReviews, loadApprovals, getRole]);
}, [stopTrace, startTrace, loadAgents, loadCoordChildren, loadChatHistory, loadFollowups, loadPeerMessages, loadThread, loadThreads, loadReviews, loadApprovals, getRole]);
const refreshDetailLists = useCallback(async () => {
const t = currentTaskRef.current;
@@ -250,6 +261,7 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
setDetailBody(body || "(暂无 result/error 输出)");
} catch { /* light header refresh must not clear the detail */ }
void loadFollowups(t.id);
void loadChatHistory(t.id);
void loadReviews(t.id);
void loadApprovals(t.id);
void loadCoordChildren(t);
@@ -257,7 +269,7 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
void loadPeerMessages(t.id);
void loadThread(t.id);
void loadThreads(t.id);
}, [loadFollowups, loadReviews, loadApprovals, loadCoordChildren, loadAgents, loadPeerMessages, loadThread, loadThreads]);
}, [loadFollowups, loadChatHistory, loadReviews, loadApprovals, loadCoordChildren, loadAgents, loadPeerMessages, loadThread, loadThreads]);
const doAction = useCallback(async (kind: string) => {
const t = currentTaskRef.current;
@@ -370,11 +382,12 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
await api(paths.taskFollowups(t.id), "POST", { body: v, mode: "direct" });
setChatMsg("已直接发送");
await loadFollowups(t.id);
await loadChatHistory(t.id);
} else {
setChatMsg("merged/merge-failed 任务不可继续,也不可发后续消息");
}
} catch (e: any) { setChatMsg("发送失败:" + e.message); }
}, [showDetail, onTasksReload, loadFollowups]);
}, [showDetail, onTasksReload, loadFollowups, loadChatHistory]);
const downloadTrace = useCallback(async () => {
const t = currentTaskRef.current;
@@ -399,7 +412,7 @@ export function useTaskDetail(getRole: (pid?: string) => string | null, onTasksR
currentTask, detailBody, actionMsg, actionReasons, costBox, forkVisible, setForkVisible, forkMsg,
coordChildren, agents, agentsNote, reviews, reviewMsg, reviewError, traceEntries, traceNote, traceDone,
traceKind, setTraceKind, traceMsg, followups, approvals, approvalMsg, peerMessages, peerMsg,
threadText, threadGateNote, threadMsg, threadList, chatMsg,
threadText, threadGateNote, threadMsg, threadList, chatMsg, chatHistory,
showDetail, refreshDetailLists, doAction, taskCost, forkTask, postReview, submitFindings,
decideApproval, postPeer, threadAction, sendChatMessage, downloadTrace,
refreshReviews: () => void loadReviews(), refreshApprovals: () => void loadApprovals(),
@@ -5,25 +5,33 @@ import { traceEntryToChatMessage, type ChatMessage } from "../../components/chat
import { TRACE_TERMINAL } from "../../api/types";
// Continue chat: same ChatList/ChatBubble/ChatInput as the trace timeline.
// History is the task prompt (user) + message-kind trace entries
// (assistant) + followups already sent (user). Live followups and trace
// entries carry no timestamps, so they cannot be interleaved exactly:
// followups are appended after trace messages and this is stated here
// and in the final report, not hidden by a fabricated order.
// Sending uses only live APIs: terminal tasks -> POST /tasks/:id/continue,
// queued/running tasks -> POST /tasks/:id/followups (direct). No new API.
// Preferred source is GET /chat-history (server-merged true alternating
// history: prompt + persisted continues + followups + trace, in
// timestamp order). Only when that endpoint is unavailable (old server)
// fall back to the previous view: prompt + message-kind trace entries
// + followups appended after — that fallback cannot interleave exactly
// and cannot show pre-persistence continues, and it says so nowhere
// else, so the fallback path is kept only as a compatibility shim.
// Sending uses only live APIs: terminal tasks -> POST /tasks/:id/continue
// (the server now persists that message in the same transaction),
// queued/running tasks -> POST /tasks/:id/followups (direct).
export default function ContinuePanel() {
const ctx = useConsole();
const t = ctx.currentTask;
const messages = useMemo<ChatMessage[]>(() => {
if (!t) return [];
if (ctx.chatHistory) {
return ctx.chatHistory.messages.map((m) => ({
id: m.id, role: m.role, text: m.text, label: m.label,
}));
}
const head: ChatMessage[] = t.prompt ? [{ id: "task-prompt", role: "user", text: t.prompt }] : [];
const assistant = ctx.traceEntries.filter((e) => e.kind === "message").map((e, i) => traceEntryToChatMessage(e, i));
const followups: ChatMessage[] = ctx.followups.map((f, i) => ({
id: "followup-" + i, role: "user" as const, text: String(f.body || ""), label: "后续消息",
}));
return [...head, ...assistant, ...followups];
}, [t, ctx.traceEntries, ctx.followups]);
}, [t, ctx.chatHistory, ctx.traceEntries, ctx.followups]);
if (!t) return <div className="muted">点任务行查看详情后,这里显示继续会话</div>;
const terminal = TRACE_TERMINAL.includes(t.status);
const running = ["queued", "running"].includes(t.status);
@@ -33,8 +41,15 @@ export default function ContinuePanel() {
: running
? "运行中/排队中:发送走后续消息直接发送(跑完当前轮后优先执行)"
: "merged/merge-failed 任务不可继续,也不可发后续消息";
const legacy = ctx.chatHistory ? (ctx.chatHistory.legacy_continue_count ?? null) : null;
return (
<div className="chat-column">
{legacy !== null && legacy > 0 ? (
<div className="muted">此前有 {legacy} 条继续消息发生在持久化之前,未入库,上面历史不完整;部署后新发的继续消息会正常显示。</div>
) : null}
{ctx.chatHistory && ctx.chatHistory.trace_note ? (
<div className="muted">{ctx.chatHistory.trace_note}</div>
) : null}
<ChatList messages={messages} empty="(暂无会话消息)" />
<ChatInput
disabled={!canSend}
+25 -25
View File
@@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Codex 命令台</title>
<script type="module" crossorigin>(function(){const r=document.createElement("link").relList;if(r&&r.supports&&r.supports("modulepreload"))return;for(const _ of document.querySelectorAll('link[rel="modulepreload"]'))o(_);new MutationObserver(_=>{for(const M of _)if(M.type==="childList")for(const N of M.addedNodes)N.tagName==="LINK"&&N.rel==="modulepreload"&&o(N)}).observe(document,{childList:!0,subtree:!0});function m(_){const M={};return _.integrity&&(M.integrity=_.integrity),_.referrerPolicy&&(M.referrerPolicy=_.referrerPolicy),_.crossOrigin==="use-credentials"?M.credentials="include":_.crossOrigin==="anonymous"?M.credentials="omit":M.credentials="same-origin",M}function o(_){if(_.ep)return;_.ep=!0;const M=m(_);fetch(_.href,M)}})();function qh(i){return i&&i.__esModule&&Object.prototype.hasOwnProperty.call(i,"default")?i.default:i}var tr={exports:{}},Pu={};/**
<script type="module" crossorigin>(function(){const r=document.createElement("link").relList;if(r&&r.supports&&r.supports("modulepreload"))return;for(const _ of document.querySelectorAll('link[rel="modulepreload"]'))o(_);new MutationObserver(_=>{for(const M of _)if(M.type==="childList")for(const N of M.addedNodes)N.tagName==="LINK"&&N.rel==="modulepreload"&&o(N)}).observe(document,{childList:!0,subtree:!0});function m(_){const M={};return _.integrity&&(M.integrity=_.integrity),_.referrerPolicy&&(M.referrerPolicy=_.referrerPolicy),_.crossOrigin==="use-credentials"?M.credentials="include":_.crossOrigin==="anonymous"?M.credentials="omit":M.credentials="same-origin",M}function o(_){if(_.ep)return;_.ep=!0;const M=m(_);fetch(_.href,M)}})();function qh(i){return i&&i.__esModule&&Object.prototype.hasOwnProperty.call(i,"default")?i.default:i}var er={exports:{}},li={};/**
* @license React
* react-jsx-runtime.production.js
*
@@ -12,7 +12,7 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var Nh;function Ay(){if(Nh)return Pu;Nh=1;var i=Symbol.for("react.transitional.element"),r=Symbol.for("react.fragment");function m(o,_,M){var N=null;if(M!==void 0&&(N=""+M),_.key!==void 0&&(N=""+_.key),"key"in _){M={};for(var j in _)j!=="key"&&(M[j]=_[j])}else M=_;return _=M.ref,{$$typeof:i,type:o,key:N,ref:_!==void 0?_:null,props:M}}return Pu.Fragment=r,Pu.jsx=m,Pu.jsxs=m,Pu}var Ch;function My(){return Ch||(Ch=1,tr.exports=Ay()),tr.exports}var s=My(),er={exports:{}},ct={};/**
*/var Nh;function Ay(){if(Nh)return li;Nh=1;var i=Symbol.for("react.transitional.element"),r=Symbol.for("react.fragment");function m(o,_,M){var N=null;if(M!==void 0&&(N=""+M),_.key!==void 0&&(N=""+_.key),"key"in _){M={};for(var E in _)E!=="key"&&(M[E]=_[E])}else M=_;return _=M.ref,{$$typeof:i,type:o,key:N,ref:_!==void 0?_:null,props:M}}return li.Fragment=r,li.jsx=m,li.jsxs=m,li}var Ch;function My(){return Ch||(Ch=1,er.exports=Ay()),er.exports}var s=My(),lr={exports:{}},ct={};/**
* @license React
* react.production.js
*
@@ -20,7 +20,7 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var Ah;function Oy(){if(Ah)return ct;Ah=1;var i=Symbol.for("react.transitional.element"),r=Symbol.for("react.portal"),m=Symbol.for("react.fragment"),o=Symbol.for("react.strict_mode"),_=Symbol.for("react.profiler"),M=Symbol.for("react.consumer"),N=Symbol.for("react.context"),j=Symbol.for("react.forward_ref"),B=Symbol.for("react.suspense"),R=Symbol.for("react.memo"),G=Symbol.for("react.lazy"),E=Symbol.for("react.activity"),J=Symbol.for("react.view_transition"),ut=Symbol.iterator;function k(v){return v===null||typeof v!="object"?null:(v=ut&&v[ut]||v["@@iterator"],typeof v=="function"?v:null)}var P={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},it=Object.assign,Tt={};function Vt(v,D,F){this.props=v,this.context=D,this.refs=Tt,this.updater=F||P}Vt.prototype.isReactComponent={},Vt.prototype.setState=function(v,D){if(typeof v!="object"&&typeof v!="function"&&v!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,v,D,"setState")},Vt.prototype.forceUpdate=function(v){this.updater.enqueueForceUpdate(this,v,"forceUpdate")};function be(){}be.prototype=Vt.prototype;function Me(v,D,F){this.props=v,this.context=D,this.refs=Tt,this.updater=F||P}var Ft=Me.prototype=new be;Ft.constructor=Me,it(Ft,Vt.prototype),Ft.isPureReactComponent=!0;var oe=Array.isArray;function st(){}var ht={H:null,A:null,T:null,S:null},Wt=Object.prototype.hasOwnProperty;function te(v,D,F){var K=F.ref;return{$$typeof:i,type:v,key:D,ref:K!==void 0?K:null,props:F}}function Lt(v,D){return te(v.type,D,v.props)}function de(v){return typeof v=="object"&&v!==null&&v.$$typeof===i}function Dt(v){var D={"=":"=0",":":"=2"};return"$"+v.replace(/[=:]/g,function(F){return D[F]})}var Pe=/\/+/g;function H(v,D){return typeof v=="object"&&v!==null&&v.key!=null?Dt(""+v.key):D.toString(36)}function w(v){switch(v.status){case"fulfilled":return v.value;case"rejected":throw v.reason;default:switch(typeof v.status=="string"?v.then(st,st):(v.status="pending",v.then(function(D){v.status==="pending"&&(v.status="fulfilled",v.value=D)},function(D){v.status==="pending"&&(v.status="rejected",v.reason=D)})),v.status){case"fulfilled":return v.value;case"rejected":throw v.reason}}throw v}function L(v,D,F,K,yt){var dt=typeof v;(dt==="undefined"||dt==="boolean")&&(v=null);var xt=!1;if(v===null)xt=!0;else switch(dt){case"bigint":case"string":case"number":xt=!0;break;case"object":switch(v.$$typeof){case i:case r:xt=!0;break;case G:return xt=v._init,L(xt(v._payload),D,F,K,yt)}}if(xt)return yt=yt(v),xt=K===""?"."+H(v,0):K,oe(yt)?(F="",xt!=null&&(F=xt.replace(Pe,"$&/")+"/"),L(yt,D,F,"",function(xe){return xe})):yt!=null&&(de(yt)&&(yt=Lt(yt,F+(yt.key==null||v&&v.key===yt.key?"":(""+yt.key).replace(Pe,"$&/")+"/")+xt)),D.push(yt)),1;xt=0;var Q=K===""?".":K+":";if(oe(v))for(var at=0;at<v.length;at++)K=v[at],dt=Q+H(K,at),xt+=L(K,D,F,dt,yt);else if(at=k(v),typeof at=="function")for(v=at.call(v),at=0;!(K=v.next()).done;)K=K.value,dt=Q+H(K,at++),xt+=L(K,D,F,dt,yt);else if(dt==="object"){if(typeof v.then=="function")return L(w(v),D,F,K,yt);throw D=String(v),Error("Objects are not valid as a React child (found: "+(D==="[object Object]"?"object with keys {"+Object.keys(v).join(", ")+"}":D)+"). If you meant to render a collection of children, use an array instead.")}return xt}function tt(v,D,F){if(v==null)return v;var K=[],yt=0;return L(v,K,"","",function(dt){return D.call(F,dt,yt++)}),K}function jt(v){if(v._status===-1){var D=v._result,F=D();F.then(function(K){(v._status===0||v._status===-1)&&(v._status=1,v._result=K,F.status===void 0&&(F.status="fulfilled",F.value=K))},function(K){(v._status===0||v._status===-1)&&(v._status=2,v._result=K,F.status===void 0&&(F.status="rejected",F.reason=K))}),v._status===-1&&(v._status=0,v._result=F)}if(v._status===1)return v._result.default;throw v._result}var vt=typeof reportError=="function"?reportError:function(v){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var D=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof v=="object"&&v!==null&&typeof v.message=="string"?String(v.message):String(v),error:v});if(!window.dispatchEvent(D))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",v);return}console.error(v)};function ee(v){var D=ht.T,F={};F.types=D!==null?D.types:null,ht.T=F;try{var K=v(),yt=ht.S;yt!==null&&yt(F,K),typeof K=="object"&&K!==null&&typeof K.then=="function"&&K.then(st,vt)}catch(dt){vt(dt)}finally{D!==null&&F.types!==null&&(D.types=F.types),ht.T=D}}function Oe(v){var D=ht.T;if(D!==null){var F=D.types;F===null?D.types=[v]:F.indexOf(v)===-1&&F.push(v)}else ee(Oe.bind(null,v))}var ue={map:tt,forEach:function(v,D,F){tt(v,function(){D.apply(this,arguments)},F)},count:function(v){var D=0;return tt(v,function(){D++}),D},toArray:function(v){return tt(v,function(D){return D})||[]},only:Line truncated
*/var Ah;function Oy(){if(Ah)return ct;Ah=1;var i=Symbol.for("react.transitional.element"),r=Symbol.for("react.portal"),m=Symbol.for("react.fragment"),o=Symbol.for("react.strict_mode"),_=Symbol.for("react.profiler"),M=Symbol.for("react.consumer"),N=Symbol.for("react.context"),E=Symbol.for("react.forward_ref"),B=Symbol.for("react.suspense"),z=Symbol.for("react.memo"),G=Symbol.for("react.lazy"),j=Symbol.for("react.activity"),V=Symbol.for("react.view_transition"),ut=Symbol.iterator;function K(v){return v===null||typeof v!="object"?null:(v=ut&&v[ut]||v["@@iterator"],typeof v=="function"?v:null)}var tt={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},it=Object.assign,jt={};function Xt(v,R,W){this.props=v,this.context=R,this.refs=jt,this.updater=W||tt}Xt.prototype.isReactComponent={},Xt.prototype.setState=function(v,R){if(typeof v!="object"&&typeof v!="function"&&v!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,v,R,"setState")},Xt.prototype.forceUpdate=function(v){this.updater.enqueueForceUpdate(this,v,"forceUpdate")};function ge(){}ge.prototype=Xt.prototype;function Oe(v,R,W){this.props=v,this.context=R,this.refs=jt,this.updater=W||tt}var Jt=Oe.prototype=new ge;Jt.constructor=Oe,it(Jt,Xt.prototype),Jt.isPureReactComponent=!0;var fe=Array.isArray;function st(){}var ht={H:null,A:null,T:null,S:null},Ft=Object.prototype.hasOwnProperty;function Pt(v,R,W){var J=W.ref;return{$$typeof:i,type:v,key:R,ref:J!==void 0?J:null,props:W}}function kt(v,R){return Pt(v.type,R,v.props)}function re(v){return typeof v=="object"&&v!==null&&v.$$typeof===i}function Dt(v){var R={"=":"=0",":":"=2"};return"$"+v.replace(/[=:]/g,function(W){return R[W]})}var tl=/\/+/g;function U(v,R){return typeof v=="object"&&v!==null&&v.key!=null?Dt(""+v.key):R.toString(36)}function w(v){switch(v.status){case"fulfilled":return v.value;case"rejected":throw v.reason;default:switch(typeof v.status=="string"?v.then(st,st):(v.status="pending",v.then(function(R){v.status==="pending"&&(v.status="fulfilled",v.value=R)},function(R){v.status==="pending"&&(v.status="rejected",v.reason=R)})),v.status){case"fulfilled":return v.value;case"rejected":throw v.reason}}throw v}function k(v,R,W,J,yt){var dt=typeof v;(dt==="undefined"||dt==="boolean")&&(v=null);var xt=!1;if(v===null)xt=!0;else switch(dt){case"bigint":case"string":case"number":xt=!0;break;case"object":switch(v.$$typeof){case i:case r:xt=!0;break;case G:return xt=v._init,k(xt(v._payload),R,W,J,yt)}}if(xt)return yt=yt(v),xt=J===""?"."+U(v,0):J,fe(yt)?(W="",xt!=null&&(W=xt.replace(tl,"$&/")+"/"),k(yt,R,W,"",function(Se){return Se})):yt!=null&&(re(yt)&&(yt=kt(yt,W+(yt.key==null||v&&v.key===yt.key?"":(""+yt.key).replace(tl,"$&/")+"/")+xt)),R.push(yt)),1;xt=0;var Q=J===""?".":J+":";if(fe(v))for(var at=0;at<v.length;at++)J=v[at],dt=Q+U(J,at),xt+=k(J,R,W,dt,yt);else if(at=K(v),typeof at=="function")for(v=at.call(v),at=0;!(J=v.next()).done;)J=J.value,dt=Q+U(J,at++),xt+=k(J,R,W,dt,yt);else if(dt==="object"){if(typeof v.then=="function")return k(w(v),R,W,J,yt);throw R=String(v),Error("Objects are not valid as a React child (found: "+(R==="[object Object]"?"object with keys {"+Object.keys(v).join(", ")+"}":R)+"). If you meant to render a collection of children, use an array instead.")}return xt}function et(v,R,W){if(v==null)return v;var J=[],yt=0;return k(v,J,"","",function(dt){return R.call(W,dt,yt++)}),J}function Tt(v){if(v._status===-1){var R=v._result,W=R();W.then(function(J){(v._status===0||v._status===-1)&&(v._status=1,v._result=J,W.status===void 0&&(W.status="fulfilled",W.value=J))},function(J){(v._status===0||v._status===-1)&&(v._status=2,v._result=J,W.status===void 0&&(W.status="rejected",W.reason=J))}),v._status===-1&&(v._status=0,v._result=W)}if(v._status===1)return v._result.default;throw v._result}var vt=typeof reportError=="function"?reportError:function(v){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var R=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof v=="object"&&v!==null&&typeof v.message=="string"?String(v.message):String(v),error:v});if(!window.dispatchEvent(R))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",v);return}console.error(v)};function te(v){var R=ht.T,W={};W.types=R!==null?R.types:null,ht.T=W;try{var J=v(),yt=ht.S;yt!==null&&yt(W,J),typeof J=="object"&&J!==null&&typeof J.then=="function"&&J.then(st,vt)}catch(dt){vt(dt)}finally{R!==null&&W.types!==null&&(R.types=W.types),ht.T=R}}function ze(v){var R=ht.T;if(R!==null){var W=R.types;W===null?R.types=[v]:W.indexOf(v)===-1&&W.push(v)}else te(ze.bind(null,v))}var ne={map:et,forEach:function(v,R,W){et(v,function(){R.apply(this,arguments)},W)},count:function(v){var R=0;return et(v,function(){R++}),R},toArray:function(v){return et(v,function(R){return R})||[]},onLine truncated
* @license React
* scheduler.production.js
*
@@ -28,7 +28,7 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var Oh;function Ry(){return Oh||(Oh=1,(function(i){function r(w,L){var tt=w.length;w.push(L);t:for(;0<tt;){var jt=tt-1>>>1,vt=w[jt];if(0<_(vt,L))w[jt]=L,w[tt]=vt,tt=jt;else break t}}function m(w){return w.length===0?null:w[0]}function o(w){if(w.length===0)return null;var L=w[0],tt=w.pop();if(tt!==L){w[0]=tt;t:for(var jt=0,vt=w.length,ee=vt>>>1;jt<ee;){var Oe=2*(jt+1)-1,ue=w[Oe],v=Oe+1,D=w[v];if(0>_(ue,tt))v<vt&&0>_(D,ue)?(w[jt]=D,w[v]=tt,jt=v):(w[jt]=ue,w[Oe]=tt,jt=Oe);else if(v<vt&&0>_(D,tt))w[jt]=D,w[v]=tt,jt=v;else break t}}return L}function _(w,L){var tt=w.sortIndex-L.sortIndex;return tt!==0?tt:w.id-L.id}if(i.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var M=performance;i.unstable_now=function(){return M.now()}}else{var N=Date,j=N.now();i.unstable_now=function(){return N.now()-j}}var B=[],R=[],G=1,E=null,J=3,ut=!1,k=!1,P=!1,it=!1,Tt=typeof setTimeout=="function"?setTimeout:null,Vt=typeof clearTimeout=="function"?clearTimeout:null,be=typeof setImmediate<"u"?setImmediate:null;function Me(w){for(var L=m(R);L!==null;){if(L.callback===null)o(R);else if(L.startTime<=w)o(R),L.sortIndex=L.expirationTime,r(B,L);else break;L=m(R)}}function Ft(w){if(P=!1,Me(w),!k)if(m(B)!==null)k=!0,oe||(oe=!0,de());else{var L=m(R);L!==null&&H(Ft,L.startTime-w)}}var oe=!1,st=-1,ht=5,Wt=-1;function te(){return it?!0:!(i.unstable_now()-Wt<ht)}function Lt(){if(it=!1,oe){var w=i.unstable_now();Wt=w;var L=!0;try{t:{k=!1,P&&(P=!1,Vt(st),st=-1),ut=!0;var tt=J;try{e:{for(Me(w),E=m(B);E!==null&&!(E.expirationTime>w&&te());){var jt=E.callback;if(typeof jt=="function"){E.callback=null,J=E.priorityLevel;var vt=jt(E.expirationTime<=w);if(w=i.unstable_now(),typeof vt=="function"){E.callback=vt,Me(w),L=!0;break e}E===m(B)&&o(B),Me(w)}else o(B);E=m(B)}if(E!==null)L=!0;else{var ee=m(R);ee!==null&&H(Ft,ee.startTime-w),L=!1}}break t}finally{E=null,J=tt,ut=!1}L=void 0}}finally{L?de():oe=!1}}}var de;if(typeof be=="function")de=function(){be(Lt)};else if(typeof MessageChannel<"u"){var Dt=new MessageChannel,Pe=Dt.port2;Dt.port1.onmessage=Lt,de=function(){Pe.postMessage(null)}}else de=function(){Tt(Lt,0)};function H(w,L){st=Tt(function(){w(i.unstable_now())},L)}i.unstable_IdlePriority=5,i.unstable_ImmediatePriority=1,i.unstable_LowPriority=4,i.unstable_NormalPriority=3,i.unstable_Profiling=null,i.unstable_UserBlockingPriority=2,i.unstable_cancelCallback=function(w){w.callback=null},i.unstable_forceFrameRate=function(w){0>w||125<w?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):ht=0<w?Math.floor(1e3/w):5},i.unstable_getCurrentPriorityLevel=function(){return J},i.unstable_next=function(w){switch(J){case 1:case 2:case 3:var L=3;break;default:L=J}var tt=J;J=L;try{return w()}finally{J=tt}},i.unstable_requestPaint=function(){it=!0},i.unstable_runWithPriority=function(w,L){switch(w){case 1:case 2:case 3:case 4:case 5:break;default:w=3}var tt=J;J=w;try{return L()}finally{J=tt}},i.unstable_scheduleCallback=function(w,L,tt){var jt=i.unstable_now();switch(typeof tt=="object"&&tt!==null?(tt=tt.delay,tt=typeof tt=="number"&&0<tt?jt+tt:jt):tt=jt,w){case 1:var vt=-1;break;case 2:vt=250;break;case 5:vt=1073741823;break;case 4:vt=1e4;break;default:vt=5e3}return vt=tt+vt,w={id:G++,callback:L,priorityLevel:w,startTime:tt,expirationTime:vt,sortIndex:-1},tt>jt?(w.sortIndex=tt,r(R,w),m(B)===null&&w===m(R)&&(P?(Vt(st),st=-1):P=!0,H(Ft,tt-jt))):(w.sortIndex=vt,r(B,w),k||ut||(k=!0,oe||(oe=!0,de()))),w},i.unstable_shouldYield=te,i.unstable_wrapCallback=function(w){var L=J;return function(){var tt=J;J=L;try{return w.apply(this,arguments)}finally{J=tt}}}})(nr)),nr}var zh;function Dy(){return zh||(zh=1,ar.exports=Ry()),ar.exports}var ur={exports:{}},Ae={};/**
*/var Oh;function Ry(){return Oh||(Oh=1,(function(i){function r(w,k){var et=w.length;w.push(k);t:for(;0<et;){var Tt=et-1>>>1,vt=w[Tt];if(0<_(vt,k))w[Tt]=k,w[et]=vt,et=Tt;else break t}}function m(w){return w.length===0?null:w[0]}function o(w){if(w.length===0)return null;var k=w[0],et=w.pop();if(et!==k){w[0]=et;t:for(var Tt=0,vt=w.length,te=vt>>>1;Tt<te;){var ze=2*(Tt+1)-1,ne=w[ze],v=ze+1,R=w[v];if(0>_(ne,et))v<vt&&0>_(R,ne)?(w[Tt]=R,w[v]=et,Tt=v):(w[Tt]=ne,w[ze]=et,Tt=ze);else if(v<vt&&0>_(R,et))w[Tt]=R,w[v]=et,Tt=v;else break t}}return k}function _(w,k){var et=w.sortIndex-k.sortIndex;return et!==0?et:w.id-k.id}if(i.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var M=performance;i.unstable_now=function(){return M.now()}}else{var N=Date,E=N.now();i.unstable_now=function(){return N.now()-E}}var B=[],z=[],G=1,j=null,V=3,ut=!1,K=!1,tt=!1,it=!1,jt=typeof setTimeout=="function"?setTimeout:null,Xt=typeof clearTimeout=="function"?clearTimeout:null,ge=typeof setImmediate<"u"?setImmediate:null;function Oe(w){for(var k=m(z);k!==null;){if(k.callback===null)o(z);else if(k.startTime<=w)o(z),k.sortIndex=k.expirationTime,r(B,k);else break;k=m(z)}}function Jt(w){if(tt=!1,Oe(w),!K)if(m(B)!==null)K=!0,fe||(fe=!0,re());else{var k=m(z);k!==null&&U(Jt,k.startTime-w)}}var fe=!1,st=-1,ht=5,Ft=-1;function Pt(){return it?!0:!(i.unstable_now()-Ft<ht)}function kt(){if(it=!1,fe){var w=i.unstable_now();Ft=w;var k=!0;try{t:{K=!1,tt&&(tt=!1,Xt(st),st=-1),ut=!0;var et=V;try{e:{for(Oe(w),j=m(B);j!==null&&!(j.expirationTime>w&&Pt());){var Tt=j.callback;if(typeof Tt=="function"){j.callback=null,V=j.priorityLevel;var vt=Tt(j.expirationTime<=w);if(w=i.unstable_now(),typeof vt=="function"){j.callback=vt,Oe(w),k=!0;break e}j===m(B)&&o(B),Oe(w)}else o(B);j=m(B)}if(j!==null)k=!0;else{var te=m(z);te!==null&&U(Jt,te.startTime-w),k=!1}}break t}finally{j=null,V=et,ut=!1}k=void 0}}finally{k?re():fe=!1}}}var re;if(typeof ge=="function")re=function(){ge(kt)};else if(typeof MessageChannel<"u"){var Dt=new MessageChannel,tl=Dt.port2;Dt.port1.onmessage=kt,re=function(){tl.postMessage(null)}}else re=function(){jt(kt,0)};function U(w,k){st=jt(function(){w(i.unstable_now())},k)}i.unstable_IdlePriority=5,i.unstable_ImmediatePriority=1,i.unstable_LowPriority=4,i.unstable_NormalPriority=3,i.unstable_Profiling=null,i.unstable_UserBlockingPriority=2,i.unstable_cancelCallback=function(w){w.callback=null},i.unstable_forceFrameRate=function(w){0>w||125<w?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):ht=0<w?Math.floor(1e3/w):5},i.unstable_getCurrentPriorityLevel=function(){return V},i.unstable_next=function(w){switch(V){case 1:case 2:case 3:var k=3;break;default:k=V}var et=V;V=k;try{return w()}finally{V=et}},i.unstable_requestPaint=function(){it=!0},i.unstable_runWithPriority=function(w,k){switch(w){case 1:case 2:case 3:case 4:case 5:break;default:w=3}var et=V;V=w;try{return k()}finally{V=et}},i.unstable_scheduleCallback=function(w,k,et){var Tt=i.unstable_now();switch(typeof et=="object"&&et!==null?(et=et.delay,et=typeof et=="number"&&0<et?Tt+et:Tt):et=Tt,w){case 1:var vt=-1;break;case 2:vt=250;break;case 5:vt=1073741823;break;case 4:vt=1e4;break;default:vt=5e3}return vt=et+vt,w={id:G++,callback:k,priorityLevel:w,startTime:et,expirationTime:vt,sortIndex:-1},et>Tt?(w.sortIndex=et,r(z,w),m(B)===null&&w===m(z)&&(tt?(Xt(st),st=-1):tt=!0,U(Jt,et-Tt))):(w.sortIndex=vt,r(B,w),K||ut||(K=!0,fe||(fe=!0,re()))),w},i.unstable_shouldYield=Pt,i.unstable_wrapCallback=function(w){var k=V;return function(){var et=V;V=k;try{return w.apply(this,arguments)}finally{V=et}}}})(ur)),ur}var zh;function Dy(){return zh||(zh=1,nr.exports=Ry()),nr.exports}var ir={exports:{}},Me={};/**
* @license React
* react-dom.production.js
*
@@ -36,7 +36,7 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var Rh;function Uy(){if(Rh)return Ae;Rh=1;var i=fr();function r(G){var E="https://react.dev/errors/"+G;if(1<arguments.length){E+="?args[]="+encodeURIComponent(arguments[1]);for(var J=2;J<arguments.length;J++)E+="&args[]="+encodeURIComponent(arguments[J])}return"Minified React error #"+G+"; visit "+E+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function m(){}var o={d:{f:m,r:function(){throw Error(r(522))},D:m,C:m,L:m,m,X:m,S:m,M:m},p:0,findDOMNode:null},_=Symbol.for("react.portal"),M=Symbol.for("react.recoverable"),N=Symbol.for("react.optimistic_key");function j(G,E,J){var ut=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:_,key:ut==null?null:ut===N?N:""+ut,children:G,containerInfo:E,implementation:J}}var B=i.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function R(G,E){if(G==="font")return"";if(typeof E=="string")return E==="use-credentials"?E:""}return Ae.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=o,Ae.browser=function(G){return{$$typeof:M,_reason:G}},Ae.createPortal=function(G,E){var J=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!E||E.nodeType!==1&&E.nodeType!==9&&E.nodeType!==11)throw Error(r(299));return j(G,E,null,J)},Ae.flushSync=function(G){var E=B.T,J=o.p;try{if(B.T=null,o.p=2,G)return G()}finally{B.T=E,o.p=J,o.d.f()}},Ae.preconnect=function(G,E){typeof G=="string"&&(E?(E=E.crossOrigin,E=typeof E=="string"?E==="use-credentials"?E:"":void 0):E=null,o.d.C(G,E))},Ae.prefetchDNS=function(G){typeof G=="string"&&o.d.D(G)},Ae.preinit=function(G,E){if(typeof G=="string"&&E&&typeof E.as=="string"){var J=E.as,ut=R(J,E.crossOrigin),k=typeof E.integrity=="string"?E.integrity:void 0,P=typeof E.fetchPriority=="string"?E.fetchPriority:void 0;J==="style"?o.d.S(G,typeof E.precedence=="string"?E.precedence:void 0,{crossOrigin:ut,integrity:k,fetchPriority:P}):J==="script"&&o.d.X(G,{crossOrigin:ut,integrity:k,fetchPriority:P,nonce:typeof E.nonce=="string"?E.nonce:void 0})}},Ae.preinitModule=function(G,E){if(typeof G=="string")if(typeof E=="object"&&E!==null){if(E.as==null||E.as==="script"){var J=R(E.as,E.crossOrigin);o.d.M(G,{crossOrigin:J,integrity:typeof E.integrity=="string"?E.integrity:void 0,nonce:typeof E.nonce=="string"?E.nonce:void 0,fetchPriority:typeof E.fetchPriority=="string"?E.fetchPriority:void 0})}}else E==null&&o.d.M(G)},Ae.preload=function(G,E){if(typeof G=="string"&&typeof E=="object"&&E!==null&&typeof E.as=="string"){var J=E.as,ut=R(J,E.crossOrigin);o.d.L(G,J,{crossOrigin:ut,integrity:typeof E.integrity=="string"?E.integrity:void 0,nonce:typeof E.nonce=="string"?E.nonce:void 0,type:typeof E.type=="string"?E.type:void 0,fetchPriority:typeof E.fetchPriority=="string"?E.fetchPriority:void 0,referrerPolicy:typeof E.referrerPolicy=="string"?E.referrerPolicy:void 0,imageSrcSet:typeof E.imageSrcSet=="string"?E.imageSrcSet:void 0,imageSizes:typeof E.imageSizes=="string"?E.imageSizes:void 0,media:typeof E.media=="string"?E.media:void 0})}},Ae.preloadModule=function(G,E){if(typeof G=="string")if(E){var J=R(E.as,E.crossOrigin);o.d.m(G,{as:typeof E.as=="string"&&E.as!=="script"?E.as:void 0,crossOrigin:J,integrity:typeof E.integrity=="string"?E.integrity:void 0,nonce:typeof E.nonce=="string"?E.nonce:void 0,fetchPriority:typeof E.fetchPriority=="string"?E.fetchPriority:void 0})}else o.d.m(G)},Ae.requestFormReset=function(G){o.d.r(G)},Ae.unstable_batchedUpdates=function(G,E){return G(E)},Ae.useFormState=function(G,E,J){return B.H.useFormState(G,E,J)},Ae.useFormStatus=function(){return B.H.useHostTransitionStatus()},Ae.version="19.3.1",Ae}var Dh;function Hy(){if(Dh)return ur.exports;Dh=1;function i(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(r){console.error(r)}}return i(),ur.exports=Uy(),ur.exports}/**
*/var Rh;function Uy(){if(Rh)return Me;Rh=1;var i=rr();function r(G){var j="https://react.dev/errors/"+G;if(1<arguments.length){j+="?args[]="+encodeURIComponent(arguments[1]);for(var V=2;V<arguments.length;V++)j+="&args[]="+encodeURIComponent(arguments[V])}return"Minified React error #"+G+"; visit "+j+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function m(){}var o={d:{f:m,r:function(){throw Error(r(522))},D:m,C:m,L:m,m,X:m,S:m,M:m},p:0,findDOMNode:null},_=Symbol.for("react.portal"),M=Symbol.for("react.recoverable"),N=Symbol.for("react.optimistic_key");function E(G,j,V){var ut=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:_,key:ut==null?null:ut===N?N:""+ut,children:G,containerInfo:j,implementation:V}}var B=i.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function z(G,j){if(G==="font")return"";if(typeof j=="string")return j==="use-credentials"?j:""}return Me.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=o,Me.browser=function(G){return{$$typeof:M,_reason:G}},Me.createPortal=function(G,j){var V=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!j||j.nodeType!==1&&j.nodeType!==9&&j.nodeType!==11)throw Error(r(299));return E(G,j,null,V)},Me.flushSync=function(G){var j=B.T,V=o.p;try{if(B.T=null,o.p=2,G)return G()}finally{B.T=j,o.p=V,o.d.f()}},Me.preconnect=function(G,j){typeof G=="string"&&(j?(j=j.crossOrigin,j=typeof j=="string"?j==="use-credentials"?j:"":void 0):j=null,o.d.C(G,j))},Me.prefetchDNS=function(G){typeof G=="string"&&o.d.D(G)},Me.preinit=function(G,j){if(typeof G=="string"&&j&&typeof j.as=="string"){var V=j.as,ut=z(V,j.crossOrigin),K=typeof j.integrity=="string"?j.integrity:void 0,tt=typeof j.fetchPriority=="string"?j.fetchPriority:void 0;V==="style"?o.d.S(G,typeof j.precedence=="string"?j.precedence:void 0,{crossOrigin:ut,integrity:K,fetchPriority:tt}):V==="script"&&o.d.X(G,{crossOrigin:ut,integrity:K,fetchPriority:tt,nonce:typeof j.nonce=="string"?j.nonce:void 0})}},Me.preinitModule=function(G,j){if(typeof G=="string")if(typeof j=="object"&&j!==null){if(j.as==null||j.as==="script"){var V=z(j.as,j.crossOrigin);o.d.M(G,{crossOrigin:V,integrity:typeof j.integrity=="string"?j.integrity:void 0,nonce:typeof j.nonce=="string"?j.nonce:void 0,fetchPriority:typeof j.fetchPriority=="string"?j.fetchPriority:void 0})}}else j==null&&o.d.M(G)},Me.preload=function(G,j){if(typeof G=="string"&&typeof j=="object"&&j!==null&&typeof j.as=="string"){var V=j.as,ut=z(V,j.crossOrigin);o.d.L(G,V,{crossOrigin:ut,integrity:typeof j.integrity=="string"?j.integrity:void 0,nonce:typeof j.nonce=="string"?j.nonce:void 0,type:typeof j.type=="string"?j.type:void 0,fetchPriority:typeof j.fetchPriority=="string"?j.fetchPriority:void 0,referrerPolicy:typeof j.referrerPolicy=="string"?j.referrerPolicy:void 0,imageSrcSet:typeof j.imageSrcSet=="string"?j.imageSrcSet:void 0,imageSizes:typeof j.imageSizes=="string"?j.imageSizes:void 0,media:typeof j.media=="string"?j.media:void 0})}},Me.preloadModule=function(G,j){if(typeof G=="string")if(j){var V=z(j.as,j.crossOrigin);o.d.m(G,{as:typeof j.as=="string"&&j.as!=="script"?j.as:void 0,crossOrigin:V,integrity:typeof j.integrity=="string"?j.integrity:void 0,nonce:typeof j.nonce=="string"?j.nonce:void 0,fetchPriority:typeof j.fetchPriority=="string"?j.fetchPriority:void 0})}else o.d.m(G)},Me.requestFormReset=function(G){o.d.r(G)},Me.unstable_batchedUpdates=function(G,j){return G(j)},Me.useFormState=function(G,j,V){return B.H.useFormState(G,j,V)},Me.useFormStatus=function(){return B.H.useHostTransitionStatus()},Me.version="19.3.1",Me}var Dh;function Hy(){if(Dh)return ir.exports;Dh=1;function i(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(r){console.error(r)}}return i(),ir.exports=Uy(),ir.exports}/**
* @license React
* react-dom-client.production.js
*
@@ -44,31 +44,31 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var Uh;function By(){if(Uh)return ti;Uh=1;var i=Dy(),r=fr(),m=Hy();function o(t){var e="https://react.dev/errors/"+t;if(1<arguments.length){e+="?args[]="+encodeURIComponent(arguments[1]);for(var l=2;l<arguments.length;l++)e+="&args[]="+encodeURIComponent(arguments[l])}return"Minified React error #"+t+"; visit "+e+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function _(t){return!(!t||t.nodeType!==1&&t.nodeType!==9&&t.nodeType!==11)}function M(t){for(var e=t,l=e;l&&!l.alternate;)e=l,(e.flags&4098)!==0&&(t=e.return),l=e.return;for(;e.return;)e=e.return;return e.tag===3?t:null}function N(t){if(t.tag===13){var e=t.memoizedState;if(e===null&&(t=t.alternate,t!==null&&(e=t.memoizedState)),e!==null)return e.dehydrated}return null}function j(t){if(t.tag===31){var e=t.memoizedState;if(e===null&&(t=t.alternate,t!==null&&(e=t.memoizedState)),e!==null)return e.dehydrated}return null}function B(t){if(M(t)!==t)throw Error(o(188))}function R(t){var e=t.alternate;if(!e){if(e=M(t),e===null)throw Error(o(188));return e!==t?null:t}for(var l=t,a=e;;){var n=l.return;if(n===null)break;var u=n.alternate;if(u===null){if(a=n.return,a!==null){l=a;continue}break}if(n.child===u.child){for(u=n.child;u;){if(u===l)return B(n),t;if(u===a)return B(n),e;u=u.sibling}throw Error(o(188))}if(l.return!==a.return)l=n,a=u;else{for(var c=!1,f=n.child;f;){if(f===l){c=!0,l=n,a=u;break}if(f===a){c=!0,a=n,l=u;break}f=f.sibling}if(!c){for(f=u.child;f;){if(f===l){c=!0,l=u,a=n;break}if(f===a){c=!0,a=u,l=n;break}f=f.sibling}if(!c)throw Error(o(189))}}if(l.alternate!==a)throw Error(o(190))}if(l.tag!==3)throw Error(o(188));return l.stateNode.current===l?t:e}function G(t){var e=t.tag;if(e===5||e===26||e===27||e===6)return t;for(t=t.child;t!==null;){if(e=G(t),e!==null)return e;t=t.sibling}return null}function E(t,e,l,a,n,u){for(;t!==null;){if((t.tag===5||t.tag===27||t.tag===6)&&l(t,a,n,u)||(t.tag!==22||t.memoizedState===null)&&(e||t.tag!==5&&t.tag!==27)&&E(t.child,e,l,a,n,u))return!0;t=t.sibling}return!1}function J(t){for(t=t.return;t!==null;){if(t.tag===3||t.tag===5||t.tag===27)return t;t=t.return}return null}function ut(t){var e=!1;for(t=t.return;t!==null&&(t.tag===4&&(e=!0),!(t.tag===3||t.tag===5||t.tag===27));)t=t.return;return e}function k(t){var e=[null,null],l=J(t);return l===null||P(e,t,l.child,{foundSelf:!1}),e}function P(t,e,l,a){for(;l!==null;){if(l===e)a.foundSelf=!0;else if(l.tag===5||l.tag===27||l.tag===6){if(a.foundSelf)return t[1]=l,!0;t[0]=l}else if((l.tag!==22||l.memoizedState===null)&&P(t,e,l.child,a))return!0;l=l.sibling}return!1}function it(t){switch(t.tag){case 5:case 27:case 6:return t.stateNode;case 3:return t.stateNode.containerInfo;default:throw Error(o(559))}}var Tt=null,Vt=null;function be(t,e,l){return t===l?!0:t===e?(Tt=t,!0):!1}function Me(t,e,l){return t===l?(Vt=t,!1):t===e?(Vt!==null&&(Tt=t),!0):!1}function Ft(t){if(t===null)return null;do t=t===null?null:t.return;while(t&&t.tag!==5&&t.tag!==27&&t.tag!==3);return t||null}function oe(t,e,l){for(var a=0,n=t;n;n=l(n))a++;n=0;for(var u=e;u;u=l(u))n++;for(;0<a-n;)t=l(t),a--;for(;0<n-a;)e=l(e),n--;for(;a--;){if(t===e||e!==null&&t===e.alternate)return t;t=l(t),e=l(e)}return null}var st=Object.assign,ht=Symbol.for("react.element"),Wt=Symbol.for("react.transitional.element"),te=Symbol.for("react.portal"),Lt=Symbol.for("react.fragment"),de=Symbol.for("react.strict_mode"),Dt=Symbol.for("react.profiler"),Pe=Symbol.for("react.consumer"),H=Symbol.for("react.context"),w=Symbol.for("react.forward_ref"),L=Symbol.for("react.suspense"),tt=Symbol.for("react.suspense_list"),jt=Symbol.for("react.memo"),vt=Symbol.for("react.lazy"),ee=Symbol.for("react.activity"),Oe=Symbol.for("react.legacy_hidden"),ue=Symbol.for("react.memo_cache_sentinel"),v=Symbol.for("react.view_transition"),D=Symbol.for("react.recoverable"),F=Symbol.iterator;function K(t){return t===null||typeof t!="object"?null:(t=F&&t[F]||t["@@iterator"],typeof t=="function"?t:null)}var yt=Symbol.for("react.client.reference");function dt(t){if(t==null)return null;if(typeof t=="function")return t.$$typeof===yt?null:t.displayName||t.name||null;if(typeof t=="string")return t;switch(t){case Lt:return"Fragment";case Dt:return"Profiler";case de:return"StrictMode";case L:return"Suspense";case tt:return"SuspenseList";case ee:return"Activity";case v:return"ViewTransition"}if(typeof t=="object")switch(t.$$typeof){case te:return"Portal";case H:return t.displayName||"Context";case Pe:return(t._context.displayName||"Context")+".Consumer";case w:var e=t.render;return t=t.displayName,t||(t=e.displayName||e.name||"",t=t!==""?"ForwardRef("+t+")":"ForwardRef"),t;case jt:return e=t.displayName||null,e!==null?e:dt(t.type)||"Memo";case vt:e=t._payload,t=t._init;try{return dt(t(e))}catch{}}return null}var xt=Array.isArray,Q=r.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,at=m.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,xe={pending:!1,data:null,method:null,action:Line truncated
*/var Uh;function By(){if(Uh)return ai;Uh=1;var i=Dy(),r=rr(),m=Hy();function o(t){var e="https://react.dev/errors/"+t;if(1<arguments.length){e+="?args[]="+encodeURIComponent(arguments[1]);for(var l=2;l<arguments.length;l++)e+="&args[]="+encodeURIComponent(arguments[l])}return"Minified React error #"+t+"; visit "+e+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function _(t){return!(!t||t.nodeType!==1&&t.nodeType!==9&&t.nodeType!==11)}function M(t){for(var e=t,l=e;l&&!l.alternate;)e=l,(e.flags&4098)!==0&&(t=e.return),l=e.return;for(;e.return;)e=e.return;return e.tag===3?t:null}function N(t){if(t.tag===13){var e=t.memoizedState;if(e===null&&(t=t.alternate,t!==null&&(e=t.memoizedState)),e!==null)return e.dehydrated}return null}function E(t){if(t.tag===31){var e=t.memoizedState;if(e===null&&(t=t.alternate,t!==null&&(e=t.memoizedState)),e!==null)return e.dehydrated}return null}function B(t){if(M(t)!==t)throw Error(o(188))}function z(t){var e=t.alternate;if(!e){if(e=M(t),e===null)throw Error(o(188));return e!==t?null:t}for(var l=t,a=e;;){var n=l.return;if(n===null)break;var u=n.alternate;if(u===null){if(a=n.return,a!==null){l=a;continue}break}if(n.child===u.child){for(u=n.child;u;){if(u===l)return B(n),t;if(u===a)return B(n),e;u=u.sibling}throw Error(o(188))}if(l.return!==a.return)l=n,a=u;else{for(var c=!1,f=n.child;f;){if(f===l){c=!0,l=n,a=u;break}if(f===a){c=!0,a=n,l=u;break}f=f.sibling}if(!c){for(f=u.child;f;){if(f===l){c=!0,l=u,a=n;break}if(f===a){c=!0,a=u,l=n;break}f=f.sibling}if(!c)throw Error(o(189))}}if(l.alternate!==a)throw Error(o(190))}if(l.tag!==3)throw Error(o(188));return l.stateNode.current===l?t:e}function G(t){var e=t.tag;if(e===5||e===26||e===27||e===6)return t;for(t=t.child;t!==null;){if(e=G(t),e!==null)return e;t=t.sibling}return null}function j(t,e,l,a,n,u){for(;t!==null;){if((t.tag===5||t.tag===27||t.tag===6)&&l(t,a,n,u)||(t.tag!==22||t.memoizedState===null)&&(e||t.tag!==5&&t.tag!==27)&&j(t.child,e,l,a,n,u))return!0;t=t.sibling}return!1}function V(t){for(t=t.return;t!==null;){if(t.tag===3||t.tag===5||t.tag===27)return t;t=t.return}return null}function ut(t){var e=!1;for(t=t.return;t!==null&&(t.tag===4&&(e=!0),!(t.tag===3||t.tag===5||t.tag===27));)t=t.return;return e}function K(t){var e=[null,null],l=V(t);return l===null||tt(e,t,l.child,{foundSelf:!1}),e}function tt(t,e,l,a){for(;l!==null;){if(l===e)a.foundSelf=!0;else if(l.tag===5||l.tag===27||l.tag===6){if(a.foundSelf)return t[1]=l,!0;t[0]=l}else if((l.tag!==22||l.memoizedState===null)&&tt(t,e,l.child,a))return!0;l=l.sibling}return!1}function it(t){switch(t.tag){case 5:case 27:case 6:return t.stateNode;case 3:return t.stateNode.containerInfo;default:throw Error(o(559))}}var jt=null,Xt=null;function ge(t,e,l){return t===l?!0:t===e?(jt=t,!0):!1}function Oe(t,e,l){return t===l?(Xt=t,!1):t===e?(Xt!==null&&(jt=t),!0):!1}function Jt(t){if(t===null)return null;do t=t===null?null:t.return;while(t&&t.tag!==5&&t.tag!==27&&t.tag!==3);return t||null}function fe(t,e,l){for(var a=0,n=t;n;n=l(n))a++;n=0;for(var u=e;u;u=l(u))n++;for(;0<a-n;)t=l(t),a--;for(;0<n-a;)e=l(e),n--;for(;a--;){if(t===e||e!==null&&t===e.alternate)return t;t=l(t),e=l(e)}return null}var st=Object.assign,ht=Symbol.for("react.element"),Ft=Symbol.for("react.transitional.element"),Pt=Symbol.for("react.portal"),kt=Symbol.for("react.fragment"),re=Symbol.for("react.strict_mode"),Dt=Symbol.for("react.profiler"),tl=Symbol.for("react.consumer"),U=Symbol.for("react.context"),w=Symbol.for("react.forward_ref"),k=Symbol.for("react.suspense"),et=Symbol.for("react.suspense_list"),Tt=Symbol.for("react.memo"),vt=Symbol.for("react.lazy"),te=Symbol.for("react.activity"),ze=Symbol.for("react.legacy_hidden"),ne=Symbol.for("react.memo_cache_sentinel"),v=Symbol.for("react.view_transition"),R=Symbol.for("react.recoverable"),W=Symbol.iterator;function J(t){return t===null||typeof t!="object"?null:(t=W&&t[W]||t["@@iterator"],typeof t=="function"?t:null)}var yt=Symbol.for("react.client.reference");function dt(t){if(t==null)return null;if(typeof t=="function")return t.$$typeof===yt?null:t.displayName||t.name||null;if(typeof t=="string")return t;switch(t){case kt:return"Fragment";case Dt:return"Profiler";case re:return"StrictMode";case k:return"Suspense";case et:return"SuspenseList";case te:return"Activity";case v:return"ViewTransition"}if(typeof t=="object")switch(t.$$typeof){case Pt:return"Portal";case U:return t.displayName||"Context";case tl:return(t._context.displayName||"Context")+".Consumer";case w:var e=t.render;return t=t.displayName,t||(t=e.displayName||e.name||"",t=t!==""?"ForwardRef("+t+")":"ForwardRef"),t;case Tt:return e=t.displayName||null,e!==null?e:dt(t.type)||"Memo";case vt:e=t._payload,t=t._init;try{return dt(t(e))}catch{}}return null}var xt=Array.isArray,Q=r.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,at=m.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,Se={pending:!1,data:null,method:null,actiLine truncated
at`)?" (<anonymous>)":-1<l.stack.indexOf("@")?"@unknown:0:0":""}return`
`+ol+t+Dl}var Ue=!1;function el(t,e){if(!t||Ue)return"";Ue=!0;var l=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var a={DetermineComponentFrameRoot:function(){try{if(e){var A=function(){throw Error()};if(Object.defineProperty(A.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(A,[])}catch(q){var g=q}Reflect.construct(t,[],A)}else{try{A.call()}catch(q){g=q}A=!1;try{var x=Object.getOwnPropertyDescriptor(t.prototype,"props");Object.defineProperty(t.prototype,"props",{configurable:!0,set:function(){throw Error()}}),A=!0,new t}finally{A&&(x!==void 0?Object.defineProperty(t.prototype,"props",x):delete t.prototype.props)}}}else{try{throw Error()}catch(q){g=q}(A=t())&&typeof A.catch=="function"&&A.catch(function(){})}}catch(q){if(q&&g&&typeof q.stack=="string")return[q.stack,g.stack]}return[null,null]}};a.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var n=Object.getOwnPropertyDescriptor(a.DetermineComponentFrameRoot,"name");n&&n.configurable&&Object.defineProperty(a.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var u=a.DetermineComponentFrameRoot(),c=u[0],f=u[1];if(c&&f){var d=c.split(`
`+qe+t+ol}var Hl=!1;function dl(t,e){if(!t||Hl)return"";Hl=!0;var l=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var a={DetermineComponentFrameRoot:function(){try{if(e){var A=function(){throw Error()};if(Object.defineProperty(A.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(A,[])}catch(q){var g=q}Reflect.construct(t,[],A)}else{try{A.call()}catch(q){g=q}A=!1;try{var x=Object.getOwnPropertyDescriptor(t.prototype,"props");Object.defineProperty(t.prototype,"props",{configurable:!0,set:function(){throw Error()}}),A=!0,new t}finally{A&&(x!==void 0?Object.defineProperty(t.prototype,"props",x):delete t.prototype.props)}}}else{try{throw Error()}catch(q){g=q}(A=t())&&typeof A.catch=="function"&&A.catch(function(){})}}catch(q){if(q&&g&&typeof q.stack=="string")return[q.stack,g.stack]}return[null,null]}};a.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var n=Object.getOwnPropertyDescriptor(a.DetermineComponentFrameRoot,"name");n&&n.configurable&&Object.defineProperty(a.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var u=a.DetermineComponentFrameRoot(),c=u[0],f=u[1];if(c&&f){var d=c.split(`
`),p=f.split(`
`);for(n=a=0;a<d.length&&!d[a].includes("DetermineComponentFrameRoot");)a++;for(;n<p.length&&!p[n].includes("DetermineComponentFrameRoot");)n++;if(a===d.length||n===p.length)for(a=d.length-1,n=p.length-1;1<=a&&0<=n&&d[a]!==p[n];)n--;for(;1<=a&&0<=n;a--,n--)if(d[a]!==p[n]){if(a!==1||n!==1)do if(a--,n--,0>n||d[a]!==p[n]){var T=`
`+d[a].replace(" at new "," at ");return t.displayName&&T.includes("<anonymous>")&&(T=T.replace("<anonymous>",t.displayName)),T}while(1<=a&&0<=n);break}}}finally{Ue=!1,Error.prepareStackTrace=l}return(l=t?t.displayName||t.name:"")?ie(l):""}function Ul(t,e){switch(t.tag){case 26:case 27:case 5:return ie(t.type);case 16:return ie("Lazy");case 13:return t.child!==e&&e!==null?ie("Suspense Fallback"):ie("Suspense");case 19:return ie("SuspenseList");case 0:case 15:return el(t.type,!1);case 11:return el(t.type.render,!1);case 1:return el(t.type,!0);case 31:return ie("Activity");case 30:return ie("ViewTransition");default:return""}}function dl(t){try{var e="",l=null;do e+=Ul(t,l),l=t,t=t.return;while(t);return e}catch(a){return`
`+d[a].replace(" at new "," at ");return t.displayName&&T.includes("<anonymous>")&&(T=T.replace("<anonymous>",t.displayName)),T}while(1<=a&&0<=n);break}}}finally{Hl=!1,Error.prepareStackTrace=l}return(l=t?t.displayName||t.name:"")?pe(l):""}function Bl(t,e){switch(t.tag){case 26:case 27:case 5:return pe(t.type);case 16:return pe("Lazy");case 13:return t.child!==e&&e!==null?pe("Suspense Fallback"):pe("Suspense");case 19:return pe("SuspenseList");case 0:case 15:return dl(t.type,!1);case 11:return dl(t.type.render,!1);case 1:return dl(t.type,!0);case 31:return pe("Activity");case 30:return pe("ViewTransition");default:return""}}function we(t){try{var e="",l=null;do e+=Bl(t,l),l=t,t=t.return;while(t);return e}catch(a){return`
Error generating stack: `+a.message+`
`+a.stack}}var Kt=Object.prototype.hasOwnProperty,oa=i.unstable_scheduleCallback,da=i.unstable_cancelCallback,Jl=i.unstable_shouldYield,Xa=i.unstable_requestPaint,ae=i.unstable_now,iu=i.unstable_getCurrentPriorityLevel,mn=i.unstable_ImmediatePriority,hn=i.unstable_UserBlockingPriority,Ze=i.unstable_NormalPriority,Va=i.unstable_LowPriority,ma=i.unstable_IdlePriority,cu=i.log,su=i.unstable_setDisableYieldValue,Fl=null,U=null;function z(t){if(typeof cu=="function"&&su(t),U&&typeof U.setStrictMode=="function")try{U.setStrictMode(Fl,t)}catch{}}var Y=Math.clz32?Math.clz32:ha,$=Math.log,Ut=Math.LN2;function ha(t){return t>>>=0,t===0?32:31-($(t)/Ut|0)|0}var ll=256,He=262144,me=4194304;function ml(t){var e=t&42;if(e!==0)return e;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&-t;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function O(t,e,l){var a=t.pendingLanes;if(a===0)return 0;var n=0,u=t.suspendedLanes,c=t.pingedLanes;t=t.warmLanes;var f=a&134217727;return f!==0?(a=f&~u,a!==0?n=ml(a):(c&=f,c!==0?n=ml(c):l||(l=f&~t,l!==0&&(n=ml(l))))):(f=a&~u,f!==0?n=ml(f):c!==0?n=ml(c):l||(l=a&~t,l!==0&&(n=ml(l)))),n===0?0:e!==0&&e!==n&&(e&u)===0&&(u=n&-n,l=e&-e,u>=l||u===32&&(l&4194048)!==0)?e:n}function V(t,e){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&e)===0}function W(t,e){(e&8)!==0&&(e|=e&32);var l=t.entangledLanes;if(l!==0)for(t=t.entanglements,l&=e;0<l;){var a=31-Y(l),n=1<<a;e|=t[a],l&=~n}return e}function Gt(t,e){switch(t){case 1:case 2:case 4:case 8:case 64:return e+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Mt(){var t=me;return me<<=1,(me&62914560)===0&&(me=4194304),t}function $t(t){for(var e=[],l=0;31>l;l++)e.push(t);return e}function Le(t,e){t.pendingLanes|=e,e!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function li(t,e,l,a,n,u){var c=t.pendingLanes;t.pendingLanes=l,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=l,t.entangledLanes&=l,t.errorRecoveryDisabledLanes&=l,t.shellSuspendCounter=0;var f=t.entanglements,d=t.expirationTimes,p=t.hiddenUpdates;for(l=c&~l;0<l;){var T=31-Y(l),A=1<<T;f[T]=0,d[T]=-1;var g=p[T];if(g!==null)for(p[T]=null,T=0;T<g.length;T++){var x=g[T];x!==null&&(x.lane&=-536870913)}l&=~A}a!==0&&ke(t,a,0),u!==0&&n===0&&t.tag!==0&&(t.suspendedLanes|=u&~(c&~e))}function ke(t,e,l){t.pendingLanes|=e,t.suspendedLanes&=~e;var a=31-Y(e);t.entangledLanes|=e,t.entanglements[a]=t.entanglements[a]|1073741824|l&261930}function Be(t,e){var l=t.entangledLanes|=e;for(t=t.entanglements;l;){var a=31-Y(l),n=1<<a;n&e|t[a]&e&&(t[a]|=e),l&=~n}}function ai(t,e){var l=e&-e;return l=(l&42)!==0?1:Cc(l),(l&(t.suspendedLanes|e))!==0?0:l}function Cc(t){switch(t){case 2:t=1;break;case 8:t=4;break;case 32:t=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:t=128;break;case 268435456:t=134217728;break;default:t=0}return t}function Ac(t){return t&=-t,2<t?8<t?(t&134217727)!==0?32:268435456:8:2}function or(){var t=at.p;return t!==0?t:(t=window.event,t===void 0?32:ph(t.type))}function dr(t,e){var l=at.p;try{return at.p=t,e()}finally{at.p=l}}var Wl=Math.random().toString(36).slice(2),Te="__reactFiber$"+Wl,Ke="__reactProps$"+Wl,vn="__reactContainer$"+Wl,mr="__reactEvents$"+Wl,Zh="__reactListeners$"+Wl,Lh="__reactHandles$"+Wl,hr="__reactResources$"+Wl,fu="__reactMarker$"+Wl,ni="__reactLoad$"+Wl;function ui(t){delete t[Te],delete t[Ke],delete t[Zh],delete t[Lh]}function Za(t){var e;if(e=t[Te])return e;for(var l=t.parentNode;l;){if(e=l[vn]||l[Te]){if(l=e.alternate,e.child!==null||l!==null&&l.child!==null)for(t=Pm(t);t!==null;){if(l=t[Te])return l;t=Pm(t)}return e}t=l,l=t.parentNode}return null}function yn(t){if(t=t[Te]||t[vn]){var e=t.tag;if(e===5||e===6||e===13||e===31||e===26||e===27||e===3)return t}return null}function ru(t){var e=t.tag;if(e===5||e===26||e===27||e===6)return t.stateNode;throw Error(o(33))}function gn(t){var e=t[hr];return e||(e=t[hr]={hoistableStyles:new Map,hoistableScripts:new Map}),e}function ye(t){t[fu]=!0}function vr(t){t[ni]=void 0}var yr=new Set,gr={};function La(t,e){Sn(t,e),Sn(t+"Capture",e)}function Sn(t,e){forLine truncated
`).replace(R0,"")}function Dm(t,e){return e=Rm(e),Rm(t)===e}function Rt(t,e,l,a,n,u){switch(l){case"children":if(typeof a=="string")e==="body"||e==="textarea"&&a===""||bn(t,a);else if(typeof a=="number"||typeof a=="bigint")e!=="body"&&bn(t,""+a);else return;break;case"className":ci(t,"class",a);break;case"tabIndex":ci(t,"tabindex",a);break;case"dir":case"role":case"viewBox":case"width":case"height":ci(t,l,a);break;case"style":Cr(t,a,u);return;case"data":if(e!=="object"){ci(t,"data",a);break}case"src":case"href":if(a===""&&(e!=="a"||l!=="href")){t.removeAttribute(l);break}if(a==null||typeof a=="function"||typeof a=="symbol"||typeof a=="boolean"){t.removeAttribute(l);break}a=si(a),t.setAttribute(l,a);break;case"action":case"formAction":if(typeof a=="function"){t.setAttribute(l,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof u=="function"&&(l==="formAction"?(e!=="input"&&Rt(t,e,"name",n.name,n,null),Rt(t,e,"formEncType",n.formEncType,n,null),Rt(t,e,"formMethod",n.formMethod,n,null),Rt(t,e,"formTarget",n.formTarget,n,null)):(Rt(t,e,"encType",n.encType,n,null),Rt(t,e,"method",n.method,n,null),Rt(t,e,"target",n.target,n,null)));if(a==null||typeof a=="symbol"||typeof a=="boolean"){t.removeAttribute(l);break}a=si(a),t.setAttribute(l,a);break;case"onClick":a!=null&&(t.onclick=Hl);return;case"onScroll":a!=null&&St("scroll",t);return;case"onScrollEnd":a!=null&&St("scrollend",t);return;case"dangerouslySetInnerHTML":if(a!=null){if(typeof a!="object"||!("__html"in a))throw Error(o(61));if(l=a.__html,l!=null){if(n.children!=null)throw Error(o(60));(u!=null?u.__html:void 0)!==l&&(t.innerHTML=l)}}break;case"multiple":t.multiple=a&&typeof a!="function"&&typeof a!="symbol";break;case"muted":t.muted=a&&typeof a!="function"&&typeof a!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(a==null||typeof a=="function"||typeof a=="boolean"||typeof a=="symbol"){t.removeAttribute("xlink:href");break}l=si(a),t.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",l);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":a!=null&&typeof a!="function"&&typeof a!="symbol"?t.setAttribute(l,a):t.removeAttribute(l);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"credentialless":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":a&&typeof a!="function"&&typeof a!="symbol"?t.setAttribute(l,""):t.removeAttribute(l);break;case"capture":case"download":a===!0?t.setAttribute(l,""):a!==!1&&a!=null&&typeof a!="function"&&typeof a!="symbol"?t.setAttribute(l,a):t.removeAttribute(l);break;case"cols":case"rows":case"size":case"span":a!=null&&typeof a!="function"&&typeof a!="symbol"&&!isNaN(a)&&1<=a?t.setAttribute(l,a):t.removeAttribute(l);break;case"rowSpan":case"start":a==null||typeof a=="function"||typeof a=="symbol"||isNaN(a)?t.removeAttribute(l):t.setAttribute(l,a);break;case"popover":St("beforetoggle",t),St("toggle",t),ii(t,"popover",a);break;case"xlinkActuate":$l(t,"http://www.w3.org/1999/xlink","xlink:actuate",a);break;case"xlinkArcrole":$l(t,"http://www.w3.org/1999/xlink","xlink:arcrole",a);break;case"xlinkRole":$l(t,"http://www.w3.org/1999/xlink","xlink:role",a);break;case"xlinkShow":$l(t,"http://www.w3.org/1999/xlink","xlink:show",a);break;case"xlinkTitle":$l(t,"http://www.w3.org/1999/xlink","xlink:title",a);break;case"xlinkType":$l(t,"http://www.w3.org/1999/xlink","xlink:type",a);break;case"xmlBase":$l(t,"http://www.w3.org/XML/1998/namespace","xml:base",a);break;case"xmlLang":$l(t,"http://www.w3.org/XML/1998/namespace","xml:lang",a);break;case"xmlSpace":$l(t,"http://www.w3.org/XML/1998/namespace","xml:space",a);break;case"is":ii(t,"is",a);break;case"innerText":case"textContent":return;default:if(!(2<l.length)||l[0]!=="o"&&l[0]!=="O"||l[1]!=="n"&&l[1]!=="N")l=$h.get(l)||l,ii(t,l,a);else return}_t=!0}function zf(t,e,l,a,n,u){switch(l){case"style":Cr(t,a,u);return;case"dangerouslySetInnerHTML":if(a!=null){if(typeof a!="object"||!("__html"in a))throw Error(o(61));if(l=a.__html,l!=null){if(n.children!=null)throw Error(o(60));(u!=null?u.__html:void 0)!==l&&(t.innerHTML=l)}}break;case"children":if(typeof a=="string")bn(t,a);else if(typeof a=="number"||typeof a=="bigint")bn(t,""+a);else return;break;case"onScroll":a!=null&&St("scroll",t);return;case"onScrollEnd":a!=null&&St("scroLine truncated
`;Gt&&($t+=Gt+`
`),Mt&&($t+=(Array.isArray(Mt)?Mt.join(`
`):String(Mt))+`
`),$t+=JSON.stringify(W,null,2).slice(0,4e3),Rl($t),xe("已返回登录信息,见下方")}catch(W){xe("发起失败:"+W.message)}},[]),He=y.useCallback(async()=>{const O=k.trim();if(!O){ut("请填要查配额的用户 id");return}try{const V=await lt(et.adminQuotaUser(O));ut("用户 "+O.slice(0,8)+":本月用量 "+cr(V.used_usd_micros)+" / 配额 "+cr(V.quota_usd_micros)+(V.remaining_usd_micros!==null&&V.remaining_usd_micros!==void 0?"(剩余 "+cr(V.remaining_usd_micros)+")":"")+" | 月份 "+(V.month||""))}catch(V){ut(ir(V))}},[k]),me=y.useCallback(async()=>{if(window.confirm("确定手动触发 sweep 吗?超阈值的陈旧 running 任务会被标为 interrupted。"))try{const V=(await lt(et.adminSweep,"POST",{})).swept||[];ut("sweep 完成,清理 "+V.length+" 个任务"+(V.length?":"+V.join("、"):""))}catch(O){ut(ir(O))}},[]),ml=y.useCallback(async()=>{try{const O=await lt(et.adminBackup,"POST",{});ut("备份完成:backend="+O.backend+" | 路径 "+O.path+" | 大小 "+O.size_bytes+" 字节 | 任务 "+O.task_count+" 行")}catch(O){ut(ir(O))}},[]);return{keySaved:i,keyState:m,quotaText:_,healthText:N,adminGate:B,adminBody:G,adminResult:J,adminUserId:k,setAdminUserId:P,projects:it,projectId:Vt,setProjectId:be,projectMsg:Me,githubRepos:oe,workers:ht,workersSummary:te,workersError:de,members:Pe,memberMsg:w,memberError:tt,skills:vt,apps:Oe,mcpServers:v,skillMsg:F,skillError:yt,mcpMsg:xt,mcpOauthMsg:at,mcpOauthBox:Qa,envs:Xe,envCount:Ct,envMsg:Kl,envRepoLine:jl,envRepoBar:kt,envRepoMsg:tl,envSelect:Dl,setEnvSelect:ie,models:Ue,modelCustomVisible:Ul,refreshHealth:da,loadQuota:Jl,probeAdmin:Xa,refreshProjects:ae,loadGithubRepos:iu,loadRelayModels:mn,refreshWorkers:hn,refreshEnvs:Ze,refreshMembers:Va,refreshSkills:ma,saveKeyInput:cu,clearKeyInput:su,createProject:Fl,createEnv:U,archiveEnv:z,saveEnvRepo:Y,inviteMember:$,changeMemberRole:Ut,reloadMcp:ha,mcpOauth:ll,adminQuota:He,adminSweep:me,adminBackup:ml}}function Fy(){const[i,r]=y.useState([]),[m,o]=y.useState(""),[_,M]=y.useState(""),[N,j]=y.useState(""),[B,R]=y.useState(""),G=y.useCallback(async(k,P)=>{const it=k!==void 0?k:P;if(!it){r([]);return}try{const Tt=await lt(et.tasks(it));r(Tt.tasks||[]),o("")}catch(Tt){o("加载失败:"+Tt.message)}},[]),E=y.useMemo(()=>{const k=B.trim().toLowerCase();return i.filter(P=>!(_&&P.status!==_||N&&(P.executor||"")!==N||k&&!((P.id||"")+" "+(P.prompt||"")).toLowerCase().includes(k)))},[i,_,N,B]),J=E.length+" 个任务(共 "+i.length+" 个)",ut=y.useCallback(async(k,P)=>{const it={...k};P.length&&(it.upload_id=await Ly(P));const Tt=await lt(et.tasksCreate,"POST",it);return"任务已建:"+ve(Tt.id)},[]);return{tasks:i,taskError:m,statusFilter:_,setStatusFilter:M,executorFilter:N,setExecutorFilter:j,searchText:B,setSearchText:R,filteredTasks:E,taskCountText:J,refreshTasks:G,dispatch:ut}}function Wy(i,r){const[m,o]=y.useState(null),[_,M]=y.useState(""),[N,j]=y.useState(""),[B,R]=y.useState(""),[G,E]=y.useState(""),[J,ut]=y.useState(!1),[k,P]=y.useState(""),[it,Tt]=y.useState([]),[Vt,be]=y.useState([]),[Me,Ft]=y.useState(""),[oe,st]=y.useState([]),[ht,Wt]=y.useState(""),[te,Lt]=y.useState(""),[de,Dt]=y.useState([]),[Pe,H]=y.useState(""),[w,L]=y.useState(""),[tt,jt]=y.useState(""),[vt,ee]=y.useState(""),[Oe,ue]=y.useState([]),[v,D]=y.useState([]),[F,K]=y.useState(""),[yt,dt]=y.useState([]),[xt,Q]=y.useState(""),[at,xe]=y.useState(""),[Qa,Rl]=y.useState(""),[Xe,Yt]=y.useState(""),[Ct,ze]=y.useState([]),[Kl,le]=y.useState(""),jl=y.useRef(null),De=y.useRef(null),kt=y.useRef(null);kt.current=m;const Ve=y.useCallback(()=>{if(jl.current){try{jl.current.abort()}catch{}jl.current=null}De.current&&(clearTimeout(De.current),De.current=null)},[]),tl=y.useCallback(async U=>{try{const z=await lt(et.taskAgents(U));be(z.agents||[]),Ft(z.note||"")}catch(z){be([]),Ft("子代理链路加载失败:"+z.message)}},[]),ol=y.useCallback(U=>{De.current&&clearTimeout(De.current),De.current=setTimeout(()=>void tl(U),1e3)},[tl]),Dl=y.useCallback(async U=>{if(!U.coordinator_parts){Tt([]);return}try{const z=await lt(et.tasksByParent(U.project_id||"",U.id));Tt(z.tasks||[])}catch{Tt([])}},[]),ie=y.useCallback(async U=>{try{const z=await lt(et.taskFollowups(U));ue(z.followups||[])}catch{ue([])}},[]),Ue=y.useCallback(async U=>{var Y;const z=U||((Y=kt.current)==null?void 0:Y.id);if(z)try{const $=await lt(et.taskReviews(z));st($.reviews||[]),Lt("")}catch($){Lt("评审队列加载失败:"+$.message)}},[]),el=y.useCallback(async U=>{var Y;const z=U||((Y=kt.current)==null?void 0:Y.id);if(z)try{const $=await lt(et.taskApprovals(z));D($.approvals||[])}catch($){K("审批队列加载失败:"+$.message)}},[]),Ul=y.useCallback(async U=>{try{const z=await lt(et.taskMessages(U));dt(z.messages||[])}catch{dt([])}},[]),dl=y.useCallback(async U=>{try{const z=await lt(et.taskThread(U)),Y=z.thread||z;let $=Y.turns;$==null&&($=Y.turn_count),$==null&&($=Y.turns_count),xe("id "+(Y.id||U)+" | 名字 "+(Y.name||Y.thread_name||"")+" | 归档 "+(Y.aLine truncated
`+a.stack}}var Vt=Object.prototype.hasOwnProperty,ll=i.unstable_scheduleCallback,ml=i.unstable_cancelCallback,ql=i.unstable_shouldYield,La=i.unstable_requestPaint,Wt=i.unstable_now,$l=i.unstable_getCurrentPriorityLevel,yn=i.unstable_ImmediatePriority,gn=i.unstable_UserBlockingPriority,Ke=i.unstable_NormalPriority,ka=i.unstable_LowPriority,ya=i.unstable_IdlePriority,fu=i.log,ru=i.unstable_setDisableYieldValue,Il=null,be=null;function hl(t){if(typeof fu=="function"&&ru(t),be&&typeof be.setStrictMode=="function")try{be.setStrictMode(Il,t)}catch{}}var xe=Math.clz32?Math.clz32:Z,H=Math.log,D=Math.LN2;function Z(t){return t>>>=0,t===0?32:31-(H(t)/D|0)|0}var F=256,Nt=262144,_l=4194304;function De(t){var e=t&42;if(e!==0)return e;switch(t&-t){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return t&-t;case 262144:case 524288:case 1048576:case 2097152:return t&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return t&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return t}}function O(t,e,l){var a=t.pendingLanes;if(a===0)return 0;var n=0,u=t.suspendedLanes,c=t.pingedLanes;t=t.warmLanes;var f=a&134217727;return f!==0?(a=f&~u,a!==0?n=De(a):(c&=f,c!==0?n=De(c):l||(l=f&~t,l!==0&&(n=De(l))))):(f=a&~u,f!==0?n=De(f):c!==0?n=De(c):l||(l=a&~t,l!==0&&(n=De(l)))),n===0?0:e!==0&&e!==n&&(e&u)===0&&(u=n&-n,l=e&-e,u>=l||u===32&&(l&4194048)!==0)?e:n}function Y(t,e){return(t.pendingLanes&~(t.suspendedLanes&~t.pingedLanes)&e)===0}function $(t,e){(e&8)!==0&&(e|=e&32);var l=t.entangledLanes;if(l!==0)for(t=t.entanglements,l&=e;0<l;){var a=31-xe(l),n=1<<a;e|=t[a],l&=~n}return e}function Ut(t,e){switch(t){case 1:case 2:case 4:case 8:case 64:return e+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Zt(){var t=_l;return _l<<=1,(_l&62914560)===0&&(_l=4194304),t}function je(t){for(var e=[],l=0;31>l;l++)e.push(t);return e}function Ue(t,e){t.pendingLanes|=e,e!==268435456&&(t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0)}function ga(t,e,l,a,n,u){var c=t.pendingLanes;t.pendingLanes=l,t.suspendedLanes=0,t.pingedLanes=0,t.warmLanes=0,t.expiredLanes&=l,t.entangledLanes&=l,t.errorRecoveryDisabledLanes&=l,t.shellSuspendCounter=0;var f=t.entanglements,d=t.expirationTimes,p=t.hiddenUpdates;for(l=c&~l;0<l;){var T=31-xe(l),A=1<<T;f[T]=0,d[T]=-1;var g=p[T];if(g!==null)for(p[T]=null,T=0;T<g.length;T++){var x=g[T];x!==null&&(x.lane&=-536870913)}l&=~A}a!==0&&Pl(t,a,0),u!==0&&n===0&&t.tag!==0&&(t.suspendedLanes|=u&~(c&~e))}function Pl(t,e,l){t.pendingLanes|=e,t.suspendedLanes&=~e;var a=31-xe(e);t.entangledLanes|=e,t.entanglements[a]=t.entanglements[a]|1073741824|l&261930}function ui(t,e){var l=t.entangledLanes|=e;for(t=t.entanglements;l;){var a=31-xe(l),n=1<<a;n&e|t[a]&e&&(t[a]|=e),l&=~n}}function ou(t,e){var l=e&-e;return l=(l&42)!==0?1:Ye(l),(l&(t.suspendedLanes|e))!==0?0:l}function Ye(t){switch(t){case 2:t=1;break;case 8:t=4;break;case 32:t=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:t=128;break;case 268435456:t=134217728;break;default:t=0}return t}function He(t){return t&=-t,2<t?8<t?(t&134217727)!==0?32:268435456:8:2}function ii(){var t=at.p;return t!==0?t:(t=window.event,t===void 0?32:ph(t.type))}function dr(t,e){var l=at.p;try{return at.p=t,e()}finally{at.p=l}}var ta=Math.random().toString(36).slice(2),Te="__reactFiber$"+ta,Je="__reactProps$"+ta,Sn="__reactContainer$"+ta,mr="__reactEvents$"+ta,Zh="__reactListeners$"+ta,Lh="__reactHandles$"+ta,hr="__reactResources$"+ta,du="__reactMarker$"+ta,ci="__reactLoad$"+ta;function si(t){delete t[Te],delete t[Je],delete t[Zh],delete t[Lh]}function Ka(t){var e;if(e=t[Te])return e;for(var l=t.parentNode;l;){if(e=l[Sn]||l[Te]){if(l=e.alternate,e.child!==null||l!==null&&l.child!==null)for(t=Pm(t);t!==null;){if(l=t[Te])return l;t=Pm(t)}return e}t=l,l=t.parentNode}return null}function pn(t){if(t=t[Te]||t[Sn]){var e=t.tag;if(e===5||e===6||e===13||e===31||e===26||e===27||e===3)return t}return null}function mu(t){var e=t.tag;if(e===5||e===26||e===27||e===6)return t.stateNode;throw Error(o(33))}function bn(t){var e=t[hr];return e||(e=t[hr]={hoistableStyles:new Map,hoistableScripts:new Map}),e}function me(t){t[du]=!0}function vr(t){t[ci]=void 0}var yr=new Set,gr={};function Ja(t,e){xn(t,e),xn(t+"Capture",e)}function xn(t,eLine truncated
`).replace(R0,"")}function Dm(t,e){return e=Rm(e),Rm(t)===e}function Rt(t,e,l,a,n,u){switch(l){case"children":if(typeof a=="string")e==="body"||e==="textarea"&&a===""||Tn(t,a);else if(typeof a=="number"||typeof a=="bigint")e!=="body"&&Tn(t,""+a);else return;break;case"className":ri(t,"class",a);break;case"tabIndex":ri(t,"tabindex",a);break;case"dir":case"role":case"viewBox":case"width":case"height":ri(t,l,a);break;case"style":Cr(t,a,u);return;case"data":if(e!=="object"){ri(t,"data",a);break}case"src":case"href":if(a===""&&(e!=="a"||l!=="href")){t.removeAttribute(l);break}if(a==null||typeof a=="function"||typeof a=="symbol"||typeof a=="boolean"){t.removeAttribute(l);break}a=oi(a),t.setAttribute(l,a);break;case"action":case"formAction":if(typeof a=="function"){t.setAttribute(l,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof u=="function"&&(l==="formAction"?(e!=="input"&&Rt(t,e,"name",n.name,n,null),Rt(t,e,"formEncType",n.formEncType,n,null),Rt(t,e,"formMethod",n.formMethod,n,null),Rt(t,e,"formTarget",n.formTarget,n,null)):(Rt(t,e,"encType",n.encType,n,null),Rt(t,e,"method",n.method,n,null),Rt(t,e,"target",n.target,n,null)));if(a==null||typeof a=="symbol"||typeof a=="boolean"){t.removeAttribute(l);break}a=oi(a),t.setAttribute(l,a);break;case"onClick":a!=null&&(t.onclick=wl);return;case"onScroll":a!=null&&St("scroll",t);return;case"onScrollEnd":a!=null&&St("scrollend",t);return;case"dangerouslySetInnerHTML":if(a!=null){if(typeof a!="object"||!("__html"in a))throw Error(o(61));if(l=a.__html,l!=null){if(n.children!=null)throw Error(o(60));(u!=null?u.__html:void 0)!==l&&(t.innerHTML=l)}}break;case"multiple":t.multiple=a&&typeof a!="function"&&typeof a!="symbol";break;case"muted":t.muted=a&&typeof a!="function"&&typeof a!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(a==null||typeof a=="function"||typeof a=="boolean"||typeof a=="symbol"){t.removeAttribute("xlink:href");break}l=oi(a),t.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",l);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":a!=null&&typeof a!="function"&&typeof a!="symbol"?t.setAttribute(l,a):t.removeAttribute(l);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"credentialless":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":a&&typeof a!="function"&&typeof a!="symbol"?t.setAttribute(l,""):t.removeAttribute(l);break;case"capture":case"download":a===!0?t.setAttribute(l,""):a!==!1&&a!=null&&typeof a!="function"&&typeof a!="symbol"?t.setAttribute(l,a):t.removeAttribute(l);break;case"cols":case"rows":case"size":case"span":a!=null&&typeof a!="function"&&typeof a!="symbol"&&!isNaN(a)&&1<=a?t.setAttribute(l,a):t.removeAttribute(l);break;case"rowSpan":case"start":a==null||typeof a=="function"||typeof a=="symbol"||isNaN(a)?t.removeAttribute(l):t.setAttribute(l,a);break;case"popover":St("beforetoggle",t),St("toggle",t),fi(t,"popover",a);break;case"xlinkActuate":ea(t,"http://www.w3.org/1999/xlink","xlink:actuate",a);break;case"xlinkArcrole":ea(t,"http://www.w3.org/1999/xlink","xlink:arcrole",a);break;case"xlinkRole":ea(t,"http://www.w3.org/1999/xlink","xlink:role",a);break;case"xlinkShow":ea(t,"http://www.w3.org/1999/xlink","xlink:show",a);break;case"xlinkTitle":ea(t,"http://www.w3.org/1999/xlink","xlink:title",a);break;case"xlinkType":ea(t,"http://www.w3.org/1999/xlink","xlink:type",a);break;case"xmlBase":ea(t,"http://www.w3.org/XML/1998/namespace","xml:base",a);break;case"xmlLang":ea(t,"http://www.w3.org/XML/1998/namespace","xml:lang",a);break;case"xmlSpace":ea(t,"http://www.w3.org/XML/1998/namespace","xml:space",a);break;case"is":fi(t,"is",a);break;case"innerText":case"textContent":return;default:if(!(2<l.length)||l[0]!=="o"&&l[0]!=="O"||l[1]!=="n"&&l[1]!=="N")l=$h.get(l)||l,fi(t,l,a);else return}_t=!0}function Rf(t,e,l,a,n,u){switch(l){case"style":Cr(t,a,u);return;case"dangerouslySetInnerHTML":if(a!=null){if(typeof a!="object"||!("__html"in a))throw Error(o(61));if(l=a.__html,l!=null){if(n.children!=null)throw Error(o(60));(u!=null?u.__html:void 0)!==l&&(t.innerHTML=l)}}break;case"children":if(typeof a=="string")Tn(t,a);else if(typeof a=="number"||typeof a=="bigint")Tn(t,""+a);else return;break;case"onScroll":a!=null&&St("scroll",t);return;case"onScrollEnd":a!=null&&St("scroLine truncated
`;Ut&&(je+=Ut+`
`),Zt&&(je+=(Array.isArray(Zt)?Zt.join(`
`):String(Zt))+`
`),je+=JSON.stringify($,null,2).slice(0,4e3),Ul(je),Se("已返回登录信息,见下方")}catch($){Se("发起失败:"+$.message)}},[]),Nt=y.useCallback(async()=>{const O=K.trim();if(!O){ut("请填要查配额的用户 id");return}try{const Y=await lt(P.adminQuotaUser(O));ut("用户 "+O.slice(0,8)+":本月用量 "+sr(Y.used_usd_micros)+" / 配额 "+sr(Y.quota_usd_micros)+(Y.remaining_usd_micros!==null&&Y.remaining_usd_micros!==void 0?"(剩余 "+sr(Y.remaining_usd_micros)+")":"")+" | 月份 "+(Y.month||""))}catch(Y){ut(cr(Y))}},[K]),_l=y.useCallback(async()=>{if(window.confirm("确定手动触发 sweep 吗?超阈值的陈旧 running 任务会被标为 interrupted。"))try{const Y=(await lt(P.adminSweep,"POST",{})).swept||[];ut("sweep 完成,清理 "+Y.length+" 个任务"+(Y.length?":"+Y.join("、"):""))}catch(O){ut(cr(O))}},[]),De=y.useCallback(async()=>{try{const O=await lt(P.adminBackup,"POST",{});ut("备份完成:backend="+O.backend+" | 路径 "+O.path+" | 大小 "+O.size_bytes+" 字节 | 任务 "+O.task_count+" 行")}catch(O){ut(cr(O))}},[]);return{keySaved:i,keyState:m,quotaText:_,healthText:N,adminGate:B,adminBody:G,adminResult:V,adminUserId:K,setAdminUserId:tt,projects:it,projectId:Xt,setProjectId:ge,projectMsg:Oe,githubRepos:fe,workers:ht,workersSummary:Pt,workersError:re,members:tl,memberMsg:w,memberError:et,skills:vt,apps:ze,mcpServers:v,skillMsg:W,skillError:yt,mcpMsg:xt,mcpOauthMsg:at,mcpOauthBox:Za,envs:Le,envCount:At,envMsg:Wl,envRepoLine:va,envRepoBar:el,envRepoMsg:le,envSelect:ol,setEnvSelect:pe,models:Hl,modelCustomVisible:Bl,refreshHealth:ml,loadQuota:ql,probeAdmin:La,refreshProjects:Wt,loadGithubRepos:$l,loadRelayModels:yn,refreshWorkers:gn,refreshEnvs:Ke,refreshMembers:ka,refreshSkills:ya,saveKeyInput:fu,clearKeyInput:ru,createProject:Il,createEnv:be,archiveEnv:hl,saveEnvRepo:xe,inviteMember:H,changeMemberRole:D,reloadMcp:Z,mcpOauth:F,adminQuota:Nt,adminSweep:_l,adminBackup:De}}function Fy(){const[i,r]=y.useState([]),[m,o]=y.useState(""),[_,M]=y.useState(""),[N,E]=y.useState(""),[B,z]=y.useState(""),G=y.useCallback(async(K,tt)=>{const it=K!==void 0?K:tt;if(!it){r([]);return}try{const jt=await lt(P.tasks(it));r(jt.tasks||[]),o("")}catch(jt){o("加载失败:"+jt.message)}},[]),j=y.useMemo(()=>{const K=B.trim().toLowerCase();return i.filter(tt=>!(_&&tt.status!==_||N&&(tt.executor||"")!==N||K&&!((tt.id||"")+" "+(tt.prompt||"")).toLowerCase().includes(K)))},[i,_,N,B]),V=j.length+" 个任务(共 "+i.length+" 个)",ut=y.useCallback(async(K,tt)=>{const it={...K};tt.length&&(it.upload_id=await Ly(tt));const jt=await lt(P.tasksCreate,"POST",it);return"任务已建:"+de(jt.id)},[]);return{tasks:i,taskError:m,statusFilter:_,setStatusFilter:M,executorFilter:N,setExecutorFilter:E,searchText:B,setSearchText:z,filteredTasks:j,taskCountText:V,refreshTasks:G,dispatch:ut}}function Wy(i,r){const[m,o]=y.useState(null),[_,M]=y.useState(""),[N,E]=y.useState(""),[B,z]=y.useState(""),[G,j]=y.useState(""),[V,ut]=y.useState(!1),[K,tt]=y.useState(""),[it,jt]=y.useState([]),[Xt,ge]=y.useState([]),[Oe,Jt]=y.useState(""),[fe,st]=y.useState([]),[ht,Ft]=y.useState(""),[Pt,kt]=y.useState(""),[re,Dt]=y.useState([]),[tl,U]=y.useState(""),[w,k]=y.useState(""),[et,Tt]=y.useState(""),[vt,te]=y.useState(""),[ze,ne]=y.useState([]),[v,R]=y.useState([]),[W,J]=y.useState(""),[yt,dt]=y.useState([]),[xt,Q]=y.useState(""),[at,Se]=y.useState(""),[Za,Ul]=y.useState(""),[Le,Yt]=y.useState(""),[At,Re]=y.useState([]),[Wl,ee]=y.useState(""),[va,El]=y.useState(null),el=y.useRef(null),ke=y.useRef(null),le=y.useRef(null);le.current=m;const qe=y.useCallback(()=>{if(el.current){try{el.current.abort()}catch{}el.current=null}ke.current&&(clearTimeout(ke.current),ke.current=null)},[]),ol=y.useCallback(async H=>{try{const D=await lt(P.taskAgents(H));ge(D.agents||[]),Jt(D.note||"")}catch(D){ge([]),Jt("子代理链路加载失败:"+D.message)}},[]),pe=y.useCallback(H=>{ke.current&&clearTimeout(ke.current),ke.current=setTimeout(()=>void ol(H),1e3)},[ol]),Hl=y.useCallback(async H=>{if(!H.coordinator_parts){jt([]);return}try{const D=await lt(P.tasksByParent(H.project_id||"",H.id));jt(D.tasks||[])}catch{jt([])}},[]),dl=y.useCallback(async H=>{try{const D=await lt(P.taskFollowups(H));ne(D.followups||[])}catch{ne([])}},[]),Bl=y.useCallback(async H=>{try{const D=await lt(P.taskChatHistory(H));El(D)}catch{El(null)}},[]),we=y.useCallback(async H=>{var Z;const D=H||((Z=le.current)==null?void 0:Z.id);if(D)try{const F=await lt(P.taskReviews(D));st(F.reviews||[]),kt("")}catch(F){kt("评审队列加载失败:"+F.message)}},[]),Vt=y.useCallback(async H=>{var Z;const D=H||((Z=le.current)==null?void 0:Z.id);if(D)try{const F=await lt(P.taskApprovals(D));R(F.approvals||[])}catch(F){J("审批队列加载失败:"+F.message)}},[]),ll=y.useCallback(async H=>{try{const D=await lt(P.taskMessages(H));dt(D.messages||[])}catch{dt([])}},[]),ml=y.useCallback(async H=>{try{const D=await lt(P.taskThread(H)),Z=D.thread||D;let F=Z.turns;F==null&Line truncated
`);Gt=$t.pop()||"";for(const Le of $t){const li=Le.split(`
`).filter(ke=>ke.indexOf("data: ")===0).map(ke=>ke.slice(6)).join(`
`);if(li)try{const ke=JSON.parse(li);if(ke.type==="snapshot")$=ke.entries||[],Object.keys(Ut).forEach(Be=>delete Ut[Be]),$.forEach((Be,ai)=>{Be.id&&(Ut[Be.id]=ai)}),ha=ke.note||"",Dt([...$]),H(ha),ol(U);else if(ke.type==="entry"){const Be=ke.entry;Be.id&&Be.id in Ut?$[Ut[Be.id]]=Be:(Be.id&&(Ut[Be.id]=$.length),$.push(Be)),Dt([...$]),ol(U)}else ke.type==="done"&&(ll=!0,He=ke.status,L(He))}catch{}}if(ll)break}}catch(me){if(me&&me.name==="AbortError")return;H("链路流中断:"+me.message);return}ll&&r()},[Ve,ol,r]),Jl=y.useCallback(async U=>{Ve();try{const z=await lt(et.task(U));o(z);let Y="";z.result_text&&(Y+=`=== result ===
`+z.result_text.slice(0,8e3)+`
`),z.error_text&&(Y+=`=== error ===
`+z.error_text.slice(0,8e3)+`
`),Y||(Y="(暂无 result/error 输出)"),M(Y),E(""),ut(!1),P(""),Q(""),Yt(""),oa(z.status);const $=i(z.project_id),Ut=$==="maintainer"||$==="owner",ha=$==="contributor"||Ut,ll=nu.includes(z.status),He=[];["failed","interrupted","merge-failed"].includes(z.status)&&Ut||He.push("重试:不可用——仅 failed/interrupted/merge-failed 可重试"+(Ut?"":",且仅 maintainer 可重试")),!ll&&ha||He.push("取消:不可用——"+(ll?"终态任务不可取消":"仅项目成员(contributor 及以上)可取消")),z.status==="completed"&&Ut||He.push("合并:不可用——仅 completed 可合并"+(Ut?"":",且仅 maintainer 可合并")),z.status==="merged"&&Ut||He.push("提 PR:不可用——仅 merged 可提 PR"+(Ut?"":",且仅 maintainer 可提 PR")),R(He.join(";")),j(""),da(U,z.status),tl(U),Dl(z),ie(U),Ul(U),dl(U),Kt(U),Ue(U),el(U)}catch(z){o(null),M("加载失败:"+z.message)}},[Ve,da,tl,Dl,ie,Ul,dl,Kt,Ue,el,i]),Xa=y.useCallback(async()=>{const U=kt.current;if(U){try{const z=await lt(et.task(U.id));o(z);let Y="";z.result_text&&(Y+=`=== result ===
`+z.result_text.slice(0,8e3)+`
`),z.error_text&&(Y+=`=== error ===
`+z.error_text.slice(0,8e3)+`
`),M(Y||"(暂无 result/error 输出)")}catch{}ie(U.id),Ue(U.id),el(U.id),Dl(U),tl(U.id),Ul(U.id),dl(U.id),Kt(U.id)}},[ie,Ue,el,Dl,tl,Ul,dl,Kt]),ae=y.useCallback(async U=>{const z=kt.current;if(!z)return;const Y={cancel:"确定取消该任务吗?",merge:"确定把 completed 任务标为 merged 吗?",pr:"确定为该任务提 PR 吗?(会在仓库真实建 PR)"};if(!(Y[U]&&!window.confirm(Y[U])))try{const $=await lt(et.taskAction(z.id,U),"POST",{});j(U+" 已提交:"+JSON.stringify($).slice(0,300)),r(),await Jl(z.id)}catch($){j(U+" 失败:"+$.message)}},[Jl,r]),iu=y.useCallback(async()=>{const U=kt.current;if(U)try{const z=await lt(et.taskCost(U.id));E(JSON.stringify(z,null,2).slice(0,4e3))}catch(z){E("查成本失败:"+z.message)}},[]),mn=y.useCallback(async U=>{const z=kt.current;if(z){if(!U.trim()){P("分支消息必填");return}if(!nu.includes(z.status)){P("仅终态任务可分支");return}try{const Y=await lt(et.taskFork(z.id),"POST",{message:U.trim()});P("分支任务已建:"+Y.id.slice(0,8)),r()}catch(Y){P("分支失败:"+Y.message)}}},[r]),hn=y.useCallback(async U=>{const z=kt.current;if(z)try{await lt(et.taskReviews(z.id),"POST",U),Wt("评审已登记(requested)"),await Ue(z.id)}catch(Y){Wt("登记评审需求失败:"+Y.message)}},[Ue]),Ze=y.useCallback(U=>{const z=kt.current;if(!z)return;const Y=window.prompt("输入该条评审的 findings(将写回记录并标为 completed):");Y!==null&&lt(et.taskReviewFindings(z.id,U),"POST",{findings:Y}).then(()=>void Ue(z.id)).catch($=>Wt("写 findings 失败:"+$.message))},[Ue]),Va=y.useCallback((U,z)=>{const Y=kt.current;if(!Y)return;const $=z==="approved"?"approve":"deny";lt(et.taskApprovalDecision(Y.id,U,$),"POST",{}).then(()=>{K("已记录"+(z==="approved"?"允许":"拒绝")+"(当前版本只留回执,不拦截实时行为)"),el(Y.id)}).catch(Ut=>K("决定失败:"+Ut.message))},[el]),ma=y.useCallback(async(U,z,Y)=>{const $=kt.current;if($){if(!z.trim()){Q("目标任务 id 不能为空");return}if(!Y.trim()){Q("消息正文不能为空");return}try{const Ut=await lt(et.taskMessagesTo(z.trim()),"POST",{from_task_id:$.id,body:Y.trim(),mode:U});Q("已发送("+Ut.delivery+")"),await Ul($.id)}catch(Ut){Q("发送失败:"+Ut.message)}}},[Ul]),cu=y.useCallback((U,z)=>{const Y=kt.current;if(!Y)return;if(!["completed","failed","interrupted"].includes(Y.status)){Yt("仅 completed/failed/interrupted 任务可操作线程(merged/merge-failed 不可改,服务端同样 409)");return}const $={};if(U==="rename"){if(!z.trim()){Yt("新线程名不能为空");return}$.name=z.trim()}lt(et.taskThreadAction(Y.id,U),"POST",$).then(Ut=>{Yt(U+" 已提交:"+JSON.stringify(Ut).slice(0,300)),dl(Y.id),Kt(Y.id)}).catch(Ut=>Yt(U+" 失败:"+Ut.message))},[dl,Kt]),su=y.useCallback(async U=>{const z=kt.current;if(!z)return;const Y=U.trim();if(!Y){le("消息不能为空");return}try{nu.includes(z.status)&&z.status!=="merged"?(await lt(et.taskContinue(z.id),"POST",{message:Y}),le("已重新排队,同一会话继续"),r(),await Jl(z.id)):["queued","running"].includes(z.status)?(await lt(et.taskFollowups(z.id),"POST",{body:Y,mode:"direct"}),le("已直接发送"),await ie(z.id)):le("merged/merge-failed 任务不可继续,也不可发后续消息")}catch($){le("发送失败:"+$.message)}},[Jl,r,ie]),Fl=y.useCallback(async()=>{const U=kt.current;if(!U){ee("先选一个任务");return}try{const z=await lt(et.taskTrace(U.id)),Y=new Blob([JSON.stringify({task_id:U.id,status:z.status,note:z.note||"",count:(z.entries||[]).length,entries:z.entries||[]},null,2)],{type:"application/json"}),$=document.createElement("a");$.href=URL.createObjectURL(Y),$.download="trace-"+U.id.slice(0,8)+".json",document.body.appendChild($),$.click(),$.remove(),setTimeout(()=>URL.revokeObjectURL($.href),1e3),ee("已下载完整链路 "+(z.entries||[]).length+" 条")}catch(z){ee("下载失败:"+z.message)}},[]);return y.useEffect(()=>()=>Ve(),[Ve]),{currentTask:m,detailBody:_,actionMsg:N,actionReasons:B,costBox:G,forkVisible:J,setForkVisible:ut,forkMsg:k,coordChildren:it,agents:Vt,agentsNote:Me,reviews:oe,reviewMsg:ht,reviewError:te,traceEntries:de,traceNote:Pe,traceDone:w,traceKind:tt,setTraceKind:jt,traceMsg:vt,followups:Oe,approvals:v,approvalMsg:F,peerMessages:yt,peerMsg:xt,threadText:at,threadGateNote:Qa,threadMsg:Xe,threadList:Ct,chatMsg:Kl,showDetail:Jl,refreshDetailLists:Xa,doAction:ae,taskCost:iu,forkTask:mn,postReview:hn,submitFindings:Ze,decideApproval:Va,postPeer:ma,threadAction:cu,sendChatMessage:su,downloadTrace:Fl,refreshReviews:()=>void Ue(),refreshApprovals:()=>void el()}}function $y(i){const[r,m]=y.useState([]),[o,_]=y.useState(""),[M,N]=y.useState(null),[j,B]=y.useState(""),R=y.useCallback(async ut=>{const k=ut!==void 0?ut:i();if(!k){m([]);return}try{const P=await lt(et.projectAutomations(k));m(P.automations||[])}catch(P){_("自动化加载失败:"+P.message)}},[i]),G=y.useCallback(async(ut,k,P,it)=>{const Tt=i();if(!Tt){_("先选项目"Line truncated
`);Ue=Pl.pop()||"";for(const ui of Pl){const ou=ui.split(`
`).filter(Ye=>Ye.indexOf("data: ")===0).map(Ye=>Ye.slice(6)).join(`
`);if(ou)try{const Ye=JSON.parse(ou);if(Ye.type==="snapshot")F=Ye.entries||[],Object.keys(Nt).forEach(He=>delete Nt[He]),F.forEach((He,ii)=>{He.id&&(Nt[He.id]=ii)}),_l=Ye.note||"",Dt([...F]),U(_l),pe(H);else if(Ye.type==="entry"){const He=Ye.entry;He.id&&He.id in Nt?F[Nt[He.id]]=He:(He.id&&(Nt[He.id]=F.length),F.push(He)),Dt([...F]),pe(H)}else Ye.type==="done"&&(De=!0,O=Ye.status,k(O))}catch{}}if(De)break}}catch(Y){if(Y&&Y.name==="AbortError")return;U("链路流中断:"+Y.message);return}De&&r()},[qe,pe,r]),$l=y.useCallback(async H=>{qe();try{const D=await lt(P.task(H));o(D);let Z="";D.result_text&&(Z+=`=== result ===
`+D.result_text.slice(0,8e3)+`
`),D.error_text&&(Z+=`=== error ===
`+D.error_text.slice(0,8e3)+`
`),Z||(Z="(暂无 result/error 输出)"),M(Z),j(""),ut(!1),tt(""),Q(""),Yt(""),La(D.status);const F=i(D.project_id),Nt=F==="maintainer"||F==="owner",_l=F==="contributor"||Nt,De=cu.includes(D.status),O=[];["failed","interrupted","merge-failed"].includes(D.status)&&Nt||O.push("重试:不可用——仅 failed/interrupted/merge-failed 可重试"+(Nt?"":",且仅 maintainer 可重试")),!De&&_l||O.push("取消:不可用——"+(De?"终态任务不可取消":"仅项目成员(contributor 及以上)可取消")),D.status==="completed"&&Nt||O.push("合并:不可用——仅 completed 可合并"+(Nt?"":",且仅 maintainer 可合并")),D.status==="merged"&&Nt||O.push("提 PR:不可用——仅 merged 可提 PR"+(Nt?"":",且仅 maintainer 可提 PR")),z(O.join(";")),E(""),Wt(H,D.status),ol(H),Hl(D),Bl(H),dl(H),ll(H),ml(H),ql(H),we(H),Vt(H)}catch(D){o(null),M("加载失败:"+D.message)}},[qe,Wt,ol,Hl,Bl,dl,ll,ml,ql,we,Vt,i]),yn=y.useCallback(async()=>{const H=le.current;if(H){try{const D=await lt(P.task(H.id));o(D);let Z="";D.result_text&&(Z+=`=== result ===
`+D.result_text.slice(0,8e3)+`
`),D.error_text&&(Z+=`=== error ===
`+D.error_text.slice(0,8e3)+`
`),M(Z||"(暂无 result/error 输出)")}catch{}dl(H.id),Bl(H.id),we(H.id),Vt(H.id),Hl(H),ol(H.id),ll(H.id),ml(H.id),ql(H.id)}},[dl,Bl,we,Vt,Hl,ol,ll,ml,ql]),gn=y.useCallback(async H=>{const D=le.current;if(!D)return;const Z={cancel:"确定取消该任务吗?",merge:"确定把 completed 任务标为 merged 吗?",pr:"确定为该任务提 PR 吗?(会在仓库真实建 PR)"};if(!(Z[H]&&!window.confirm(Z[H])))try{const F=await lt(P.taskAction(D.id,H),"POST",{});E(H+" 已提交:"+JSON.stringify(F).slice(0,300)),r(),await $l(D.id)}catch(F){E(H+" 失败:"+F.message)}},[$l,r]),Ke=y.useCallback(async()=>{const H=le.current;if(H)try{const D=await lt(P.taskCost(H.id));j(JSON.stringify(D,null,2).slice(0,4e3))}catch(D){j("查成本失败:"+D.message)}},[]),ka=y.useCallback(async H=>{const D=le.current;if(D){if(!H.trim()){tt("分支消息必填");return}if(!cu.includes(D.status)){tt("仅终态任务可分支");return}try{const Z=await lt(P.taskFork(D.id),"POST",{message:H.trim()});tt("分支任务已建:"+Z.id.slice(0,8)),r()}catch(Z){tt("分支失败:"+Z.message)}}},[r]),ya=y.useCallback(async H=>{const D=le.current;if(D)try{await lt(P.taskReviews(D.id),"POST",H),Ft("评审已登记(requested)"),await we(D.id)}catch(Z){Ft("登记评审需求失败:"+Z.message)}},[we]),fu=y.useCallback(H=>{const D=le.current;if(!D)return;const Z=window.prompt("输入该条评审的 findings(将写回记录并标为 completed):");Z!==null&&lt(P.taskReviewFindings(D.id,H),"POST",{findings:Z}).then(()=>void we(D.id)).catch(F=>Ft("写 findings 失败:"+F.message))},[we]),ru=y.useCallback((H,D)=>{const Z=le.current;if(!Z)return;const F=D==="approved"?"approve":"deny";lt(P.taskApprovalDecision(Z.id,H,F),"POST",{}).then(()=>{J("已记录"+(D==="approved"?"允许":"拒绝")+"(当前版本只留回执,不拦截实时行为)"),Vt(Z.id)}).catch(Nt=>J("决定失败:"+Nt.message))},[Vt]),Il=y.useCallback(async(H,D,Z)=>{const F=le.current;if(F){if(!D.trim()){Q("目标任务 id 不能为空");return}if(!Z.trim()){Q("消息正文不能为空");return}try{const Nt=await lt(P.taskMessagesTo(D.trim()),"POST",{from_task_id:F.id,body:Z.trim(),mode:H});Q("已发送("+Nt.delivery+")"),await ll(F.id)}catch(Nt){Q("发送失败:"+Nt.message)}}},[ll]),be=y.useCallback((H,D)=>{const Z=le.current;if(!Z)return;if(!["completed","failed","interrupted"].includes(Z.status)){Yt("仅 completed/failed/interrupted 任务可操作线程(merged/merge-failed 不可改,服务端同样 409)");return}const F={};if(H==="rename"){if(!D.trim()){Yt("新线程名不能为空");return}F.name=D.trim()}lt(P.taskThreadAction(Z.id,H),"POST",F).then(Nt=>{Yt(H+" 已提交:"+JSON.stringify(Nt).slice(0,300)),ml(Z.id),ql(Z.id)}).catch(Nt=>Yt(H+" 失败:"+Nt.message))},[ml,ql]),hl=y.useCallback(async H=>{const D=le.current;if(!D)return;const Z=H.trim();if(!Z){ee("消息不能为空");return}try{cu.includes(D.status)&&D.status!=="merged"?(await lt(P.taskContinue(D.id),"POST",{message:Z}),ee("已重新排队,同一会话继续"),r(),await $l(D.id)):["queued","running"].includes(D.status)?(await lt(P.taskFollowups(D.id),"POST",{body:Z,mode:"direct"}),ee("已直接发送"),await dl(D.id),await Bl(D.id)):ee("merged/merge-failed 任务不可继续,也不可发后续消息")}catch(F){ee("发送失败:"+F.message)}},[$l,r,dl,Bl]),xe=y.useCallback(async()=>{const H=le.current;if(!H){te("先选一个任务");return}try{const D=await lt(P.taskTrace(H.id)),Z=new Blob([JSON.stringify({task_id:H.id,status:D.status,note:D.note||"",count:(D.entries||[]).length,entries:D.entries||[]},null,2)],{type:"application/json"}),F=document.createElement("a");F.href=URL.createObjectURL(Z),F.download="trace-"+H.id.slice(0,8)+".json",document.body.appendChild(F),F.click(),F.remove(),setTimeout(()=>URL.revokeObjectURL(F.href),1e3),te("已下载完整链路 "+(D.entries||[]).length+" 条")}catch(D){te("下载失败:"+D.message)}},[]);return y.useEffect(()=>()=>qe(),[qe]),{currentTask:m,detailBody:_,actionMsg:N,actionReasons:B,costBox:G,forkVisible:V,setForkVisible:ut,forkMsg:K,coordChildren:it,agents:Xt,agentsNote:Oe,reviews:fe,reviewMsg:ht,reviewError:Pt,traceEntries:re,traceNote:tl,traceDone:w,traceKind:et,setTraceKind:Tt,traceMsg:vt,followups:ze,approvals:v,approvalMsg:W,peerMessages:yt,peerMsg:xt,threadText:at,threadGateNote:Za,threadMsg:Le,threadList:At,chatMsg:Wl,chatHistory:va,showDetail:$l,refreshDetailLists:yn,doAction:gn,taskCost:Ke,forkTask:ka,postReview:ya,submitFindings:fu,decideApproval:ru,postPeer:Il,threadAction:be,sendChatMessage:hl,downloadTrace:xe,refreshReviews:()=>void we(),refreshApprovals:()=>void Vt()}}function $y(i){const[r,m]=y.useState([]),[o,_]=y.useState(""),[M,N]=y.useState(null),[E,B]=y.useState(""),z=y.useCallback(async ut=>{const K=ut!==void 0?ut:i();if(!K){m([]);return}try{const tt=await lt(P.projectAutomations(K));m(tt.automations||[])}catch(tt){_("自动化加载失败:"+tt.message)}},[i]),G=y.useCallback(async(ut,K,tt,iLine truncated
<style rel="stylesheet" crossorigin>:root{--text:#1d2129;--muted:#667085;--line:#e4e7ec;--line-strong:#d0d5dd;--paper:#ffffff;--card:#ffffff;--bubble-assistant:#f2f2f2;--bubble-user:#bfe0ff;--accent:#3b82f6;--accent-dark:#2563eb;--accent-soft:#eef4ff;--green:#22c55e;--success:#16a34a;--danger:#b42318;--danger-soft:#fef3f2;--amber:#b54708;--purple:#6941c6;--radius:12px;--radius-sm:10px;--shadow:none;--sans:-apple-system,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;--mono:"IBM Plex Mono",Menlo,Consolas,monospace;--ctl:40px}*{box-sizing:border-box}html,body,#root{height:100%}body{font-family:var(--sans);margin:0;color:var(--text);background:#fff;line-height:1.6;font-size:14px}a{color:var(--accent);text-underline-offset:3px}:focus-visible{outline:3px solid var(--accent);outline-offset:2px}@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;transition:none!important}}.muted{color:var(--muted);font-size:13px;line-height:1.6}.err{color:var(--danger)}.ok{color:var(--success)}button{cursor:pointer;font-weight:600;border-radius:999px}button:disabled{opacity:.45;cursor:not-allowed;box-shadow:none}button.primary{background:var(--accent);border-color:var(--accent);color:#fff}button.primary:hover{background:var(--accent-dark)}button.seal{background:var(--accent);border-color:var(--accent);color:#fff}button.seal:hover{background:var(--accent-dark)}button.danger{color:var(--danger);border-color:#fecdca;background:var(--danger-soft)}button.danger:hover{background:#fee4e2}.icon-btn{width:32px;height:32px;padding:0;display:grid;place-items:center;border:1px solid var(--line);border-radius:50%;background:#fff;color:var(--text);font-size:16px;line-height:1}.icon-btn:hover{background:#f9fafb}.muse-app{display:grid;grid-template-columns:56px 260px minmax(0,1fr) 300px;height:100vh;background:#fff}.muse-app.no-right{grid-template-columns:56px 260px minmax(0,1fr)}.icon-rail{border-right:1px solid var(--line);display:flex;flex-direction:column;align-items:center;gap:4px;padding:12px 0;overflow-y:auto}.icon-rail button{width:40px;height:40px;border:0;border-radius:12px;background:transparent;display:grid;place-items:center;color:var(--text);padding:0;flex:none}.icon-rail button:hover{background:#f2f2f2}.icon-rail button.active{background:var(--accent-soft);color:var(--accent)}.icon-rail svg{width:22px;height:22px}.list-col{border-right:1px solid var(--line);overflow-y:auto;padding:16px 12px;min-width:0}.search-box{display:flex;align-items:center;gap:8px;border:1px solid var(--line);border-radius:999px;padding:8px 12px;background:#fff}.search-box svg{flex:none;color:var(--muted)}.search-box input{border:0;padding:0;flex:1;min-width:0;font-size:14px;background:transparent}.search-box input:focus{box-shadow:none;outline:none}.list-section-head{display:flex;align-items:center;justify-content:space-between;padding:16px 4px 8px;font-weight:600;font-size:14px}.list-section-head button{width:28px;height:28px;padding:0;display:grid;place-items:center;border-radius:50%}.muse-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}.muse-item{padding:10px 12px;border-radius:12px;cursor:pointer;overflow:hidden}.muse-item:hover{background:#f9fafb}.muse-item.selected{background:#f2f2f2}.muse-item-title{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:14px}.muse-item-sub{display:flex;gap:6px;align-items:center;color:var(--muted);font-size:12px;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.task-dot{width:8px;height:8px;border-radius:50%;background:currentColor;flex:none}.center-col{min-width:0;display:flex;flex-direction:column;overflow-y:auto;background:#fff}.center-topbar{position:sticky;top:0;z-index:5;display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#fff;border-bottom:1px solid var(--line)}.center-inner{max-width:760px;margin:0 auto;width:100%;padding:24px 20px 16px;display:flex;flex-direction:column;gap:20px;flex:1}.right-col{border-left:1px solid var(--line);overflow-y:auto;padding:20px 16px;display:flex;flex-direction:column;gap:20px;min-width:0}.right-profile{text-align:center;position:relative}.right-profile .icon-btn{position:absolute;top:0;right:0}.right-avatar{width:72px;height:72px;border-radius:50%;background:var(--accent-soft);color:var(--accent);display:grid;place-items:center;font-size:28px;font-weight:700;margin:0 auto}.right-name{font-weight:700;font-size:17px;margin-top:10px}.right-status{color:var(--green);font-size:13px;font-weight:600}.right-status.off{color:var(--muted)}.right-icon-row{display:flex;gap:4px;justify-content:space-between;border-top:1px solid var(--line);border-bottom:1px solid var(--line);padding:10px 4px}.right-icon-row button{width:36px;height:36px;border:0;border-radius:12px;background:transparent;display:grid;place-items:center;color:var(--text);padding:0}.right-icon-row button:hover{background:#f2f2f2}.right-icon-row button.active{background:var(--accent-sLine truncated
</head>
<body>
+232 -1
View File
@@ -1215,6 +1215,97 @@ def _followup_to_dict(row):
}
def _ensure_continue_messages_table(conn):
"""幂等建继续消息表:POST /continue 接受的每条用户消息都在
与状态改回 queued 的同一事务里落盘(见 _continue_task),
否则继续消息只活在 resume_message 列里、worker 跑完即清,
聊天历史永远拼不出用户那一半。老库第一次跑到这里时自动
补表,不丢数据;此前已发生的继续(audit 里 task_continued
有、表里没有)无法追回,chat-history 会如实标注缺口数,
不编造。列写法与 followups 表一致(DOUBLE PRECISION 在
SQLite 与 Postgres 下都可用)。"""
conn.execute(
"CREATE TABLE IF NOT EXISTS task_continue_messages("
"id TEXT PRIMARY KEY, "
"task_id TEXT, "
"project_id TEXT, "
"body TEXT, "
"created_by TEXT, "
"created_at DOUBLE PRECISION)"
)
conn.execute(
"CREATE INDEX IF NOT EXISTS idx_continue_messages_task"
" ON task_continue_messages(task_id, created_at, id)")
def _continue_message_to_dict(row):
return {
"id": row["id"],
"task_id": row["task_id"],
"project_id": row["project_id"],
"body": row["body"],
"created_by": row["created_by"],
"created_at": row["created_at"],
}
def _chat_message_from_trace(entry):
"""链路条目 -> 聊天消息(与 React 的 mapTrace.ts 同款映射,
两边改动要同步):message 是助手回复,exec/file/tool/call
是工具气泡,plan/reasoning/agent-start/agent-done 是居中
状态提示。链路条目本身没有发送方字段,这里不编造角色,
只按 kind 分流。"""
kind = entry.get("kind") or ""
eid = entry.get("id") or ""
if kind == "message":
text = ((("[" + entry["phase"] + "] ") if entry.get("phase")
else "") + (entry.get("text") or ""))
return {"id": eid, "role": "assistant", "source": "trace",
"kind": kind, "label": "助手", "text": text}
if kind in ("exec", "file", "tool", "call"):
label = {"exec": "命令", "file": "文件",
"tool": "工具"}.get(kind, "调用")
if kind == "exec":
# 与 React traceLineText 同款:缺失字段为空,不渲染 None。
def _s(v):
return "" if v is None else str(v)
text = "%s(exit=%s,%sms,cwd=%s)" % (
entry.get("command") or "", _s(entry.get("exitCode")),
_s(entry.get("durationMs")), entry.get("cwd") or "")
elif kind == "file":
text = "%s %s %s" % (entry.get("path") or "",
entry.get("change") or "",
entry.get("status") or "")
elif kind == "tool":
text = "%s/%s %s" % (entry.get("server") or "",
entry.get("tool") or "",
entry.get("status") or "")
else:
text = entry.get("name") or ""
return {"id": eid, "role": "tool", "source": "trace",
"kind": kind, "label": label, "text": text}
if kind == "plan":
return {"id": eid, "role": "status", "source": "trace",
"kind": kind, "text": entry.get("text") or ""}
if kind == "reasoning":
return {"id": eid, "role": "status", "source": "trace",
"kind": kind,
"text": "思考:" + (entry.get("summary") or "")}
if kind == "agent-start":
return {"id": eid, "role": "status", "source": "trace",
"kind": kind,
"text": "子代理开始:" + (entry.get("name") or "")
+ (("(" + entry["prompt"] + ")")
if entry.get("prompt") else "")}
if kind == "agent-done":
return {"id": eid, "role": "status", "source": "trace",
"kind": kind,
"text": "子代理结束:" + (entry.get("name") or "")
+ " " + (entry.get("status") or "")}
return {"id": eid, "role": "status", "source": "trace",
"kind": kind, "text": json.dumps(entry)[:300]}
def _ensure_environments_table(conn):
"""幂等建环境表:server init_db 与各环境接口都可调它,老库
第一次跑到这里时自动补表,不丢数据。类型写法与 followups
@@ -1420,6 +1511,7 @@ def init_db(db_path):
migrate_tasks_table(conn)
migrate_projects_table(conn)
_ensure_followups_table(conn)
_ensure_continue_messages_table(conn)
_ensure_environments_table(conn)
_ensure_quotas_table(conn)
_backfill_default_quotas(conn)
@@ -1454,6 +1546,7 @@ def init_db(db_path):
migrate_tasks_table(conn)
migrate_projects_table(conn)
_ensure_followups_table(conn)
_ensure_continue_messages_table(conn)
_ensure_environments_table(conn)
_ensure_quotas_table(conn)
_backfill_default_quotas(conn)
@@ -1983,6 +2076,9 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
self._create_followup(user_id, rest[: -len("/followups")])
elif rest.endswith("/followups") and method == "GET":
self._list_followups(user_id, rest[: -len("/followups")])
elif rest.endswith("/chat-history") and method == "GET":
self._task_chat_history(
user_id, rest[: -len("/chat-history")])
elif rest.endswith("/messages") and method == "POST":
self._create_peer_message(user_id, rest[: -len("/messages")])
elif rest.endswith("/messages") and method == "GET":
@@ -4476,6 +4572,124 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
self._send_json(200, {
"followups": [_followup_to_dict(r) for r in rows]})
def _task_chat_history(self, user_id, task_id):
"""GET /tasks/{id}/chat-history:真正的交替历史,一次性
按时间交错拼好返回,前端不再猜顺序。
来源(全部是后端已有记录,不编造):
- 首条 prompt:任务行 prompt + created_at(用户)
- 继续消息:task_continue_messages(用户,部署后才入库)
- 后续消息:task_followups(用户,含任务间互发)
- 助手/工具/状态:链路 message/exec/file/tool/call/
plan/reasoning/agent-* 条目(trace 的 ts 是毫秒时间戳,
无 ts 的条目按链路顺序贴在上一条有 ts 的条目之后,
不给它伪造时间戳——输出里 ts_ms 为 null)
排序:按 ts_ms 升序,同一毫秒用户消息在链路条目之前
(用户消息是触发该轮的原因),其余按原顺序稳定排序。
旧任务缺口:部署前发生的继续只在 audit 里留了
task_continued(只有任务 id,没有正文),无法追回;
legacy_continue_count = audit 次数 - 已入库条数,>0 时
前端必须在聊天窗上标“此前继续消息未入库,历史不完整”,
不能假装这就是全部对话。异机 worktree 时链路读不到,
messages 里只有用户侧 + note,不谎报无过程。"""
row = self._fetch_task(task_id)
if row is None:
self._send_json(404, {"error": "task not found"})
return
task = _task_to_dict(row)
with _auth_store(self.server.db_path) as store:
if not store.check_permission(
user_id, task["project_id"], "read"):
self._send_json(403, {"error": "forbidden"})
return
entries, trace_note = self._trace_snapshot(task)
conn = self._db()
try:
_ensure_continue_messages_table(conn)
_ensure_followups_table(conn)
cont_rows = conn.execute(
"SELECT * FROM task_continue_messages WHERE task_id = ?"
" ORDER BY created_at, id",
(task_id,),
).fetchall()
fu_rows = conn.execute(
"SELECT * FROM task_followups WHERE task_id = ?"
" ORDER BY created_at, id",
(task_id,),
).fetchall()
legacy = None
try:
aud = conn.execute(
"SELECT COUNT(*) FROM audit_log"
" WHERE action = 'task_continued' AND detail = ?",
(task_id,),
).fetchone()
legacy = max(0, int(aud[0]) - len(cont_rows))
except Exception:
legacy = None
finally:
conn.close()
merged = []
def _ms(seconds):
try:
return int(float(seconds) * 1000)
except (TypeError, ValueError):
return None
if task.get("prompt"):
merged.append((0, _ms(task.get("created_at")), {
"id": "task-prompt", "role": "user", "source": "prompt",
"label": "用户", "text": task["prompt"],
"ts_ms": _ms(task.get("created_at"))}))
for r in cont_rows:
d = _continue_message_to_dict(r)
merged.append((0, _ms(d["created_at"]), {
"id": "continue-" + d["id"], "role": "user",
"source": "continue", "label": "用户",
"text": d["body"], "ts_ms": _ms(d["created_at"])}))
for r in fu_rows:
d = _followup_to_dict(r)
merged.append((0, _ms(d["created_at"]), {
"id": "followup-" + d["id"], "role": "user",
"source": "followup", "label": "后续消息",
"text": d["body"], "ts_ms": _ms(d["created_at"])}))
last_trace_ms = _ms(task.get("created_at"))
for e in entries:
if not isinstance(e, dict):
continue
if str(e.get("id") or "").startswith("fu-"):
# 直接发送的后续消息已在链路里留了一条回声
# (_append_direct_trace 写 kind=message),
# followups 表里同一条已作用户消息返回;这里
# 跳过回声,否则同一条消息会先以用户、再以
# 助手各出现一次。
continue
msg = _chat_message_from_trace(e)
ts = e.get("ts")
if isinstance(ts, (int, float)):
last_trace_ms = int(ts)
sort_ms = int(ts)
else:
sort_ms = last_trace_ms
msg["ts_ms"] = int(ts) if isinstance(ts, (int, float)) else None
merged.append((1, sort_ms, msg))
# 排序键:(时间, 用户优先, 原顺序)。merged 里用户项的
# 第一元组是 0、链路项是 1;Python 排序稳定,同一
# (时间, 优先级) 内保持插入顺序。
merged.sort(key=lambda item: (
item[1] if item[1] is not None else 0, item[0]))
self._send_json(200, {
"task_id": task_id,
"status": task["status"],
"messages": [m for _p, _t, m in merged],
"continue_messages_stored": len(cont_rows),
"legacy_continue_count": legacy,
"trace_note": trace_note,
})
def _create_peer_message(self, user_id, task_id):
"""任务间互发消息(API 发送方):以 from_task_id 任务的名义
给目标任务发消息。投递语义与 worker 路径共用 deliver 层,
@@ -6623,13 +6837,30 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
% ", ".join(sets),
tuple(vals),
)
conn.commit()
if cur.rowcount != 1:
# 状态已变化:更新 0 行,同一事务里什么都不留——
# 尤其不能先把继续消息落盘再报 409,那会凭空多
# 一条从未被执行的“用户消息”。
conn.rollback()
self._send_json(409, {
"error": "任务状态已变化,请刷新后重试",
"status": task["status"],
})
return
# 继续消息与状态改回 queued 同一事务:要么一起生效,
# 要么一起不生效,绝不出现“任务已重排队、消息却丢了”
# 的半截状态。created_at 用本请求的 updated_at 时间戳
# (vals[1]),与任务行的时间源一致,便于按时间交错
# 拼聊天历史。
_ensure_continue_messages_table(conn)
conn.execute(
"INSERT INTO task_continue_messages(id, task_id,"
" project_id, body, created_by, created_at)"
" VALUES(?, ?, ?, ?, ?, ?)",
(uuid.uuid4().hex, task_id, task["project_id"],
message, user_id, vals[1]),
)
conn.commit()
new_row = conn.execute(
"SELECT * FROM tasks WHERE id = ?", (task_id,)
).fetchone()
+215
View File
@@ -0,0 +1,215 @@
"""继续消息持久化 + 交替历史单测:POST /continue 接受的用户
消息必须落盘(与状态改回 queued 同一事务),GET
/tasks/{id}/chat-history 按时间交错拼出 prompt / 继续 /
后续消息 / 链路助手消息;409 的继续绝不能留 phantom 消息;
部署前发生的继续在 audit 里只有任务 id、正文无法追回,
接口要如实报 legacy_continue_count,不编造。
不跑真实模型回合、不联网:server 侧走本地临时端口 + 临时
SQLite,链路通过 server.trace_dir 注入临时 jsonl。
"""
import http.client
import json
import os
import sqlite3
import sys
import tempfile
import threading
import time
import unittest
from pathlib import Path
try:
from cloud.auth import AuthStore
from cloud.server import create_server
except ImportError: # discover -s cloud/tests 时把项目根目录补进 sys.path
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from cloud.auth import AuthStore
from cloud.server import create_server
class ChatHistoryTestCase(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.db_path = os.path.join(self.tmp.name, "control.db")
self.trace_dir = os.path.join(self.tmp.name, "traces")
os.makedirs(self.trace_dir)
self.auth = AuthStore(self.db_path)
self.owner_id, self.owner_key = self.auth.create_user("owner")
self.server = create_server(self.db_path, "127.0.0.1", 0)
self.server.trace_dir = self.trace_dir
self.port = self.server.server_address[1]
self.thread = threading.Thread(
target=self.server.serve_forever, daemon=True)
self.thread.start()
status, self.project = self._req(
"POST", "/projects", {"name": "demo"}, self.owner_key)
self.assertEqual(status, 201)
def tearDown(self):
self.server.shutdown()
self.server.server_close()
self.thread.join(timeout=5)
try:
self.auth.conn.close()
except Exception:
pass
self.tmp.cleanup()
def _req(self, method, path, body=None, key=None):
conn = http.client.HTTPConnection(
"127.0.0.1", self.port, timeout=10)
hdrs = {}
payload = None
if key:
hdrs["X-API-Key"] = key
if body is not None:
payload = json.dumps(body)
hdrs["Content-Type"] = "application/json"
conn.request(method, path, body=payload, headers=hdrs)
resp = conn.getresponse()
raw = resp.read()
conn.close()
try:
return resp.status, json.loads(raw.decode("utf-8"))
except (ValueError, UnicodeDecodeError):
return resp.status, {}
def _task(self, status="completed"):
_s, task = self._req(
"POST", "/tasks",
{"project_id": self.project["id"], "prompt": "original"},
self.owner_key)
self.assertEqual(_s, 201, task)
if status != "queued":
conn = sqlite3.connect(self.db_path)
conn.execute("UPDATE tasks SET status = ? WHERE id = ?",
(status, task["id"]))
conn.commit()
conn.close()
return task["id"]
def _set_status(self, task_id, status):
conn = sqlite3.connect(self.db_path)
conn.execute("UPDATE tasks SET status = ? WHERE id = ?",
(status, task_id))
conn.commit()
conn.close()
def _chat(self, task_id, key=None):
return self._req(
"GET", "/tasks/%s/chat-history" % task_id,
key=key or self.owner_key)
def test_continue_persisted_and_interleaved(self):
tid = self._task("completed")
s, _t = self._req(
"POST", "/tasks/%s/continue" % tid,
{"message": "first follow-up"}, self.owner_key)
self.assertEqual(s, 200)
self._set_status(tid, "completed")
s, _t = self._req(
"POST", "/tasks/%s/continue" % tid,
{"message": "second follow-up"}, self.owner_key)
self.assertEqual(s, 200)
s, chat = self._chat(tid)
self.assertEqual(s, 200, chat)
self.assertEqual(chat["continue_messages_stored"], 2)
# 两次继续都有 audit,全部已入库,缺口为 0。
self.assertEqual(chat["legacy_continue_count"], 0)
texts = [(m["role"], m["source"], m["text"])
for m in chat["messages"]]
self.assertEqual(texts, [
("user", "prompt", "original"),
("user", "continue", "first follow-up"),
("user", "continue", "second follow-up"),
])
# 注入链路:把两次继续与任务创建时间钉到确定时间戳,
# 助手回复落在两次继续之间与之后,按时间交错(不靠
# 墙钟间隔赌顺序——两次继续在测试里只隔几十毫秒,
# 助手 +500ms 本就该排在第二次继续之后)。
conn = sqlite3.connect(self.db_path)
conn.execute("UPDATE tasks SET created_at = ? WHERE id = ?",
(1699999900.0, tid))
conn.execute(
"UPDATE task_continue_messages SET created_at = ?"
" WHERE task_id = ? AND body = ?",
(1700000000.0, tid, "first follow-up"))
conn.execute(
"UPDATE task_continue_messages SET created_at = ?"
" WHERE task_id = ? AND body = ?",
(1700000100.0, tid, "second follow-up"))
conn.commit()
conn.close()
entries = [
{"kind": "message", "id": "m1", "ts": 1700000050000,
"phase": "final_answer", "text": "done first"},
{"kind": "message", "id": "m2", "ts": 1700000150000,
"phase": "final_answer", "text": "done second"},
{"kind": "exec", "id": "e1", "ts": 1700000150600,
"command": "ls", "exitCode": 0, "durationMs": 1,
"cwd": "/tmp"},
]
with open(os.path.join(self.trace_dir, tid + ".jsonl"),
"w", encoding="utf-8") as f:
for e in entries:
f.write(json.dumps(e) + "\n")
s, chat = self._chat(tid)
self.assertEqual(s, 200, chat)
texts = [(m["role"], m["source"], m["text"])
for m in chat["messages"]]
self.assertEqual(texts, [
("user", "prompt", "original"),
("user", "continue", "first follow-up"),
("assistant", "trace", "[final_answer] done first"),
("user", "continue", "second follow-up"),
("assistant", "trace", "[final_answer] done second"),
("tool", "trace", "ls(exit=0,1ms,cwd=/tmp)"),
])
def test_followup_and_legacy_gap_reported(self):
# 后续消息仅支持进行中任务,completed 会 409。
tid = self._task("running")
s, _f = self._req(
"POST", "/tasks/%s/followups" % tid,
{"body": "ping while running", "mode": "direct"},
self.owner_key)
self.assertEqual(s, 201, _f)
# 模拟部署前的一次继续:audit 里有记录、表里没有正文。
conn = sqlite3.connect(self.db_path)
conn.execute(
"INSERT INTO audit_log(user_id, project_id, action,"
" detail, created_at) VALUES(?, ?, 'task_continued', ?, ?)",
(self.owner_id, self.project["id"], tid, time.time()))
conn.commit()
conn.close()
s, chat = self._chat(tid)
self.assertEqual(s, 200, chat)
self.assertEqual(chat["continue_messages_stored"], 0)
self.assertEqual(chat["legacy_continue_count"], 1)
sources = [(m["role"], m["source"]) for m in chat["messages"]]
self.assertEqual(sources, [
("user", "prompt"), ("user", "followup")])
def test_rejected_continue_stores_nothing(self):
tid = self._task("queued")
code, _b = self._req(
"POST", "/tasks/%s/continue" % tid,
{"message": "should not persist"}, self.owner_key)
self.assertEqual(code, 409)
s, chat = self._chat(tid)
self.assertEqual(s, 200, chat)
self.assertEqual(chat["continue_messages_stored"], 0)
self.assertEqual(len(chat["messages"]), 1) # 只有 prompt
def test_chat_history_auth(self):
tid = self._task("completed")
code, _b = self._chat(tid, key="bad-key")
self.assertEqual(code, 401)
code, _b = self._chat("no-such-task")
self.assertEqual(code, 404)
if __name__ == "__main__":
unittest.main()