diff --git a/cloud/console-react/src/sections/TaskListSection.tsx b/cloud/console-react/src/sections/TaskListSection.tsx index 048a9ab..cdf6fe1 100644 --- a/cloud/console-react/src/sections/TaskListSection.tsx +++ b/cloud/console-react/src/sections/TaskListSection.tsx @@ -21,16 +21,12 @@ import type { Task } from "../api/types"; // 累计总时长,服务端需保留首次 started_at 或累加各轮时长, // 那是服务端字段缺口,不是前端能算出来的。 // -// token:列表接口(_task_to_dict)目前不回任何 token 字段, -// 单任务成本在 GET /tasks/{id}/cost 里(total_tokens、 -// actual_tokens、estimated_tokens)。列表行不逐个调 /cost: -// 一屏 N 个任务就是 N 个额外请求,会把列表刷新拖慢、还容易 -// 撞限流。所以这里只留栏位:服务端一旦在列表里补 -// total_tokens 就显示真数;分项只有 actual/estimated 时加总 -// 显示;都没有时显示“token —”,不编造、不估算。 -// 服务端补字段的最小改法:_task_to_dict 里给每行附 -// cost_records 按 task_id 求和的 total_tokens(或在列表查询 -// 里 LEFT JOIN 聚合一次),不需要新端点。 +// token:列表接口一次聚合回 total_tokens(服务端 +// _task_tokens_map:cost_records 按 task_id 求和,一屏只多 +// 一条查询)。列表行不逐个调 /cost:逐个调是一屏 N 个额外 +// 请求,会把列表刷新拖慢、还容易撞限流。分项只有 +// actual/estimated 时加总显示;都没有(非列表调用方、 +// total_tokens 为 null)时显示“token —”,不编造、不估算。 function firstChar(t: Task): string { const p = (t.prompt || "").trim(); if (p) return [...p][0]; @@ -87,7 +83,7 @@ function tokenOf(t: Task): [string, string] { if (actual !== null || estimated !== null) { return [((actual || 0) + (estimated || 0)).toLocaleString() + " token", "token:actual + estimated 分项加总"]; } - return ["token —", "token:列表接口未返回 token 字段(服务端需在任务列表里补 total_tokens);单任务真数在任务详情的成本里可查,本行不逐个调成本接口、不编造"]; + return ["token —", "token:本行未带 token 字段;单任务真数在任务详情的成本里可查,本行不逐个调成本接口、不编造"]; } // GitHub issues-style task list: search on top, the project selector diff --git a/cloud/console.html b/cloud/console.html index e3109d3..cd38036 100644 --- a/cloud/console.html +++ b/cloud/console.html @@ -68,7 +68,7 @@ Error generating stack: `+a.message+` `+D.result_text.slice(0,8e3)+` `),D.error_text&&(L+=`=== error === `+D.error_text.slice(0,8e3)+` -`),C(L||"(暂无 result/error 输出)")}catch{}dl(H.id),Bl(H.id),Ye(H.id),Vt(H.id),Hl(H),ol(H.id),ll(H.id),ml(H.id),ql(H.id)}},[dl,Bl,Ye,Vt,Hl,ol,ll,ml,ql]),gn=v.useCallback(async H=>{const D=ne.current;if(!D)return;const L={cancel:"确定取消该任务吗?",merge:"确定把 completed 任务标为 merged 吗?",pr:"确定为该任务提 PR 吗?(会在仓库真实建 PR)"};if(!(L[H]&&!window.confirm(L[H])))try{const J=await tt($.taskAction(D.id,H),"POST",{});x(H+" 已提交:"+JSON.stringify(J).slice(0,300)),r(),await Wl(D.id)}catch(J){x(H+" 失败:"+J.message)}},[Wl,r]),Ke=v.useCallback(async()=>{const H=ne.current;if(H)try{const D=await tt($.taskCost(H.id));b(JSON.stringify(D,null,2).slice(0,4e3))}catch(D){b("查成本失败:"+D.message)}},[]),Za=v.useCallback(async H=>{const D=ne.current;if(D){if(!H.trim()){I("分支消息必填");return}if(!su.includes(D.status)){I("仅终态任务可分支");return}try{const L=await tt($.taskFork(D.id),"POST",{message:H.trim()});I("分支任务已建:"+L.id.slice(0,8)),r()}catch(L){I("分支失败:"+L.message)}}},[r]),va=v.useCallback(async H=>{const D=ne.current;if(D)try{await tt($.taskReviews(D.id),"POST",H),le("评审已登记(requested)"),await Ye(D.id)}catch(L){le("登记评审需求失败:"+L.message)}},[Ye]),ou=v.useCallback(H=>{const D=ne.current;if(!D)return;const L=window.prompt("输入该条评审的 findings(将写回记录并标为 completed):");L!==null&&tt($.taskReviewFindings(D.id,H),"POST",{findings:L}).then(()=>void Ye(D.id)).catch(J=>le("写 findings 失败:"+J.message))},[Ye]),du=v.useCallback((H,D)=>{const L=ne.current;if(!L)return;const J=D==="approved"?"approve":"deny";tt($.taskApprovalDecision(L.id,H,J),"POST",{}).then(()=>{K("已记录"+(D==="approved"?"允许":"拒绝")+"(当前版本只留回执,不拦截实时行为)"),Vt(L.id)}).catch(Ct=>K("决定失败:"+Ct.message))},[Vt]),$l=v.useCallback(async(H,D,L)=>{const J=ne.current;if(J){if(!D.trim()){k("目标任务 id 不能为空");return}if(!L.trim()){k("消息正文不能为空");return}try{const Ct=await tt($.taskMessagesTo(D.trim()),"POST",{from_task_id:J.id,body:L.trim(),mode:H});k("已发送("+Ct.delivery+")"),await ll(J.id)}catch(Ct){k("发送失败:"+Ct.message)}}},[ll]),Se=v.useCallback((H,D)=>{const L=ne.current;if(!L)return;if(!["completed","failed","interrupted"].includes(L.status)){Gt("仅 completed/failed/interrupted 任务可操作线程(merged/merge-failed 不可改,服务端同样 409)");return}const J={};if(H==="rename"){if(!D.trim()){Gt("新线程名不能为空");return}J.name=D.trim()}tt($.taskThreadAction(L.id,H),"POST",J).then(Ct=>{Gt(H+" 已提交:"+JSON.stringify(Ct).slice(0,300)),ml(L.id),ql(L.id)}).catch(Ct=>Gt(H+" 失败:"+Ct.message))},[ml,ql]),hl=v.useCallback(async H=>{const D=ne.current;if(!D)return;const L=H.trim();if(!L){ae("消息不能为空");return}try{su.includes(D.status)&&D.status!=="merged"?(await tt($.taskContinue(D.id),"POST",{message:L}),ae("已重新排队,同一会话继续"),r(),await Wl(D.id)):["queued","running"].includes(D.status)?(await tt($.taskFollowups(D.id),"POST",{body:L,mode:"direct"}),ae("已直接发送"),await dl(D.id),await Bl(D.id)):ae("merged/merge-failed 任务不可继续,也不可发后续消息")}catch(J){ae("发送失败:"+J.message)}},[Wl,r,dl,Bl]),be=v.useCallback(async()=>{const H=ne.current;if(!H){ie("先选一个任务");return}try{const D=await tt($.taskTrace(H.id)),L=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"}),J=document.createElement("a");J.href=URL.createObjectURL(L),J.download="trace-"+H.id.slice(0,8)+".json",document.body.appendChild(J),J.click(),J.remove(),setTimeout(()=>URL.revokeObjectURL(J.href),1e3),ie("已下载完整链路 "+(D.entries||[]).length+" 条")}catch(D){ie("下载失败:"+D.message)}},[]);return v.useEffect(()=>()=>qe(),[qe]),{currentTask:m,detailBody:_,actionMsg:N,actionReasons:G,costBox:Q,forkVisible:B,setForkVisible:lt,forkMsg:at,coordChildren:V,agents:Ot,agentsNote:nt,reviews:ee,reviewMsg:yt,reviewError:de,traceEntries:Ft,traceNote:tl,traceDone:Y,traceKind:et,setTraceKind:Tt,traceMsg:gt,followups:Oe,approvals:y,approvalMsg:F,peerMessages:pt,peerMsg:_t,threadText:ut,threadGateNote:Va,threadMsg:Le,threadList:zt,chatMsg:Fl,chatHistory:ha,showDetail:Wl,refreshDetailLists:yn,doAction:gn,taskCost:Ke,forkTask:Za,postReview:va,submitFindings:ou,decideApproval:du,postPeer:$l,threadAction:Se,sendChatMessage:hl,downloadTrace:be,refreshReviews:()=>void Ye(),refreshApprovals:()=>void Vt()}}function Wy(i){const[r,m]=v.useState([]),[o,_]=v.useState(""),[C,N]=v.useState(null),[x,G]=v.useState(""),U=v.useCallback(async lt=>{const at=lt!==void 0?lt:i();if(!at){m([]);return}try{const I=await tt($.projectAutomations(at));m(I.automations||[])}catch(I){_("自动化加载失败:"+I.message)}},[i]),Q=v.useCallback(async(lt,at,I,V)=>{const dt=i();if(!dt){_("先选项目");return}if(!lt.trim()){_("自动化名称必填");return}if(!at.trim()){_("定时任务内容必填");return}const Ot={name:lt.trim(),prompt:at.trim()};I.trim()&&(Ot.interval_seconds=parseInt(I.trim(),10)),V.trim()&&(Ot.repo_url=V.trim());try{await tt($.projectAutomations(dt),"POST",Ot),_("自动化已创建(登记定义,到期后由执行器认领)"),await U(dt)}catch(te){_("创建失败:"+te.message)}},[i,U]),b=v.useCallback(lt=>{const at=i();at&&tt($.projectAutomationCancel(at,lt),"POST",{}).then(()=>{_("自动化已取消"),U(at)}).catch(I=>_("取消失败:"+I.message))},[i,U]),B=v.useCallback(async lt=>{const at=lt!==void 0?lt:i();if(!at){N(null);return}try{const I=await tt($.projectTriage(at));N(I),G("")}catch(I){G("分诊加载失败:"+I.message)}},[i]);return{automations:r,autoMsg:o,triage:C,triageError:x,refreshAutomations:U,createAutomation:Q,cancelAutomation:b,refreshTriage:B}}function $y(i,r){const m=v.useRef(i);m.current=i,v.useEffect(()=>{const o=setInterval(()=>{const _=document.activeElement;m.current(_?_.tagName==="SELECT":!1)},r);return()=>clearInterval(o)},[r])}function Iy(i,r){const m=v.useRef(i);m.current=i,v.useEffect(()=>{const o=setInterval(()=>m.current(),r);return()=>clearInterval(o)},[r])}function Py(i){const r=m=>String(m).padStart(2,"0");return r(i.getHours())+":"+r(i.getMinutes())+":"+r(i.getSeconds())}function tg(){var I;const i=Ky(),r=Jy(),m=v.useRef("");m.current=i.projectId;const o=v.useCallback(()=>m.current,[]),_=Wy(o),C=v.useCallback(V=>{const dt=i.projects.find(Ot=>Ot.id===V);return dt&&dt.my_role||null},[i.projects]),N=v.useCallback(()=>{r.refreshTasks(void 0,m.current)},[r]),x=Fy(C,N),[G,U]=v.useState("--:--:--"),Q=v.useCallback(V=>{i.setProjectId(V),m.current=V,r.refreshTasks(V),i.refreshEnvs(V),i.refreshMembers(V),i.refreshSkills(V),_.refreshAutomations(V),_.refreshTriage(V)},[i,r,_]),b=(I=i.projects[0])==null?void 0:I.id;v.useEffect(()=>{!i.projectId&&b&&Q(b)},[i.projectId,b,Q]);const B=v.useCallback(V=>{i.saveKeyInput(V),V.trim()&&(i.refreshHealth(),i.refreshProjects(),i.refreshWorkers(),m.current&&(r.refreshTasks(m.current),i.refreshEnvs(m.current),i.refreshMembers(m.current)),i.loadRelayModels(),i.loadQuota(),i.loadGithubRepos(),i.probeAdmin())},[i,r]),lt=v.useCallback(V=>{if(U(Py(new Date)),!_l()){i.refreshHealth();return}r.refreshTasks(void 0,m.current),i.refreshHealth(),i.loadQuota(),i.refreshWorkers(),i.refreshMembers(),i.refreshSkills(),_.refreshAutomations(),_.refreshTriage(),x.refreshDetailLists(),V||(i.refreshProjects(),i.refreshEnvs())},[i,r,_,x]),at=v.useCallback(()=>{_l()&&(i.loadRelayModels(),i.loadGithubRepos())},[i]);return v.useEffect(()=>{i.refreshHealth(),i.refreshProjects(),i.refreshWorkers(),i.loadQuota(),i.loadGithubRepos(),_l()&&i.loadRelayModels(),i.probeAdmin()},[]),{keySaved:i.keySaved,keyState:i.keyState,saveKeyInput:B,clearKeyInput:i.clearKeyInput,quotaText:i.quotaText,healthText:i.healthText,refreshHealth:i.refreshHealth,adminGate:i.adminGate,adminBody:i.adminBody,adminResult:i.adminResult,adminUserId:i.adminUserId,setAdminUserId:i.setAdminUserId,adminQuota:i.adminQuota,adminSweep:i.adminSweep,adminBackup:i.adminBackup,projects:i.projects,projectId:i.projectId,projectMsg:i.projectMsg,selectProject:Q,refreshProjects:i.refreshProjects,createProject:i.createProject,githubRepos:i.githubRepos,workers:i.workers,workersSummary:i.workersSummary,workersError:i.workersError,refreshWorkers:i.refreshWorkers,members:i.members,memberMsg:i.memberMsg,memberError:i.memberError,refreshMembers:()=>void i.refreshMembers(),inviteMember:i.inviteMember,changeMemberRole:i.changeMemberRole,skills:i.skills,apps:i.apps,mcpServers:i.mcpServers,skillMsg:i.skillMsg,skillError:i.skillError,mcpMsg:i.mcpMsg,mcpOauthMsg:i.mcpOauthMsg,mcpOauthBox:i.mcpOauthBox,refreshSkills:()=>void i.refreshSkills(),reloadMcp:i.reloadMcp,mcpOauth:i.mcpOauth,envs:i.envs,envCount:i.envCount,envMsg:i.envMsg,envRepoLine:i.envRepoLine,envRepoBar:i.envRepoBar,envRepoMsg:i.envRepoMsg,envSelect:i.envSelect,setEnvSelect:i.setEnvSelect,refreshEnvs:()=>void i.refreshEnvs(),createEnv:i.createEnv,archiveEnv:i.archiveEnv,saveEnvRepo:i.saveEnvRepo,models:i.models,modelCustomVisible:i.modelCustomVisible,tasks:r.tasks,taskCountText:r.taskCountText,taskError:r.taskError,refreshTasks:()=>void r.refreshTasks(void 0,m.current),statusFilter:r.statusFilter,setStatusFilter:r.setStatusFilter,executorFilter:r.executorFilter,setExecutorFilter:r.setExecutorFilter,searchText:r.searchText,setSearchText:r.setSearchText,filteredTasks:r.filteredTasks,currentTask:x.currentTask,detailBody:x.detailBody,actionMsg:x.actionMsg,actionReasons:x.actionReasons,showDetail:V=>void x.showDetail(V),doAction:x.doAction,taskCost:x.taskCost,costBox:x.costBox,forkVisible:x.forkVisible,setForkVisible:x.setForkVisible,forkTask:x.forkTask,forkMsg:x.forkMsg,coordChildren:x.coordChildren,coordText:"",agents:x.agents,agentsNote:x.agentsNote,reviews:x.reviews,reviewMsg:x.reviewMsg,reviewError:x.reviewError,refreshReviews:x.refreshReviews,postReview:x.postReview,submitFindings:x.submitFindings,traceEntries:x.traceEntries,traceNote:x.traceNote,traceDone:x.traceDone,traceKind:x.traceKind,setTraceKind:x.setTraceKind,downloadTrace:x.downloadTrace,traceMsg:x.traceMsg,sendChatMessage:x.sendChatMessage,chatMsg:x.chatMsg,chatHistory:x.chatHistory,followups:x.followups,approvals:x.approvals,approvalMsg:x.approvalMsg,refreshApprovals:x.refreshApprovals,decideApproval:x.decideApproval,peerMessages:x.peerMessages,peerMsg:x.peerMsg,postPeer:x.postPeer,automations:_.automations,autoMsg:_.autoMsg,refreshAutomations:()=>void _.refreshAutomations(),createAutomation:_.createAutomation,cancelAutomation:_.cancelAutomation,triage:_.triage,triageError:_.triageError,refreshTriage:()=>void _.refreshTriage(),threadText:x.threadText,threadGateNote:x.threadGateNote,threadMsg:x.threadMsg,threadList:x.threadList,threadAction:x.threadAction,dispatch:async(V,dt)=>{const Ot=await r.dispatch(V,dt);return r.refreshTasks(m.current),Ot},dispatchMsg:"",refreshAll:lt,refreshSlow:at,refreshStamp:G}}function cu({label:i,value:r,sub:m}){return s.jsxs("div",{className:"dash-card data-card",children:[s.jsx("div",{className:"dash-card-label",children:i}),s.jsx("div",{className:"dash-card-value",children:r}),s.jsx("div",{className:"muted",children:m})]})}function eg(){const i=Mt(),r=i.healthText==="未加载"?"未加载":i.healthText.includes("worker_ok=true")?"正常":i.healthText.includes("worker_ok=false")?"异常":i.healthText.includes("加载失败")?"加载失败":"未知",m=i.quotaText.match(/剩余 \$([0-9.]+)/),o=i.quotaText.includes("加载失败")?"加载失败":i.quotaText.includes("未填 key")||i.quotaText==="未加载"?"未加载":m?"$"+m[1]:"配额",_=["running","completed","failed","queued","merged","interrupted"].map(b=>i.tasks.filter(B=>B.status===b).length).map((b,B)=>["running","completed","failed","queued","merged","interrupted"][B]+" "+b).join(" | "),C=i.projects.find(b=>b.id===i.projectId),N=i.taskError?{value:"加载失败",sub:i.taskError}:i.projectId?{value:String(i.tasks.length),sub:i.taskCountText+" | "+_+(C?" | "+C.name:"")}:{value:"先选项目",sub:i.projectMsg||"项目加载后自动选第一个"};let x;if(i.workersError)x={value:"加载失败",sub:i.workersError};else if(i.workers===null)x={value:i.workersSummary||"未加载",sub:"完整 worker 清单仅全局管理员可见"};else{const b=i.workers.filter(B=>{if(B.alive!==void 0&&B.alive!==null)return!!B.alive;const lt=Qh(B).age;return lt!==null&<<=120}).length;x={value:b+" 在线 / "+i.workers.length,sub:i.workers.length-b+" 离线"}}const G=i.projectMsg.includes("加载失败")?{value:"加载失败",sub:i.projectMsg}:{value:i.projects.length+" 个",sub:C?"当前项目:"+C.name:"未选项目(加载后自动选第一个)"},U=[i.envMsg,i.memberError,i.skillError].filter(b=>b&&b.includes("失败")),Q=i.projectId?U.length?{value:"部分加载失败",sub:U.join(" | ")}:{value:i.envs.length+" 个环境",sub:i.members.length+" 成员 | "+i.skills.length+" 技能 | "+i.apps.length+" 应用 | "+i.mcpServers.length+" MCP"}:{value:"先选项目",sub:"选项目后显示环境 / 成员 / 技能计数"};return s.jsxs("section",{className:"dash-cards","aria-label":"数据卡片",children:[s.jsx(cu,{label:"健康",value:r,sub:i.healthText}),s.jsx(cu,{label:"配额",value:o,sub:i.quotaText}),s.jsx(cu,{label:"任务",value:N.value,sub:N.sub}),s.jsx(cu,{label:"Workers",value:x.value,sub:x.sub}),s.jsx(cu,{label:"项目",value:G.value,sub:G.sub}),s.jsx(cu,{label:"项目数据",value:Q.value,sub:Q.sub})]})}function Ve({id:i,className:r,title:m,children:o}){return s.jsxs("section",{id:i,className:"card"+(r?" "+r:""),children:[m?s.jsx("h2",{children:m}):null,o]})}function Dt({children:i}){return s.jsx("div",{className:"bar",children:i})}function Dl({items:i,empty:r}){return i.length?s.jsx("ul",{className:"data-list",children:i.map((m,o)=>s.jsx("li",{children:m},o))}):s.jsx("div",{className:"muted",children:r||"(暂无)"})}function ru({children:i}){return s.jsx("ul",{className:"data-list",children:i})}function lg(){const i=Mt(),[r,m]=v.useState(""),[o,_]=v.useState(""),[C,N]=v.useState(!1),[x,G]=v.useState(!1),[U,Q]=v.useState(!1),[b,B]=v.useState("docker"),[lt,at]=v.useState(""),[I,V]=v.useState(""),[dt,Ot]=v.useState(""),[te,nt]=v.useState(""),[Xt,ee]=v.useState(""),[ct,yt]=v.useState(""),[le,de]=v.useState(""),Kt=v.useRef(null),Ft=v.useRef(null),Wt=v.useRef(null),[tl,mt]=v.useState("");async function Y(){var Oe,ze,y;if(mt(""),!i.projectId){mt("先选项目");return}if(!r.trim()){mt("prompt 必填");return}const A={project_id:i.projectId,prompt:r.trim()};i.envSelect&&(A.environment_id=i.envSelect),b&&(A.executor=b);const et=I.trim()||lt;et&&(A.model=et),dt&&(A.effort=dt);const Tt=ct.trim()||Xt;if(Tt&&(A.child_model=Tt),le&&(A.child_effort=le),te&&(A.approval=te),U){if(te==="full"){mt("计划模式不能与完全访问同用");return}if(C||x){mt("计划模式不能与 goal 模式同用");return}A.plan=1}x?(A.goal_mode=1,A.no_time_limit=1):C&&(A.goal_mode=1);const gt=o.trim();if(gt){try{A.coordinator_parts=JSON.parse(gt)}catch{mt("协调员子任务 JSON 解析失败");return}if(!Array.isArray(A.coordinator_parts)){mt("协调员子任务必须是数组");return}if(C||x||U){mt("协调员任务不能与 goal/计划模式同用:协调员任务本身不跑模型回合,请取消 goal/计划勾选,或清空协调员 JSON");return}}const ie=Zy(((Oe=Kt.current)==null?void 0:Oe.files)||null,((ze=Ft.current)==null?void 0:ze.files)||null,((y=Wt.current)==null?void 0:y.files)||null);try{const R=await i.dispatch(A,ie);mt(R),m(""),_(""),Kt.current&&(Kt.current.value=""),Ft.current&&(Ft.current.value=""),Wt.current&&(Wt.current.value="")}catch(R){mt("提交失败:"+((R==null?void 0:R.message)||String(R)))}}return s.jsxs(Ve,{id:"nav-newtask",className:"composer",title:"新建任务",children:[s.jsxs(Dt,{children:[s.jsxs("select",{"aria-label":"环境",value:i.envSelect,onChange:A=>i.setEnvSelect(A.target.value),children:[s.jsx("option",{value:"",children:"环境:不指定(走项目仓库)"}),i.envs.map(A=>s.jsxs("option",{value:A.id,disabled:A.status!=="ready",children:[A.name,"(",A.status,")"]},A.id))]}),s.jsxs("select",{"aria-label":"执行器",value:b,onChange:A=>B(A.target.value),children:[s.jsx("option",{value:"docker",children:"docker(默认,多用户下隔离最强)"}),s.jsx("option",{value:"local",children:"local"}),s.jsx("option",{value:"cloud",children:"cloud"})]}),s.jsxs("label",{children:[s.jsx("input",{type:"checkbox",checked:C,onChange:A=>N(A.target.checked)})," goal 模式"]}),s.jsxs("label",{children:[s.jsx("input",{type:"checkbox",checked:x,onChange:A=>G(A.target.checked)})," goal 模式无敌版"]}),s.jsxs("label",{children:[s.jsx("input",{type:"checkbox",checked:U,onChange:A=>Q(A.target.checked)})," 计划模式(只读)"]})]}),s.jsx("textarea",{className:"prompt stacked",placeholder:"任务 prompt(必填)",value:r,onChange:A=>m(A.target.value)}),s.jsx("textarea",{className:"coord stacked",placeholder:"协调员子任务 JSON(可选),留空为普通任务",value:o,onChange:A=>_(A.target.value)}),s.jsxs("div",{className:"muted",children:["协调员格式:[","{",'"name":"后端","prompt":"做后端"',"}",",","{",'"name":"前端","prompt":"做前端"',"}","];提交后 worker 并行建子任务、等齐、合流汇总。不能与 goal/计划模式同用。协调员与 goal/计划互斥:勾了 goal 或计划模式时不要填协调员 JSON,提交前会校验并拦截。子任务费用记父任务创建人账;父任务创建时按分工数预留配额,余额不足时整个父任务被挡下。"]}),s.jsxs(Dt,{children:[s.jsxs("select",{"aria-label":"模型",value:lt,onChange:A=>at(A.target.value),children:[s.jsx("option",{value:"",children:"模型:默认(跟随配置)"}),i.models.map(A=>s.jsx("option",{value:A,children:A},A))]}),i.modelCustomVisible?s.jsx("input",{type:"text",placeholder:"手动输入模型(中转列表取不到时用)",value:I,onChange:A=>V(A.target.value)}):null,s.jsxs("select",{"aria-label":"推理强度",value:dt,onChange:A=>Ot(A.target.value),children:[s.jsx("option",{value:"",children:"推理强度:默认(跟随配置)"}),Yh.map(A=>s.jsx("option",{value:A,children:A},A))]}),s.jsxs("select",{"aria-label":"权限",value:te,onChange:A=>nt(A.target.value),children:[s.jsx("option",{value:"",children:"权限:默认(跟随配置)"}),s.jsx("option",{value:"ask",children:"请求批准"}),s.jsx("option",{value:"auto",children:"帮我批准"}),s.jsx("option",{value:"full",children:"完全访问"})]})]}),s.jsxs(Dt,{children:[s.jsxs("select",{"aria-label":"子任务模型",value:Xt,onChange:A=>ee(A.target.value),children:[s.jsx("option",{value:"",children:"子任务模型:跟随本任务模型"}),i.models.map(A=>s.jsx("option",{value:A,children:A},A))]}),s.jsx("input",{type:"text",placeholder:"子任务模型手动输入(中转列表里没有时填,如 gpt-6-luna;留空=跟随本任务模型)",value:ct,onChange:A=>yt(A.target.value)}),s.jsxs("select",{"aria-label":"子任务强度",value:le,onChange:A=>de(A.target.value),children:[s.jsx("option",{value:"",children:"子任务强度:跟随本任务强度"}),Yh.map(A=>s.jsx("option",{value:A,children:A},A))]})]}),s.jsx("div",{className:"muted",children:"子任务模型/强度是派生默认:本任务用 marker task-create 或协调员分工派生的子任务,未显式写模型/强度时用它;块/分工里显式写了则覆盖它。也可以不在这里填、直接在 prompt 里写“子任务用 gpt-6-luna max”,建任务时服务端会解析成同一对字段(任务详情里可核对)。"}),C?s.jsx("div",{className:"muted",children:"goal 模式:输入框内容即目标,不需要专用 prompt 与验收命令;不限轮次,模型确认达成后在回复末尾输出“目标已达成”即结束,未达成会一直运行,可用取消停止。"}):null,x?s.jsx("div",{className:"muted",children:"goal 模式无敌版:goal 模式 + 任务不限时(去掉 worker/docker 总超时;单轮 600 秒护栏还在,不限的是任务总时长与总轮次)。服务端默认的自动续跑与断点镜像同时生效。"}):null,s.jsxs(Dt,{children:[s.jsx("span",{className:"muted",children:"附件(放进任务工作区,图片与普通文件不区分):"}),s.jsx("input",{ref:Kt,type:"file",multiple:!0,title:"文件"}),s.jsx("input",{ref:Ft,type:"file",multiple:!0,title:"文件夹",webkitdirectory:""}),s.jsx("input",{ref:Wt,type:"file",accept:"image/*",multiple:!0,title:"图片"})]}),s.jsxs(Dt,{children:[s.jsx("button",{className:"seal",onClick:Y,children:"提交任务"}),s.jsx("span",{className:"muted",children:tl||i.dispatchMsg})]})]})}function ag(i){const r=(i.prompt||"").trim();return r?[...r][0]:i.id?[...i.id][0]:"?"}function zc(i){const r=Math.max(0,Math.floor(i));if(r<60)return r+"秒";if(r<3600){const _=Math.floor(r/60),C=r%60;return C?_+"分"+C+"秒":_+"分"}if(r<86400){const _=Math.floor(r/3600),C=Math.floor(r%3600/60);return C?_+"小时"+C+"分":_+"小时"}const m=Math.floor(r/86400),o=Math.floor(r%86400/3600);return o?m+"天"+o+"小时":m+"天"}function fu(i){if(i==null)return null;const r=Number(i);return Number.isFinite(r)?r:null}function ng(i){const r=Date.now()/1e3,m=fu(i.started_at),o=fu(i.finished_at),_=fu(i.created_at);return m!==null&&o!==null&&o>=m?[zc(o-m),"总耗时:开始→结束(末次认领的执行时长)"]:m!==null&&i.status==="running"?["进行中 "+zc(r-m),"总耗时:进行中,已用时长从开始算起,随列表刷新更新"]:_!==null&&o!==null&&o>=_?[zc(o-_),"总耗时:创建→完成(含排队等待;无开始时间时的兜底口径)"]:_!==null&&(i.status==="queued"||i.status==="running")?[(i.status==="queued"?"排队中 ":"进行中 ")+zc(r-_),"总耗时:从创建算起的已用时长(无开始时间时的兜底口径)"]:["—","总耗时:无可用时间戳"]}function ug(i){const r=fu(i.total_tokens);if(r!==null)return[r.toLocaleString()+" token","token:服务端列表 total_tokens"];const m=fu(i.actual_tokens),o=fu(i.estimated_tokens);return m!==null||o!==null?[((m||0)+(o||0)).toLocaleString()+" token","token:actual + estimated 分项加总"]:["token —","token:列表接口未返回 token 字段(服务端需在任务列表里补 total_tokens);单任务真数在任务详情的成本里可查,本行不逐个调成本接口、不编造"]}function ig({onNew:i}){const r=Mt();return s.jsxs("div",{children:[s.jsxs("div",{className:"search-box",children:[s.jsxs("svg",{viewBox:"0 0 24 24",width:"18",height:"18",fill:"none",stroke:"currentColor",strokeWidth:"1.8",strokeLinecap:"round","aria-hidden":"true",children:[s.jsx("circle",{cx:"11",cy:"11",r:"7"}),s.jsx("path",{d:"M21 21l-4.3-4.3"})]}),s.jsx("input",{type:"text",placeholder:"搜索","aria-label":"搜索任务",value:r.searchText,onChange:m=>r.setSearchText(m.target.value)})]}),s.jsx("div",{className:"bar",children:s.jsxs("select",{"aria-label":"项目",value:r.projectId,onChange:m=>r.selectProject(m.target.value),children:[r.projects.length===0?s.jsx("option",{value:"",children:"(先加载项目)"}):null,r.projects.map(m=>s.jsxs("option",{value:m.id,children:[m.name," (",Me(m.id),")"]},m.id))]})}),s.jsxs("div",{className:"list-section-head",children:[s.jsx("span",{children:"主要聊天"}),s.jsx("button",{type:"button","aria-label":"派发新任务",title:"派发新任务",onClick:i,children:"+"})]}),s.jsxs("div",{className:"bar",children:[s.jsxs("select",{"aria-label":"状态筛选",value:r.statusFilter,onChange:m=>r.setStatusFilter(m.target.value),children:[s.jsx("option",{value:"",children:"状态:全部"}),["queued","running","completed","failed","interrupted","merged","merge-failed"].map(m=>s.jsx("option",{value:m,children:m},m))]}),s.jsxs("select",{"aria-label":"执行器筛选",value:r.executorFilter,onChange:m=>r.setExecutorFilter(m.target.value),children:[s.jsx("option",{value:"",children:"执行器:全部"}),s.jsx("option",{value:"local",children:"local"}),s.jsx("option",{value:"docker",children:"docker"}),s.jsx("option",{value:"cloud",children:"cloud"})]})]}),s.jsxs("div",{className:"bar",children:[s.jsx("button",{onClick:r.refreshTasks,children:"刷新"}),s.jsx("span",{className:"muted",children:r.taskCountText})]}),r.taskError?s.jsx("div",{className:"err",children:r.taskError}):r.projectId?r.filteredTasks.length===0?s.jsx("div",{className:"muted",children:r.tasks.length?"没有符合筛选条件的任务":"该项目暂无任务"}):s.jsx("ul",{className:"issue-list",children:r.filteredTasks.slice().reverse().map(m=>{var G;const[o,_]=ng(m),[C,N]=ug(m),x=ag(m);return s.jsxs("li",{className:"issue-item"+(((G=r.currentTask)==null?void 0:G.id)===m.id?" selected":""),onClick:()=>r.showDetail(m.id),children:[s.jsx("span",{className:"issue-state st-"+m.status,"aria-hidden":"true"}),s.jsx("span",{className:"issue-first",title:"任务首字:"+x,"aria-hidden":"true",children:x}),s.jsxs("span",{style:{flex:1,minWidth:0},children:[s.jsxs("div",{className:"issue-title",children:[m.plan?"[计划] ":"",m.forked_from?"[分支] ":"",m.coordinator_parts?"[协调] ":"",m.parent_task_id?"[协调子任务] ":"",String(m.prompt||Me(m.id)).slice(0,60)]}),s.jsxs("div",{className:"issue-meta",children:[Me(m.id)," · ",s.jsx("span",{className:"st-"+m.status,children:m.status})," · ",m.executor||"",m.updated_at?" · "+new Date(m.updated_at*1e3).toLocaleString():""," · ",s.jsxs("span",{title:_,children:["耗时 ",o]})," · ",s.jsx("span",{title:N,children:C})]})]})]},m.id)})}):s.jsx("div",{className:"muted",children:"先选项目"})]})}function cg(i){const r=v.useRef(null),[m,o]=v.useState(!0),_=v.useRef(!0),C=()=>{const N=r.current;if(!N)return;const x=N.scrollHeight-N.scrollTop-N.clientHeight<80;_.current=x,o(x)};return v.useEffect(()=>{_.current&&r.current&&(r.current.scrollTop=r.current.scrollHeight)},[i]),{ref:r,following:m,onScroll:C}}function sg({msg:i}){if(i.role==="status")return s.jsx("div",{className:"chat-status",children:i.text});const r=i.label||(i.role==="user"?"用户":i.role==="assistant"?"助手":"工具");return s.jsxs("div",{className:"gh-comment "+i.role,children:[s.jsx("div",{className:"gh-comment-header",children:s.jsx("span",{className:"gh-comment-author",children:r})}),s.jsx("div",{className:"gh-comment-body",children:i.text})]})}function kh({messages:i,empty:r}){const{ref:m,following:o,onScroll:_}=cg(i.length);return s.jsxs("div",{className:"chat-list",ref:m,onScroll:_,children:[i.length===0?s.jsx("div",{className:"muted",children:r||"(暂无消息)"}):i.map(C=>s.jsx(sg,{msg:C},C.id)),!o&&i.length>0?s.jsx("button",{className:"chat-follow",onClick:()=>{m.current&&(m.current.scrollTop=m.current.scrollHeight)},children:"回到最新 ↓"}):null]})}function fg({onSend:i,disabled:r,placeholder:m}){const[o,_]=v.useState(""),C=()=>{const N=o.trim();!N||r||(i(N),_(""))};return s.jsxs("div",{className:"gh-comment-box",children:[s.jsx("textarea",{"aria-label":"会话输入",rows:3,placeholder:m||"消息",value:o,disabled:r,onChange:N=>_(N.target.value),onKeyDown:N=>{N.key==="Enter"&&!N.shiftKey&&(N.preventDefault(),C())}}),s.jsx("div",{className:"gh-comment-footer",children:s.jsx("button",{type:"button",className:"primary","aria-label":"发送",disabled:r||!o.trim(),onClick:C,children:"发送"})})]})}function Xh(i,r){const m=i.id||"trace-"+r;if(i.kind==="message")return{id:m,role:"assistant",text:(i.phase?"["+i.phase+"] ":"")+(i.text||""),label:"助手"};if(i.kind==="exec"||i.kind==="file"||i.kind==="tool"||i.kind==="call"){const o=i.kind==="exec"?"命令":i.kind==="file"?"文件":i.kind==="tool"?"工具":"调用";return{id:m,role:"tool",text:or(i),label:o}}return i.kind==="plan"?{id:m,role:"status",text:i.text||""}:i.kind==="reasoning"?{id:m,role:"status",text:"思考:"+(i.summary||"")}:i.kind==="agent-start"?{id:m,role:"status",text:"子代理开始:"+(i.name||"")+(i.prompt?"("+i.prompt+")":"")}:i.kind==="agent-done"?{id:m,role:"status",text:"子代理结束:"+(i.name||"")+" "+(i.status||"")}:{id:m,role:"status",text:or(i)}}function rg(){const i=Mt(),r=v.useMemo(()=>{const m=i.currentTask,o=m&&m.prompt?[{id:"task-prompt",role:"user",text:m.prompt}]:[],C=(i.traceKind?i.traceEntries.filter(x=>x.kind===i.traceKind):i.traceEntries).map((x,G)=>Xh(x,G)),N=i.traceDone?[{id:"trace-done",role:"status",text:"链路已结束("+i.traceDone+")"}]:[];return[...o,...C,...N]},[i.currentTask,i.traceEntries,i.traceKind,i.traceDone]);return s.jsxs(s.Fragment,{children:[s.jsx("div",{className:"muted",children:"实时链路"}),s.jsxs(Dt,{children:[s.jsxs("select",{"aria-label":"链路类型",value:i.traceKind,onChange:m=>i.setTraceKind(m.target.value),children:[s.jsx("option",{value:"",children:"链路类型:全部"}),["exec","file","call","tool","message","reasoning","plan","agent-start","agent-done"].map(m=>s.jsx("option",{value:m,children:m==="exec"?"命令":m==="file"?"文件":m==="call"?"调用":m==="tool"?"工具":m==="message"?"消息":m==="reasoning"?"思考":m==="plan"?"计划":"子代理"},m))]}),s.jsx("button",{onClick:i.downloadTrace,children:"下载完整链路"}),s.jsx("span",{className:"muted",children:i.traceMsg})]}),i.traceNote?s.jsx("div",{children:s.jsx("span",{className:"err",children:i.traceNote})}):null,s.jsx("div",{className:"chat-column",children:s.jsx(kh,{messages:r,empty:"(暂无链路条目)"})}),s.jsx("div",{className:"muted",children:"对话时间线:首条是本任务 prompt(用户);助手回复来自 message 条目,命令/文件/工具/调用是工具评论框,计划/思考/子代理开始结束是时间线上的状态文字。流式进行中时新条目实时追加;你在底部时自动跟随,往上翻时暂停跟随,点“回到最新”恢复。"})]})}function og(){const i=Mt(),r=i.currentTask,m=v.useMemo(()=>{if(!r)return[];if(i.chatHistory)return i.chatHistory.messages.map(b=>({id:b.id,role:b.role,text:b.text,label:b.label}));const G=r.prompt?[{id:"task-prompt",role:"user",text:r.prompt}]:[],U=i.traceEntries.filter(b=>b.kind==="message").map((b,B)=>Xh(b,B)),Q=i.followups.map((b,B)=>({id:"followup-"+B,role:"user",text:String(b.body||""),label:"后续消息"}));return[...G,...U,...Q]},[r,i.chatHistory,i.traceEntries,i.followups]);if(!r)return s.jsx("div",{className:"muted",children:"点任务行查看详情后,这里显示继续会话"});const o=su.includes(r.status),_=["queued","running"].includes(r.status),C=o||_,N=o?"终态任务:发送走继续接口(同一会话再跑一轮)":_?"运行中/排队中:发送走后续消息直接发送(跑完当前轮后优先执行)":"merged/merge-failed 任务不可继续,也不可发后续消息",x=i.chatHistory?i.chatHistory.legacy_continue_count??null:null;return s.jsxs("div",{className:"chat-column",children:[x!==null&&x>0?s.jsxs("div",{className:"muted",children:["此前有 ",x," 条继续消息发生在持久化之前,未入库,上面历史不完整;部署后新发的继续消息会正常显示。"]}):null,i.chatHistory&&i.chatHistory.trace_note?s.jsx("div",{className:"muted",children:i.chatHistory.trace_note}):null,s.jsx(kh,{messages:m,empty:"(暂无会话消息)"}),s.jsx(fg,{disabled:!C,placeholder:"消息",onSend:G=>void i.sendChatMessage(G)}),s.jsxs("div",{className:"muted",children:[N,"。",i.chatMsg]})]})}function dg(){return Mt().currentTask?s.jsxs("div",{className:"conversation",children:[s.jsx(rg,{}),s.jsx("div",{className:"conv-divider"}),s.jsx(og,{})]}):s.jsx("div",{className:"conversation",children:s.jsx("div",{className:"muted",children:"在任务列表里点一条任务,对话和详情就切到该任务;也可以在上面派发一个新任务。"})})}function mg(){const i=Mt(),r=i.currentTask,[m,o]=v.useState("");if(!r)return null;const _=su.includes(r.status),C=["failed","interrupted","merge-failed"].includes(r.status);return s.jsxs(s.Fragment,{children:[s.jsxs(Dt,{children:[s.jsx("button",{disabled:!C,title:C?"":"仅 failed/interrupted/merge-failed 可重试",onClick:()=>i.doAction("retry"),children:"重试"}),s.jsx("button",{className:"danger",disabled:_,title:_?"终态任务不可取消":"",onClick:()=>i.doAction("cancel"),children:"取消"}),s.jsx("button",{disabled:r.status!=="completed",title:r.status!=="completed"?"仅 completed 可合并":"",onClick:()=>i.doAction("merge"),children:"合并(completed → merged)"}),s.jsx("button",{disabled:r.status!=="merged",title:r.status!=="merged"?"仅 merged 可提 PR":"",onClick:()=>i.doAction("pr"),children:"提 PR(须 merged)"}),_?s.jsx("button",{onClick:()=>i.setForkVisible(!0),children:"分支"}):null,s.jsx("button",{onClick:i.taskCost,children:"查成本"}),s.jsxs("span",{className:"muted",children:[i.actionMsg," ",i.actionReasons]})]}),i.forkVisible?s.jsxs("div",{children:[s.jsx("input",{type:"text",placeholder:"分支首轮消息(必填,带历史的新会话)",style:{width:"100%",boxSizing:"border-box",margin:"6px 0"},value:m,onChange:N=>o(N.target.value)}),s.jsxs(Dt,{children:[s.jsx("button",{className:"seal",onClick:async()=>{await i.forkTask(m),o("")},children:"创建分支任务"}),s.jsx("span",{className:"muted",children:i.forkMsg})]})]}):null]})}function hg(){const i=Mt(),[r,m]=v.useState("uncommittedChanges"),[o,_]=v.useState(""),[C,N]=v.useState(""),[x,G]=v.useState("");return s.jsxs(s.Fragment,{children:[s.jsx("h3",{children:"评审队列"}),s.jsxs("select",{"aria-label":"评审目标",value:r,onChange:U=>m(U.target.value),children:[s.jsx("option",{value:"uncommittedChanges",children:"未提交改动(工作区)"}),s.jsx("option",{value:"baseBranch",children:"与分支的差异"}),s.jsx("option",{value:"commit",children:"指定提交"}),s.jsx("option",{value:"custom",children:"自定义评审指令"})]}),s.jsx("input",{className:"stacked",placeholder:"与分支的差异用:分支名(如 main)",style:{width:"100%"},value:o,onChange:U=>_(U.target.value)}),s.jsx("input",{className:"stacked",placeholder:"指定提交用:提交 sha",style:{width:"100%"},value:C,onChange:U=>N(U.target.value)}),s.jsx("textarea",{placeholder:"自定义评审指令用:评审指令正文",value:x,onChange:U=>G(U.target.value)}),s.jsxs(Dt,{children:[s.jsx("button",{className:"primary",onClick:()=>void i.postReview({target_type:r,branch:o.trim()||void 0,sha:C.trim()||void 0,instructions:x.trim()||void 0}),children:"登记评审需求"}),s.jsx("button",{onClick:i.refreshReviews,children:"刷新队列"}),s.jsx("span",{className:"muted",children:i.reviewMsg})]}),s.jsx("div",{className:"muted",children:"本基线只登记评审需求(requested),不自动跑评审;评审是否执行、结果如何,以评审队列里的记录为准。"}),i.reviewError?s.jsx("div",{className:"muted",children:i.reviewError}):i.reviews.length===0?s.jsx("div",{className:"muted",children:"点任务行查看详情后,点“刷新队列”查看评审记录"}):s.jsx(ru,{children:i.reviews.map(U=>{let Q=U.target_type||"";return U.branch&&(Q+=":"+U.branch),U.sha&&(Q+=":"+String(U.sha).slice(0,12)),U.instructions&&(Q+=":"+String(U.instructions).slice(0,80)),s.jsxs("li",{children:[s.jsxs("span",{className:"row-main",children:[s.jsx("b",{children:"评审"})," ",Me(U.id)," | ",Q," | ",s.jsx("span",{className:"st-"+(U.status||""),children:U.status}),U.created_at?" | "+Xa(U.created_at):"",s.jsx("br",{}),U.findings?String(U.findings).slice(0,1e3):s.jsx("span",{className:"muted",children:"(暂无 findings:评审尚未执行或尚未写回)"})]}),s.jsx("button",{onClick:()=>i.submitFindings(U.id),children:"写 findings"})]},U.id)})})]})}function vg(){const i=Mt();return i.currentTask?i.currentTask.coordinator_parts?i.coordChildren.length===0?s.jsx("div",{className:"muted",children:"(子任务尚未创建)"}):s.jsx(ru,{children:i.coordChildren.map(r=>s.jsx("li",{children:s.jsxs("span",{className:"row-main",children:["分工 ",s.jsx("b",{children:r.coordinator_part||""})," | 状态 ",s.jsx("span",{className:"st-"+r.status,children:r.status})," | ",Me(r.id)," | ",String(r.prompt||"").slice(0,60)]})},r.id))}):s.jsx("div",{className:"muted",children:"(非协调员任务)"}):s.jsx("div",{className:"muted",children:"点任务行查看详情后,这里显示协调员的子任务"})}function yg(){const i=Mt();if(!i.currentTask)return s.jsx("div",{className:"muted",children:"点任务行查看详情后,这里显示子代理链路"});if(i.agents.length===0)return s.jsxs("div",{className:"muted",children:["(暂无子代理)",i.agentsNote?s.jsx("span",{className:"err",children:i.agentsNote}):null]});const r={main:"主任务",delegate:"委派子任务",model:"模型子代理"},m={running:"进行中",completed:"已完成",failed:"失败",interrupted:"已中断",unknown:"未知"};return s.jsx(ru,{children:i.agents.map((o,_)=>s.jsx("li",{children:s.jsxs("span",{className:"row-main",children:[s.jsxs("b",{children:[r[o.kind||""]||o.kind||"",":"]}),o.name||""," ",s.jsx("span",{className:"st-"+(o.status||""),children:m[o.status||""]||o.status||"?"})," | 过程调用 "+(o.calls?o.calls.length:0)+" 条",o.updated_ts?" | 最近活动 "+Xa(o.updated_ts/1e3):"",o.prompt?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),s.jsxs("span",{className:"muted",children:["任务:",o.prompt]})]}):null,o.calls&&o.calls.length?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),o.calls.slice(-50).map((C,N)=>s.jsx("div",{children:or(C)},N))]}):s.jsxs(s.Fragment,{children:[s.jsx("br",{}),s.jsx("span",{className:"muted",children:"(暂无过程调用)"})]})]})},_))})}function gg(){const i=Mt();return s.jsxs(s.Fragment,{children:[s.jsxs(Dt,{children:[s.jsx("button",{onClick:i.refreshApprovals,children:"刷新审批队列"}),s.jsx("span",{className:"muted",children:i.approvalMsg})]}),s.jsx("div",{className:"muted",children:"待审批项需人工点记录允许或记录拒绝;超时默认拒绝。当前版本只留回执,不拦截实时行为(实时请求的事前拦截待 host 接入后生效,在此之前实时行为不变)。"}),s.jsx(Dl,{empty:"(暂无审批项)",items:i.approvals.map(r=>s.jsxs("span",{className:"row-main",children:["[",r.kind,"|",r.status,"] ",r.summary,["approved","denied","expired"].includes(r.status||"")?"|回执:"+(r.decision||r.status)+" by "+String(r.decided_by||"").slice(0,8)+" "+Xa(r.decided_at):"",r.status==="pending"?s.jsxs(s.Fragment,{children:[" ",s.jsx("button",{onClick:()=>i.decideApproval(r.id,"approved"),children:"记录允许"})," ",s.jsx("button",{onClick:()=>i.decideApproval(r.id,"denied"),children:"记录拒绝"})]}):null]},r.id))})]})}function pg(){const i=Mt(),[r,m]=v.useState(""),[o,_]=v.useState("");return s.jsxs(s.Fragment,{children:[s.jsx("input",{placeholder:"目标任务 id(与本任务同项目;对方运行中排队执行、已终态则续跑复活,绝不打断当前轮)",style:{width:"100%"},value:r,onChange:C=>m(C.target.value)}),s.jsx("textarea",{placeholder:"消息正文(以本任务名义发出;对方 agent 会看到发送方任务 id,可照此回消息)",value:o,onChange:C=>_(C.target.value)}),s.jsxs(Dt,{children:[s.jsx("button",{onClick:async()=>{await i.postPeer("queued",r,o),_("")},children:"排队发给对方"}),s.jsx("button",{className:"primary",onClick:async()=>{await i.postPeer("direct",r,o),_("")},children:"直接发送"}),s.jsx("span",{className:"muted",children:i.peerMsg})]}),s.jsx(Dl,{empty:"(本任务尚未发出任务间消息)",items:i.peerMessages.map((C,N)=>s.jsxs("span",{className:"row-main",children:["[发给 ",String(C.task_id).slice(0,8),"|",C.mode,"|",C.status,"] ",String(C.body||"").slice(0,120)]},N))})]})}function Sg(){const i=Mt(),[r,m]=v.useState(""),o=i.currentTask?["completed","failed","interrupted"].includes(i.currentTask.status):!1;return s.jsxs(s.Fragment,{children:[s.jsx("div",{className:"muted",children:i.threadText||"点任务行查看详情后,这里显示线程元数据"}),s.jsxs(Dt,{children:[s.jsx("input",{type:"text",placeholder:"新线程名",value:r,onChange:_=>m(_.target.value)}),s.jsx("button",{disabled:!o,onClick:()=>i.threadAction("rename",r),children:"重命名"}),s.jsx("button",{disabled:!o,onClick:()=>i.threadAction("archive",""),children:"归档"}),s.jsx("button",{disabled:!o,onClick:()=>i.threadAction("unarchive",""),children:"取消归档"}),s.jsx("button",{disabled:!o,onClick:()=>i.threadAction("compact",""),children:"压缩"}),s.jsx("span",{className:"muted",children:i.threadGateNote}),s.jsx("span",{className:"muted",children:i.threadMsg})]}),s.jsx(Dl,{empty:"暂无其他线程",items:i.threadList.map((_,C)=>s.jsxs("span",{className:"row-main",children:[_.name||_.thread_name||_.id||""," [",_.id||"","]",_.archived?" [已归档]":"",_.cwd?" "+_.cwd:""]},C))}),s.jsx("div",{className:"muted",children:"限制:Docker/异机任务线程操作一律 409;merged/merge-failed 不可改;turn/steer 与 review/start 不在本批。"})]})}function bg(){const i=Mt(),r=i.currentTask;return r?s.jsxs(Ve,{id:"nav-detail",title:"任务详情",children:[s.jsxs("div",{children:["状态 ",s.jsx("b",{className:"st-"+r.status,children:r.status}),"(",Qy[r.status]||"未知状态",")",s.jsx("br",{}),"任务 ",r.id," | turn ",Me(r.turn_id)," | root turn ",Me(r.root_turn_id),s.jsx("br",{}),"executor ",r.executor||""," | 主机 ",r.worktree_host||"",s.jsx("br",{}),"模型 ",r.model||"默认"," | 强度 ",r.effort||"默认",r.child_model||r.child_effort?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),"子任务默认 ",(r.child_model||"跟随本任务")+" / "+(r.child_effort||"跟随本任务")]}):null,s.jsx("br",{}),"权限 ",r.approval||"默认"," | 项目 ",Me(r.project_id),s.jsx("br",{}),"更新 ",Xa(r.updated_at),r.plan?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),"计划模式(只读)"]}):null,r.forked_from?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),"分支自 ",Me(r.forked_from)]}):null,r.goal_mode?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),r.no_time_limit?"goal 模式无敌版(不限时)":"goal 模式(不限轮次,模型宣告完成)"]}):r.verify_command?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),"goal 模式(旧语义,验收命令)"]}):null,r.coordinator_parts?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),"协调员:",r.coordinator_parts.length," 个子任务(",r.coordinator_parts.map(m=>m.name).join("、"),")"]}):null,r.parent_task_id?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),"协调子任务,父任务 ",Me(r.parent_task_id),r.coordinator_part?"("+r.coordinator_part+")":""]}):null,r.attachments&&r.attachments.length?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),"附件:",r.attachments.join("、")]}):null,r.pr_url?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),s.jsxs("a",{href:r.pr_url,target:"_blank",rel:"noopener noreferrer",children:["PR #",r.pr_number]})]}):null,s.jsx("br",{}),"prompt:",r.prompt||""]}),s.jsx(mg,{}),s.jsx("pre",{children:i.detailBody||"(暂无 result/error 输出)"}),i.costBox?s.jsx("pre",{children:i.costBox}):null,s.jsx("h3",{children:"协调子任务"}),s.jsx(vg,{}),s.jsx("h3",{children:"子代理链路"}),s.jsx(yg,{}),s.jsx("h3",{children:"评审队列"}),s.jsx(hg,{}),s.jsx("h3",{children:"运行时审批队列"}),s.jsx(gg,{}),s.jsx("h3",{children:"给其他任务发消息"}),s.jsx(pg,{}),s.jsx("h3",{children:"线程"}),s.jsx(Sg,{})]}):s.jsx(Ve,{id:"nav-detail",title:"任务详情",children:s.jsx("div",{className:"muted",children:"点任务行查看详情"})})}function jg(){const i=Mt(),[r,m]=v.useState(""),[o,_]=v.useState(""),[C,N]=v.useState([]),[x,G]=v.useState(null),[U,Q]=v.useState(!1),[b,B]=v.useState(""),[lt,at]=v.useState(""),I=v.useCallback(async()=>{if(!i.keySaved){N([]);return}try{const nt=await tt($.userKeys);N(nt.keys||[]),at("")}catch(nt){at(nt instanceof Error?nt.message:"加载 key 列表失败")}},[i.keySaved]);v.useEffect(()=>{I()},[I]);const V=async()=>{B(""),at(""),Q(!1);try{const nt=await tt($.userKeys,"POST",{label:o.trim()||"api-key"});G(nt),_(""),B("新 key 已生成,只显示一次:复制后请妥善保存。"),await I()}catch(nt){at(nt instanceof Error?nt.message:"生成 key 失败")}},dt=async()=>{if(x)try{await navigator.clipboard.writeText(x.api_key),Q(!0)}catch{at("复制失败:请手动选中上方 key 文本复制")}},Ot=()=>{x&&(i.saveKeyInput(x.api_key),G(null),B("已存为当前浏览器 key(原有 key 未被撤销,需要时可单独撤销)。"))},te=async nt=>{const Xt=(()=>{try{return _l().slice(-4)}catch{return""}})(),ee=nt.id==="legacy"?"(主 key:撤销后当前浏览器保存的 key 也可能随之失效,需重新登录拿新 key)":nt.hint&&nt.hint===Xt?"(这就是本浏览器当前在用的 key,撤销后本页会立刻未连接)":"";if(window.confirm(`撤销 key「${nt.label}」(后 4 位 ${nt.hint||"无"})吗?${ee}撤销不可恢复。`)){B(""),at("");try{await tt($.userKey(nt.id),"DELETE"),B(`已撤销 key「${nt.label}」。`),await I()}catch(ct){at(ct instanceof Error?ct.message:"撤销失败")}}};return s.jsxs(Ve,{id:"nav-key",title:"API Key",children:[s.jsxs(Dt,{children:[s.jsx("input",{type:"password",placeholder:"粘贴你的 X-API-Key(与调 API 用的同一个)",autoComplete:"off",value:r,onChange:nt=>m(nt.target.value)}),s.jsx("button",{className:"primary",onClick:()=>{i.saveKeyInput(r),m("")},children:"保存到本浏览器"}),s.jsx("button",{onClick:i.clearKeyInput,children:"清除"}),s.jsx("span",{className:"muted",children:i.keyState})]}),s.jsx("div",{children:s.jsx("span",{className:"muted",children:i.quotaText})}),!i.keySaved&&s.jsx("div",{children:s.jsx("span",{className:"muted",children:"先粘贴并保存已有 key,再生成或管理 key(生成接口本身也要用已有 key 鉴权)。"})}),i.keySaved&&s.jsxs(s.Fragment,{children:[s.jsxs(Dt,{children:[s.jsx("input",{placeholder:"新 key 备注(可选,默认 api-key,如 harness2-temp)",autoComplete:"off",value:o,onChange:nt=>_(nt.target.value)}),s.jsx("button",{className:"primary",onClick:()=>void V(),children:"生成新 key"}),s.jsx("button",{onClick:()=>void I(),children:"刷新列表"})]}),x&&s.jsxs("div",{children:[s.jsx("div",{children:s.jsxs("span",{className:"muted",children:["新 key(",x.label,",后 4 位 ",x.hint,"):只显示一次,关闭后无法再查看。"]})}),s.jsxs(Dt,{children:[s.jsx("input",{type:"text",readOnly:!0,value:x.api_key,onFocus:nt=>nt.target.select()}),s.jsx("button",{className:"primary",onClick:()=>void dt(),children:U?"已复制":"复制"}),s.jsx("button",{onClick:Ot,children:"存为当前浏览器 key"}),s.jsx("button",{onClick:()=>G(null),children:"关闭(不再显示)"})]})]}),b&&s.jsx("div",{children:s.jsx("span",{className:"muted",children:b})}),lt&&s.jsx("div",{children:s.jsx("span",{children:lt})}),s.jsxs("div",{children:[C.length===0&&s.jsx("span",{className:"muted",children:"暂无 key(或列表加载失败,见上方报错)。"}),C.map(nt=>s.jsxs("div",{children:[s.jsx("span",{children:nt.label})," ",s.jsxs("span",{className:"muted",children:["后 4 位 ",nt.hint||"无"," · 创建 ",Xa(nt.created_at)," · 最近使用 ",nt.last_used_at?Xa(nt.last_used_at):"未使用过"]})," ",nt.revoked_at?s.jsxs("span",{className:"muted",children:["已撤销(",Xa(nt.revoked_at),")"]}):s.jsx("button",{onClick:()=>void te(nt),children:"撤销"})]},nt.id))]})]})]})}function xg(){const i=Mt();return s.jsxs(Ve,{id:"adminCard",title:s.jsxs(s.Fragment,{children:["管理区 ",s.jsx("span",{className:"muted",children:"(仅全局管理员)"})]}),children:[s.jsx("div",{className:"muted",children:i.adminGate}),i.adminBody?s.jsxs(s.Fragment,{children:[s.jsxs(Dt,{children:[s.jsx("input",{type:"text",placeholder:"要查配额的用户 id",value:i.adminUserId,onChange:r=>i.setAdminUserId(r.target.value)}),s.jsx("button",{onClick:i.adminQuota,children:"查该用户配额"})]}),s.jsxs(Dt,{children:[s.jsx("button",{onClick:i.adminSweep,children:"手动触发 sweep(清理陈旧 running 任务)"}),s.jsx("button",{onClick:i.adminBackup,children:"手动触发备份"})]}),s.jsx("div",{className:"muted",children:i.adminResult})]}):null]})}function Tg(){const i=Mt(),[r,m]=v.useState(""),[o,_]=v.useState(""),[C,N]=v.useState("");return s.jsxs(Ve,{id:"nav-projects",title:"项目",children:[s.jsxs(Dt,{children:[s.jsxs("select",{"aria-label":"项目",value:i.projectId,onChange:x=>i.selectProject(x.target.value),children:[i.projects.length===0?s.jsx("option",{value:"",children:"(先加载项目)"}):null,i.projects.map(x=>s.jsxs("option",{value:x.id,children:[x.name," (",Me(x.id),")"]},x.id))]}),s.jsx("button",{onClick:i.refreshProjects,children:"刷新"})]}),s.jsxs(Dt,{children:[s.jsx("input",{type:"text",placeholder:"新项目名",value:r,onChange:x=>m(x.target.value)}),s.jsxs("select",{"aria-label":"从 GitHub 仓库选",value:o,onChange:x=>{_(x.target.value),x.target.value&&N(x.target.value)},children:[s.jsx("option",{value:"",children:"(从 GitHub App 安装仓库里选,或手动输入)"}),i.githubRepos.map(x=>s.jsx("option",{value:x.clone_url,children:x.repo_full_name},x.clone_url))]}),s.jsx("input",{type:"text",placeholder:"repo_url(可选)",value:C,onChange:x=>N(x.target.value)}),s.jsx("button",{onClick:async()=>{await i.createProject(r,C),m(""),N(""),_("")},children:"新建项目"})]}),s.jsx("span",{className:"muted",children:i.projectMsg})]})}function _g(){const i=Mt(),[r,m]=v.useState(""),[o,_]=v.useState("contributor"),C=i.projects.find(N=>N.id===i.projectId);return s.jsxs(Ve,{title:"项目成员",children:[C?s.jsxs("div",{className:"muted",children:[C.name," | ",C.id," | 仓库 ",C.repo_url||"未绑定"," | 我的角色 ",C.my_role||"未知"]}):s.jsx("div",{className:"muted",children:"先选项目"}),s.jsxs(Dt,{children:[s.jsx("input",{type:"text",placeholder:"成员 user_id 或用户名",value:r,onChange:N=>m(N.target.value)}),s.jsxs("select",{"aria-label":"邀请角色",value:o,onChange:N=>_(N.target.value),children:[s.jsx("option",{value:"viewer",children:"viewer(只读)"}),s.jsx("option",{value:"contributor",children:"contributor(可建任务)"}),s.jsx("option",{value:"maintainer",children:"maintainer(可批合并)"}),s.jsx("option",{value:"owner",children:"owner(可管项目)"})]}),s.jsx("button",{onClick:async()=>{await i.inviteMember(r,o),m("")},children:"邀请成员"}),s.jsx("button",{onClick:i.refreshMembers,children:"刷新成员"})]}),i.memberError?s.jsx("div",{className:"muted",children:i.memberError}):i.members.length===0?s.jsx("div",{className:"muted",children:"先选项目"}):s.jsx(ru,{children:i.members.map(N=>{const x=(N.username||"")+(N.username?"("+String(N.user_id).slice(0,8)+")":String(N.user_id).slice(0,8));return s.jsxs("li",{children:[s.jsx("span",{className:"row-main",children:x}),s.jsx("select",{"aria-label":"成员角色",value:N.role||"viewer",onChange:G=>void i.changeMemberRole(N.user_id,G.target.value),children:ky.map(G=>s.jsx("option",{value:G,children:G},G))})]},N.user_id)})}),s.jsx("span",{className:"muted",children:i.memberMsg})]})}function Eg(){const i=Mt(),[r,m]=v.useState(""),[o,_]=v.useState(""),[C,N]=v.useState("");return s.jsxs(Ve,{id:"nav-env",title:s.jsxs(s.Fragment,{children:["环境 ",s.jsx("span",{className:"muted",children:"(选、建、管:当前项目的基准检出)"})]}),children:[s.jsxs(Dt,{children:[s.jsx("button",{onClick:i.refreshEnvs,children:"刷新"}),s.jsx("span",{className:"muted",children:i.envCount})]}),i.envs.length===0?s.jsx("div",{className:"muted",children:"先选项目"}):s.jsx(ru,{children:i.envs.map(x=>s.jsxs("li",{children:[s.jsxs("span",{className:"row-main",children:[x.name,"(",Me(x.id),") / 分支 ",x.branch||"默认分支"," / 执行器 ",x.executor||"不限"," / 状态 ",x.status,x.status==="failed"&&x.error_text?" / 失败原因:"+x.error_text:""]}),x.status==="archived"?null:s.jsx("button",{onClick:()=>void i.archiveEnv(x.id),children:"归档"})]},x.id))}),s.jsxs(Dt,{children:[s.jsx("input",{type:"text",placeholder:"新环境名",value:r,onChange:x=>m(x.target.value)}),s.jsx("input",{type:"text",placeholder:"分支(可选,空=默认分支)",value:o,onChange:x=>_(x.target.value)}),s.jsxs("select",{"aria-label":"新环境执行器",value:C,onChange:x=>N(x.target.value),children:[s.jsx("option",{value:"",children:"执行器:不限"}),s.jsx("option",{value:"local",children:"local"}),s.jsx("option",{value:"docker",children:"docker"})]}),s.jsx("button",{onClick:async()=>{await i.createEnv(r,o,C),m(""),_("")},children:"新建环境"})]}),s.jsx("span",{className:"muted",children:i.envMsg})]})}function Ng(){const i=Mt(),[r,m]=v.useState("");return s.jsxs(Ve,{title:"仓库与环境上下文",children:[s.jsx("div",{className:"muted",children:i.envRepoLine||"先选项目"}),s.jsx("div",{className:"muted",children:i.envCount}),i.envRepoBar?s.jsxs(Dt,{children:[s.jsxs("select",{"aria-label":"补仓库",value:r,onChange:o=>m(o.target.value),children:[s.jsx("option",{value:"",children:"(从 GitHub App 安装仓库里选)"}),i.githubRepos.map(o=>s.jsx("option",{value:o.clone_url,children:o.repo_full_name},o.clone_url))]}),s.jsx("button",{onClick:()=>void i.saveEnvRepo(r),children:"保存仓库"})]}):null,s.jsx("span",{className:"muted",children:i.envRepoMsg})]})}function Cg(){const i=Mt();return s.jsxs(Ve,{id:"nav-workers",title:"Workers",children:[s.jsx(Dt,{children:s.jsx("button",{onClick:i.refreshWorkers,children:"刷新"})}),i.workersError?s.jsx("div",{className:"muted",children:i.workersError}):i.workers===null?s.jsx("div",{className:"muted",children:i.workersSummary||"未加载"}):i.workers.length===0?s.jsx("div",{className:"muted",children:"无 worker 注册"}):s.jsx(ru,{children:i.workers.map((r,m)=>{const o=r.running===void 0||r.running===null?"?":r.running,_=r.capacity===void 0||r.capacity===null?"?":r.capacity,C=Qh(r),N=r.alive!==void 0&&r.alive!==null?!!r.alive:C.age!==null&&C.age<=120,x=N?" / 在线":" / 离线(占用为上次上报值)";return s.jsx("li",{className:N?"is-online":"is-offline",children:s.jsxs("span",{className:"row-main",children:[r.worker_id||r.id||"?"," / ",r.executor||""," / 占用 ",o,"/",_,x," / 最后心跳 ",C.text]})},m)})})]})}function Ag(){const i=Mt();return s.jsxs(Ve,{title:"健康与配额",children:[s.jsxs(Dt,{children:[s.jsx("span",{className:"muted",children:i.healthText}),s.jsx("button",{onClick:i.refreshHealth,children:"刷新"})]}),s.jsx("div",{children:s.jsx("span",{className:"muted",children:i.quotaText})}),s.jsx("div",{className:"muted",children:i.workersSummary})]})}function Mg(){const i=Mt();return s.jsxs(Ve,{id:"nav-skills",title:"技能与 MCP",children:[s.jsx("div",{className:"muted",children:"反映宿主租户 home,容器内 home 可能不同;重载只在 schema 枚举里,当前 pin 的二进制拒绝时原样 502;OAuth 只发起登录,不代表登录完成。"}),s.jsxs(Dt,{children:[s.jsx("button",{onClick:i.refreshSkills,children:"刷新"}),s.jsx("span",{className:"muted",children:i.skillMsg})]}),s.jsx("h3",{children:"技能"}),i.skillError?s.jsx("div",{className:"muted",children:i.skillError}):s.jsx(Dl,{empty:"暂无技能",items:i.skills.map((r,m)=>s.jsxs("span",{className:"row-main",children:[r.name||r.id||"",r.description?" - "+r.description:"",r.detail?" - "+r.detail:""]},m))}),s.jsx("h3",{children:"应用"}),s.jsx(Dl,{empty:"暂无应用",items:i.apps.map((r,m)=>s.jsxs("span",{className:"row-main",children:[r.name||r.id||"",r.description?" - "+r.description:""]},m))}),s.jsx("h3",{children:"MCP 服务"}),s.jsx(Dl,{empty:"暂无 MCP 服务",items:i.mcpServers.map((r,m)=>{const o=r.detail||r.details||r.state||"";return s.jsxs("span",{className:"row-main",children:[r.name||r.id||""," | 状态 ",r.status||"",o?" | "+o:""]},m)})})]})}function Og(){const i=Mt(),[r,m]=v.useState("");return s.jsxs(Ve,{title:"MCP 操作",children:[s.jsxs(Dt,{children:[s.jsx("button",{onClick:i.reloadMcp,children:"重载配置"}),s.jsx("span",{className:"muted",children:i.mcpMsg})]}),s.jsxs(Dt,{children:[s.jsx("input",{type:"text",placeholder:"MCP 服务名",value:r,onChange:o=>m(o.target.value)}),s.jsx("button",{onClick:()=>void i.mcpOauth(r),children:"发起 OAuth 登录"}),s.jsx("span",{className:"muted",children:i.mcpOauthMsg})]}),i.mcpOauthBox?s.jsx("pre",{children:i.mcpOauthBox}):null]})}function zg(){const i=Mt(),[r,m]=v.useState(""),[o,_]=v.useState(""),[C,N]=v.useState(""),[x,G]=v.useState("");return s.jsxs(s.Fragment,{children:[s.jsx("input",{placeholder:"自动化名称(同项目内启用中的名称唯一)",style:{width:"100%"},value:r,onChange:U=>m(U.target.value)}),s.jsx("textarea",{placeholder:"定时任务内容(创建只登记定义与到期状态;执行由后续执行器经 claim 抢单后建任务,不另起调度器)",value:o,onChange:U=>_(U.target.value)}),s.jsxs(Dt,{children:[s.jsx("input",{placeholder:"间隔秒数(默认 86400,最小 60)",style:{width:"40%"},value:C,onChange:U=>N(U.target.value)}),s.jsx("input",{placeholder:"仓库(可选)",style:{width:"40%"},value:x,onChange:U=>G(U.target.value)}),s.jsx("button",{className:"primary",onClick:async()=>{await i.createAutomation(r,o,C,x),m(""),_(""),N(""),G("")},children:"创建自动化"}),s.jsx("button",{onClick:i.refreshAutomations,children:"刷新自动化"}),s.jsx("span",{className:"muted",children:i.autoMsg})]}),s.jsx(Dl,{empty:"(暂无自动化)",items:i.automations.map(U=>s.jsxs("span",{className:"row-main",children:["[",U.status,U.due?"|到期":"","] ",U.name,"|每 ",U.interval_seconds," 秒|下次 ",Xa(U.next_run_at),U.status==="active"?s.jsxs(s.Fragment,{children:[" ",s.jsx("button",{onClick:()=>i.cancelAutomation(U.id),children:"取消"})]}):null]},U.id))})]})}function Rg(){var m,o,_,C;const i=Mt(),r=i.triage;return i.triageError?s.jsx("div",{className:"muted",children:i.triageError}):r?s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"data-group",children:[s.jsxs("b",{children:["到期自动化(",((m=r.counts)==null?void 0:m.automations_due)||0,")"]}),s.jsx(Dl,{empty:"(无)",items:(r.automations_due||[]).map((N,x)=>s.jsxs("span",{className:"row-main",children:[N.name,"|每 ",N.interval_seconds," 秒"]},x))})]}),s.jsxs("div",{className:"data-group",children:[s.jsxs("b",{children:["失败/中断任务(",((o=r.counts)==null?void 0:o.failed_tasks)||0,")"]}),s.jsx(Dl,{empty:"(无)",items:(r.failed_tasks||[]).map((N,x)=>s.jsxs("span",{className:"row-main",children:[Me(N.id),"|",N.status,"|",String(N.prompt||"").slice(0,80)]},x))})]}),s.jsxs("div",{className:"data-group",children:[s.jsxs("b",{children:["待处理后续消息(",((_=r.counts)==null?void 0:_.pending_followups)||0,")"]}),s.jsx(Dl,{empty:"(无)",items:(r.pending_followups||[]).map((N,x)=>s.jsxs("span",{className:"row-main",children:["任务 ",Me(N.task_id),"|",String(N.body||"").slice(0,80)]},x))})]}),s.jsxs("div",{className:"data-group",children:[s.jsxs("b",{children:["最近自动化产出(",((C=r.counts)==null?void 0:C.recent_runs)||0,")"]}),s.jsx(Dl,{empty:"(无)",items:(r.recent_runs||[]).map((N,x)=>s.jsxs("span",{className:"row-main",children:[Me(N.automation_id),"|",N.status,N.task_id?"|任务 "+Me(N.task_id):"",N.task_status?"|"+N.task_status:""]},x))})]})]}):s.jsx("div",{className:"muted",children:"(先选项目)"})}function Dg(){return s.jsx(Ve,{id:"nav-automations",title:"自动化",children:s.jsx(zg,{})})}function Ug(){const i=Mt();return s.jsxs(Ve,{id:"nav-triage",title:"分诊收件箱",children:[s.jsx(Dt,{children:s.jsx("button",{onClick:i.refreshTriage,children:"刷新分诊"})}),s.jsx(Rg,{})]})}const Hg=[["#sec-dispatch","派发"],["#sec-tasks","任务"],["#sec-conv","对话"],["#sec-projects","项目"],["#sec-env","环境"],["#sec-workers","Workers"],["#sec-skills","技能"],["#sec-automations","自动化"],["#sec-triage","分诊"],["#sec-admin","管理区"],["#sec-key","API Key"]];function Bg(){return s.jsxs("header",{className:"dash-topbar",children:[s.jsxs("div",{className:"dash-topbar-row",children:[s.jsx("span",{className:"dash-brand",children:"Codex 命令台"}),s.jsx(qg,{}),s.jsx("span",{className:"muted dash-hide-sm",children:s.jsx(Yg,{})}),s.jsx("span",{className:"muted dash-hide-sm",children:s.jsx(wg,{})}),s.jsx("span",{className:"muted dash-stamp",children:s.jsx(Gg,{})})]}),s.jsx("nav",{className:"dash-nav","aria-label":"页面导航",children:Hg.map(([i,r])=>s.jsx("a",{href:i,children:r},i))})]})}function qg(){const i=Mt();return s.jsx("span",{className:i.keySaved?"dash-status ok":"dash-status off",children:i.keySaved?"● 已连接":"○ 未连接"})}function Yg(){return s.jsx(s.Fragment,{children:Mt().healthText})}function wg(){return s.jsx(s.Fragment,{children:Mt().quotaText})}function Gg(){return s.jsxs(s.Fragment,{children:["自动刷新 · 每 10 秒 · 已刷新 ",Mt().refreshStamp]})}function Qg(){const i=tg();$y(m=>i.refreshAll(m),1e4),Iy(()=>i.refreshSlow(),6e4);const r=()=>{var m;(m=document.getElementById("sec-dispatch"))==null||m.scrollIntoView({behavior:"smooth"})};return s.jsxs(Gh.Provider,{value:i,children:[s.jsx(Bg,{}),s.jsxs("main",{className:"dash-page",children:[s.jsx(eg,{}),s.jsxs("div",{className:"dash-col dash-col-left",children:[s.jsxs("section",{id:"sec-tasks",className:"dash-section card",children:[s.jsx("h2",{children:"任务列表"}),s.jsx(ig,{onNew:r})]}),s.jsxs("section",{id:"sec-projects",className:"dash-section",children:[s.jsx(Tg,{}),s.jsx(_g,{})]})]}),s.jsxs("div",{className:"dash-col dash-col-mid",children:[s.jsx("section",{id:"sec-dispatch",className:"dash-section",children:s.jsx(lg,{})}),s.jsxs("section",{id:"sec-conv",className:"dash-section",children:[s.jsxs("div",{className:"card",children:[s.jsx("h2",{children:"对话"}),s.jsx(dg,{})]}),s.jsx(bg,{})]})]}),s.jsxs("div",{className:"dash-col dash-col-right",children:[s.jsxs("section",{id:"sec-env",className:"dash-section",children:[s.jsx(Eg,{}),s.jsx(Ng,{})]}),s.jsxs("section",{id:"sec-workers",className:"dash-section",children:[s.jsx(Cg,{}),s.jsx(Ag,{})]}),s.jsxs("section",{id:"sec-skills",className:"dash-section",children:[s.jsx(Mg,{}),s.jsx(Og,{})]}),s.jsx("section",{id:"sec-automations",className:"dash-section",children:s.jsx(Dg,{})}),s.jsx("section",{id:"sec-triage",className:"dash-section",children:s.jsx(Ug,{})}),s.jsx("section",{id:"sec-admin",className:"dash-section",children:s.jsx(xg,{})}),s.jsx("section",{id:"sec-key",className:"dash-section",children:s.jsx(jg,{})})]})]})]})}wy.createRoot(document.getElementById("root")).render(s.jsx(zy.StrictMode,{children:s.jsx(Qg,{})})); +`),C(L||"(暂无 result/error 输出)")}catch{}dl(H.id),Bl(H.id),Ye(H.id),Vt(H.id),Hl(H),ol(H.id),ll(H.id),ml(H.id),ql(H.id)}},[dl,Bl,Ye,Vt,Hl,ol,ll,ml,ql]),gn=v.useCallback(async H=>{const D=ne.current;if(!D)return;const L={cancel:"确定取消该任务吗?",merge:"确定把 completed 任务标为 merged 吗?",pr:"确定为该任务提 PR 吗?(会在仓库真实建 PR)"};if(!(L[H]&&!window.confirm(L[H])))try{const J=await tt($.taskAction(D.id,H),"POST",{});x(H+" 已提交:"+JSON.stringify(J).slice(0,300)),r(),await Wl(D.id)}catch(J){x(H+" 失败:"+J.message)}},[Wl,r]),Ke=v.useCallback(async()=>{const H=ne.current;if(H)try{const D=await tt($.taskCost(H.id));b(JSON.stringify(D,null,2).slice(0,4e3))}catch(D){b("查成本失败:"+D.message)}},[]),Za=v.useCallback(async H=>{const D=ne.current;if(D){if(!H.trim()){I("分支消息必填");return}if(!su.includes(D.status)){I("仅终态任务可分支");return}try{const L=await tt($.taskFork(D.id),"POST",{message:H.trim()});I("分支任务已建:"+L.id.slice(0,8)),r()}catch(L){I("分支失败:"+L.message)}}},[r]),va=v.useCallback(async H=>{const D=ne.current;if(D)try{await tt($.taskReviews(D.id),"POST",H),le("评审已登记(requested)"),await Ye(D.id)}catch(L){le("登记评审需求失败:"+L.message)}},[Ye]),ou=v.useCallback(H=>{const D=ne.current;if(!D)return;const L=window.prompt("输入该条评审的 findings(将写回记录并标为 completed):");L!==null&&tt($.taskReviewFindings(D.id,H),"POST",{findings:L}).then(()=>void Ye(D.id)).catch(J=>le("写 findings 失败:"+J.message))},[Ye]),du=v.useCallback((H,D)=>{const L=ne.current;if(!L)return;const J=D==="approved"?"approve":"deny";tt($.taskApprovalDecision(L.id,H,J),"POST",{}).then(()=>{K("已记录"+(D==="approved"?"允许":"拒绝")+"(当前版本只留回执,不拦截实时行为)"),Vt(L.id)}).catch(Ct=>K("决定失败:"+Ct.message))},[Vt]),$l=v.useCallback(async(H,D,L)=>{const J=ne.current;if(J){if(!D.trim()){k("目标任务 id 不能为空");return}if(!L.trim()){k("消息正文不能为空");return}try{const Ct=await tt($.taskMessagesTo(D.trim()),"POST",{from_task_id:J.id,body:L.trim(),mode:H});k("已发送("+Ct.delivery+")"),await ll(J.id)}catch(Ct){k("发送失败:"+Ct.message)}}},[ll]),Se=v.useCallback((H,D)=>{const L=ne.current;if(!L)return;if(!["completed","failed","interrupted"].includes(L.status)){Gt("仅 completed/failed/interrupted 任务可操作线程(merged/merge-failed 不可改,服务端同样 409)");return}const J={};if(H==="rename"){if(!D.trim()){Gt("新线程名不能为空");return}J.name=D.trim()}tt($.taskThreadAction(L.id,H),"POST",J).then(Ct=>{Gt(H+" 已提交:"+JSON.stringify(Ct).slice(0,300)),ml(L.id),ql(L.id)}).catch(Ct=>Gt(H+" 失败:"+Ct.message))},[ml,ql]),hl=v.useCallback(async H=>{const D=ne.current;if(!D)return;const L=H.trim();if(!L){ae("消息不能为空");return}try{su.includes(D.status)&&D.status!=="merged"?(await tt($.taskContinue(D.id),"POST",{message:L}),ae("已重新排队,同一会话继续"),r(),await Wl(D.id)):["queued","running"].includes(D.status)?(await tt($.taskFollowups(D.id),"POST",{body:L,mode:"direct"}),ae("已直接发送"),await dl(D.id),await Bl(D.id)):ae("merged/merge-failed 任务不可继续,也不可发后续消息")}catch(J){ae("发送失败:"+J.message)}},[Wl,r,dl,Bl]),be=v.useCallback(async()=>{const H=ne.current;if(!H){ie("先选一个任务");return}try{const D=await tt($.taskTrace(H.id)),L=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"}),J=document.createElement("a");J.href=URL.createObjectURL(L),J.download="trace-"+H.id.slice(0,8)+".json",document.body.appendChild(J),J.click(),J.remove(),setTimeout(()=>URL.revokeObjectURL(J.href),1e3),ie("已下载完整链路 "+(D.entries||[]).length+" 条")}catch(D){ie("下载失败:"+D.message)}},[]);return v.useEffect(()=>()=>qe(),[qe]),{currentTask:m,detailBody:_,actionMsg:N,actionReasons:G,costBox:Q,forkVisible:B,setForkVisible:lt,forkMsg:at,coordChildren:V,agents:Ot,agentsNote:nt,reviews:ee,reviewMsg:yt,reviewError:de,traceEntries:Ft,traceNote:tl,traceDone:Y,traceKind:et,setTraceKind:Tt,traceMsg:gt,followups:Oe,approvals:y,approvalMsg:F,peerMessages:pt,peerMsg:_t,threadText:ut,threadGateNote:Va,threadMsg:Le,threadList:zt,chatMsg:Fl,chatHistory:ha,showDetail:Wl,refreshDetailLists:yn,doAction:gn,taskCost:Ke,forkTask:Za,postReview:va,submitFindings:ou,decideApproval:du,postPeer:$l,threadAction:Se,sendChatMessage:hl,downloadTrace:be,refreshReviews:()=>void Ye(),refreshApprovals:()=>void Vt()}}function Wy(i){const[r,m]=v.useState([]),[o,_]=v.useState(""),[C,N]=v.useState(null),[x,G]=v.useState(""),U=v.useCallback(async lt=>{const at=lt!==void 0?lt:i();if(!at){m([]);return}try{const I=await tt($.projectAutomations(at));m(I.automations||[])}catch(I){_("自动化加载失败:"+I.message)}},[i]),Q=v.useCallback(async(lt,at,I,V)=>{const dt=i();if(!dt){_("先选项目");return}if(!lt.trim()){_("自动化名称必填");return}if(!at.trim()){_("定时任务内容必填");return}const Ot={name:lt.trim(),prompt:at.trim()};I.trim()&&(Ot.interval_seconds=parseInt(I.trim(),10)),V.trim()&&(Ot.repo_url=V.trim());try{await tt($.projectAutomations(dt),"POST",Ot),_("自动化已创建(登记定义,到期后由执行器认领)"),await U(dt)}catch(te){_("创建失败:"+te.message)}},[i,U]),b=v.useCallback(lt=>{const at=i();at&&tt($.projectAutomationCancel(at,lt),"POST",{}).then(()=>{_("自动化已取消"),U(at)}).catch(I=>_("取消失败:"+I.message))},[i,U]),B=v.useCallback(async lt=>{const at=lt!==void 0?lt:i();if(!at){N(null);return}try{const I=await tt($.projectTriage(at));N(I),G("")}catch(I){G("分诊加载失败:"+I.message)}},[i]);return{automations:r,autoMsg:o,triage:C,triageError:x,refreshAutomations:U,createAutomation:Q,cancelAutomation:b,refreshTriage:B}}function $y(i,r){const m=v.useRef(i);m.current=i,v.useEffect(()=>{const o=setInterval(()=>{const _=document.activeElement;m.current(_?_.tagName==="SELECT":!1)},r);return()=>clearInterval(o)},[r])}function Iy(i,r){const m=v.useRef(i);m.current=i,v.useEffect(()=>{const o=setInterval(()=>m.current(),r);return()=>clearInterval(o)},[r])}function Py(i){const r=m=>String(m).padStart(2,"0");return r(i.getHours())+":"+r(i.getMinutes())+":"+r(i.getSeconds())}function tg(){var I;const i=Ky(),r=Jy(),m=v.useRef("");m.current=i.projectId;const o=v.useCallback(()=>m.current,[]),_=Wy(o),C=v.useCallback(V=>{const dt=i.projects.find(Ot=>Ot.id===V);return dt&&dt.my_role||null},[i.projects]),N=v.useCallback(()=>{r.refreshTasks(void 0,m.current)},[r]),x=Fy(C,N),[G,U]=v.useState("--:--:--"),Q=v.useCallback(V=>{i.setProjectId(V),m.current=V,r.refreshTasks(V),i.refreshEnvs(V),i.refreshMembers(V),i.refreshSkills(V),_.refreshAutomations(V),_.refreshTriage(V)},[i,r,_]),b=(I=i.projects[0])==null?void 0:I.id;v.useEffect(()=>{!i.projectId&&b&&Q(b)},[i.projectId,b,Q]);const B=v.useCallback(V=>{i.saveKeyInput(V),V.trim()&&(i.refreshHealth(),i.refreshProjects(),i.refreshWorkers(),m.current&&(r.refreshTasks(m.current),i.refreshEnvs(m.current),i.refreshMembers(m.current)),i.loadRelayModels(),i.loadQuota(),i.loadGithubRepos(),i.probeAdmin())},[i,r]),lt=v.useCallback(V=>{if(U(Py(new Date)),!_l()){i.refreshHealth();return}r.refreshTasks(void 0,m.current),i.refreshHealth(),i.loadQuota(),i.refreshWorkers(),i.refreshMembers(),i.refreshSkills(),_.refreshAutomations(),_.refreshTriage(),x.refreshDetailLists(),V||(i.refreshProjects(),i.refreshEnvs())},[i,r,_,x]),at=v.useCallback(()=>{_l()&&(i.loadRelayModels(),i.loadGithubRepos())},[i]);return v.useEffect(()=>{i.refreshHealth(),i.refreshProjects(),i.refreshWorkers(),i.loadQuota(),i.loadGithubRepos(),_l()&&i.loadRelayModels(),i.probeAdmin()},[]),{keySaved:i.keySaved,keyState:i.keyState,saveKeyInput:B,clearKeyInput:i.clearKeyInput,quotaText:i.quotaText,healthText:i.healthText,refreshHealth:i.refreshHealth,adminGate:i.adminGate,adminBody:i.adminBody,adminResult:i.adminResult,adminUserId:i.adminUserId,setAdminUserId:i.setAdminUserId,adminQuota:i.adminQuota,adminSweep:i.adminSweep,adminBackup:i.adminBackup,projects:i.projects,projectId:i.projectId,projectMsg:i.projectMsg,selectProject:Q,refreshProjects:i.refreshProjects,createProject:i.createProject,githubRepos:i.githubRepos,workers:i.workers,workersSummary:i.workersSummary,workersError:i.workersError,refreshWorkers:i.refreshWorkers,members:i.members,memberMsg:i.memberMsg,memberError:i.memberError,refreshMembers:()=>void i.refreshMembers(),inviteMember:i.inviteMember,changeMemberRole:i.changeMemberRole,skills:i.skills,apps:i.apps,mcpServers:i.mcpServers,skillMsg:i.skillMsg,skillError:i.skillError,mcpMsg:i.mcpMsg,mcpOauthMsg:i.mcpOauthMsg,mcpOauthBox:i.mcpOauthBox,refreshSkills:()=>void i.refreshSkills(),reloadMcp:i.reloadMcp,mcpOauth:i.mcpOauth,envs:i.envs,envCount:i.envCount,envMsg:i.envMsg,envRepoLine:i.envRepoLine,envRepoBar:i.envRepoBar,envRepoMsg:i.envRepoMsg,envSelect:i.envSelect,setEnvSelect:i.setEnvSelect,refreshEnvs:()=>void i.refreshEnvs(),createEnv:i.createEnv,archiveEnv:i.archiveEnv,saveEnvRepo:i.saveEnvRepo,models:i.models,modelCustomVisible:i.modelCustomVisible,tasks:r.tasks,taskCountText:r.taskCountText,taskError:r.taskError,refreshTasks:()=>void r.refreshTasks(void 0,m.current),statusFilter:r.statusFilter,setStatusFilter:r.setStatusFilter,executorFilter:r.executorFilter,setExecutorFilter:r.setExecutorFilter,searchText:r.searchText,setSearchText:r.setSearchText,filteredTasks:r.filteredTasks,currentTask:x.currentTask,detailBody:x.detailBody,actionMsg:x.actionMsg,actionReasons:x.actionReasons,showDetail:V=>void x.showDetail(V),doAction:x.doAction,taskCost:x.taskCost,costBox:x.costBox,forkVisible:x.forkVisible,setForkVisible:x.setForkVisible,forkTask:x.forkTask,forkMsg:x.forkMsg,coordChildren:x.coordChildren,coordText:"",agents:x.agents,agentsNote:x.agentsNote,reviews:x.reviews,reviewMsg:x.reviewMsg,reviewError:x.reviewError,refreshReviews:x.refreshReviews,postReview:x.postReview,submitFindings:x.submitFindings,traceEntries:x.traceEntries,traceNote:x.traceNote,traceDone:x.traceDone,traceKind:x.traceKind,setTraceKind:x.setTraceKind,downloadTrace:x.downloadTrace,traceMsg:x.traceMsg,sendChatMessage:x.sendChatMessage,chatMsg:x.chatMsg,chatHistory:x.chatHistory,followups:x.followups,approvals:x.approvals,approvalMsg:x.approvalMsg,refreshApprovals:x.refreshApprovals,decideApproval:x.decideApproval,peerMessages:x.peerMessages,peerMsg:x.peerMsg,postPeer:x.postPeer,automations:_.automations,autoMsg:_.autoMsg,refreshAutomations:()=>void _.refreshAutomations(),createAutomation:_.createAutomation,cancelAutomation:_.cancelAutomation,triage:_.triage,triageError:_.triageError,refreshTriage:()=>void _.refreshTriage(),threadText:x.threadText,threadGateNote:x.threadGateNote,threadMsg:x.threadMsg,threadList:x.threadList,threadAction:x.threadAction,dispatch:async(V,dt)=>{const Ot=await r.dispatch(V,dt);return r.refreshTasks(m.current),Ot},dispatchMsg:"",refreshAll:lt,refreshSlow:at,refreshStamp:G}}function cu({label:i,value:r,sub:m}){return s.jsxs("div",{className:"dash-card data-card",children:[s.jsx("div",{className:"dash-card-label",children:i}),s.jsx("div",{className:"dash-card-value",children:r}),s.jsx("div",{className:"muted",children:m})]})}function eg(){const i=Mt(),r=i.healthText==="未加载"?"未加载":i.healthText.includes("worker_ok=true")?"正常":i.healthText.includes("worker_ok=false")?"异常":i.healthText.includes("加载失败")?"加载失败":"未知",m=i.quotaText.match(/剩余 \$([0-9.]+)/),o=i.quotaText.includes("加载失败")?"加载失败":i.quotaText.includes("未填 key")||i.quotaText==="未加载"?"未加载":m?"$"+m[1]:"配额",_=["running","completed","failed","queued","merged","interrupted"].map(b=>i.tasks.filter(B=>B.status===b).length).map((b,B)=>["running","completed","failed","queued","merged","interrupted"][B]+" "+b).join(" | "),C=i.projects.find(b=>b.id===i.projectId),N=i.taskError?{value:"加载失败",sub:i.taskError}:i.projectId?{value:String(i.tasks.length),sub:i.taskCountText+" | "+_+(C?" | "+C.name:"")}:{value:"先选项目",sub:i.projectMsg||"项目加载后自动选第一个"};let x;if(i.workersError)x={value:"加载失败",sub:i.workersError};else if(i.workers===null)x={value:i.workersSummary||"未加载",sub:"完整 worker 清单仅全局管理员可见"};else{const b=i.workers.filter(B=>{if(B.alive!==void 0&&B.alive!==null)return!!B.alive;const lt=Qh(B).age;return lt!==null&<<=120}).length;x={value:b+" 在线 / "+i.workers.length,sub:i.workers.length-b+" 离线"}}const G=i.projectMsg.includes("加载失败")?{value:"加载失败",sub:i.projectMsg}:{value:i.projects.length+" 个",sub:C?"当前项目:"+C.name:"未选项目(加载后自动选第一个)"},U=[i.envMsg,i.memberError,i.skillError].filter(b=>b&&b.includes("失败")),Q=i.projectId?U.length?{value:"部分加载失败",sub:U.join(" | ")}:{value:i.envs.length+" 个环境",sub:i.members.length+" 成员 | "+i.skills.length+" 技能 | "+i.apps.length+" 应用 | "+i.mcpServers.length+" MCP"}:{value:"先选项目",sub:"选项目后显示环境 / 成员 / 技能计数"};return s.jsxs("section",{className:"dash-cards","aria-label":"数据卡片",children:[s.jsx(cu,{label:"健康",value:r,sub:i.healthText}),s.jsx(cu,{label:"配额",value:o,sub:i.quotaText}),s.jsx(cu,{label:"任务",value:N.value,sub:N.sub}),s.jsx(cu,{label:"Workers",value:x.value,sub:x.sub}),s.jsx(cu,{label:"项目",value:G.value,sub:G.sub}),s.jsx(cu,{label:"项目数据",value:Q.value,sub:Q.sub})]})}function Ve({id:i,className:r,title:m,children:o}){return s.jsxs("section",{id:i,className:"card"+(r?" "+r:""),children:[m?s.jsx("h2",{children:m}):null,o]})}function Dt({children:i}){return s.jsx("div",{className:"bar",children:i})}function Dl({items:i,empty:r}){return i.length?s.jsx("ul",{className:"data-list",children:i.map((m,o)=>s.jsx("li",{children:m},o))}):s.jsx("div",{className:"muted",children:r||"(暂无)"})}function ru({children:i}){return s.jsx("ul",{className:"data-list",children:i})}function lg(){const i=Mt(),[r,m]=v.useState(""),[o,_]=v.useState(""),[C,N]=v.useState(!1),[x,G]=v.useState(!1),[U,Q]=v.useState(!1),[b,B]=v.useState("docker"),[lt,at]=v.useState(""),[I,V]=v.useState(""),[dt,Ot]=v.useState(""),[te,nt]=v.useState(""),[Xt,ee]=v.useState(""),[ct,yt]=v.useState(""),[le,de]=v.useState(""),Kt=v.useRef(null),Ft=v.useRef(null),Wt=v.useRef(null),[tl,mt]=v.useState("");async function Y(){var Oe,ze,y;if(mt(""),!i.projectId){mt("先选项目");return}if(!r.trim()){mt("prompt 必填");return}const A={project_id:i.projectId,prompt:r.trim()};i.envSelect&&(A.environment_id=i.envSelect),b&&(A.executor=b);const et=I.trim()||lt;et&&(A.model=et),dt&&(A.effort=dt);const Tt=ct.trim()||Xt;if(Tt&&(A.child_model=Tt),le&&(A.child_effort=le),te&&(A.approval=te),U){if(te==="full"){mt("计划模式不能与完全访问同用");return}if(C||x){mt("计划模式不能与 goal 模式同用");return}A.plan=1}x?(A.goal_mode=1,A.no_time_limit=1):C&&(A.goal_mode=1);const gt=o.trim();if(gt){try{A.coordinator_parts=JSON.parse(gt)}catch{mt("协调员子任务 JSON 解析失败");return}if(!Array.isArray(A.coordinator_parts)){mt("协调员子任务必须是数组");return}if(C||x||U){mt("协调员任务不能与 goal/计划模式同用:协调员任务本身不跑模型回合,请取消 goal/计划勾选,或清空协调员 JSON");return}}const ie=Zy(((Oe=Kt.current)==null?void 0:Oe.files)||null,((ze=Ft.current)==null?void 0:ze.files)||null,((y=Wt.current)==null?void 0:y.files)||null);try{const R=await i.dispatch(A,ie);mt(R),m(""),_(""),Kt.current&&(Kt.current.value=""),Ft.current&&(Ft.current.value=""),Wt.current&&(Wt.current.value="")}catch(R){mt("提交失败:"+((R==null?void 0:R.message)||String(R)))}}return s.jsxs(Ve,{id:"nav-newtask",className:"composer",title:"新建任务",children:[s.jsxs(Dt,{children:[s.jsxs("select",{"aria-label":"环境",value:i.envSelect,onChange:A=>i.setEnvSelect(A.target.value),children:[s.jsx("option",{value:"",children:"环境:不指定(走项目仓库)"}),i.envs.map(A=>s.jsxs("option",{value:A.id,disabled:A.status!=="ready",children:[A.name,"(",A.status,")"]},A.id))]}),s.jsxs("select",{"aria-label":"执行器",value:b,onChange:A=>B(A.target.value),children:[s.jsx("option",{value:"docker",children:"docker(默认,多用户下隔离最强)"}),s.jsx("option",{value:"local",children:"local"}),s.jsx("option",{value:"cloud",children:"cloud"})]}),s.jsxs("label",{children:[s.jsx("input",{type:"checkbox",checked:C,onChange:A=>N(A.target.checked)})," goal 模式"]}),s.jsxs("label",{children:[s.jsx("input",{type:"checkbox",checked:x,onChange:A=>G(A.target.checked)})," goal 模式无敌版"]}),s.jsxs("label",{children:[s.jsx("input",{type:"checkbox",checked:U,onChange:A=>Q(A.target.checked)})," 计划模式(只读)"]})]}),s.jsx("textarea",{className:"prompt stacked",placeholder:"任务 prompt(必填)",value:r,onChange:A=>m(A.target.value)}),s.jsx("textarea",{className:"coord stacked",placeholder:"协调员子任务 JSON(可选),留空为普通任务",value:o,onChange:A=>_(A.target.value)}),s.jsxs("div",{className:"muted",children:["协调员格式:[","{",'"name":"后端","prompt":"做后端"',"}",",","{",'"name":"前端","prompt":"做前端"',"}","];提交后 worker 并行建子任务、等齐、合流汇总。不能与 goal/计划模式同用。协调员与 goal/计划互斥:勾了 goal 或计划模式时不要填协调员 JSON,提交前会校验并拦截。子任务费用记父任务创建人账;父任务创建时按分工数预留配额,余额不足时整个父任务被挡下。"]}),s.jsxs(Dt,{children:[s.jsxs("select",{"aria-label":"模型",value:lt,onChange:A=>at(A.target.value),children:[s.jsx("option",{value:"",children:"模型:默认(跟随配置)"}),i.models.map(A=>s.jsx("option",{value:A,children:A},A))]}),i.modelCustomVisible?s.jsx("input",{type:"text",placeholder:"手动输入模型(中转列表取不到时用)",value:I,onChange:A=>V(A.target.value)}):null,s.jsxs("select",{"aria-label":"推理强度",value:dt,onChange:A=>Ot(A.target.value),children:[s.jsx("option",{value:"",children:"推理强度:默认(跟随配置)"}),Yh.map(A=>s.jsx("option",{value:A,children:A},A))]}),s.jsxs("select",{"aria-label":"权限",value:te,onChange:A=>nt(A.target.value),children:[s.jsx("option",{value:"",children:"权限:默认(跟随配置)"}),s.jsx("option",{value:"ask",children:"请求批准"}),s.jsx("option",{value:"auto",children:"帮我批准"}),s.jsx("option",{value:"full",children:"完全访问"})]})]}),s.jsxs(Dt,{children:[s.jsxs("select",{"aria-label":"子任务模型",value:Xt,onChange:A=>ee(A.target.value),children:[s.jsx("option",{value:"",children:"子任务模型:跟随本任务模型"}),i.models.map(A=>s.jsx("option",{value:A,children:A},A))]}),s.jsx("input",{type:"text",placeholder:"子任务模型手动输入(中转列表里没有时填,如 gpt-6-luna;留空=跟随本任务模型)",value:ct,onChange:A=>yt(A.target.value)}),s.jsxs("select",{"aria-label":"子任务强度",value:le,onChange:A=>de(A.target.value),children:[s.jsx("option",{value:"",children:"子任务强度:跟随本任务强度"}),Yh.map(A=>s.jsx("option",{value:A,children:A},A))]})]}),s.jsx("div",{className:"muted",children:"子任务模型/强度是派生默认:本任务用 marker task-create 或协调员分工派生的子任务,未显式写模型/强度时用它;块/分工里显式写了则覆盖它。也可以不在这里填、直接在 prompt 里写“子任务用 gpt-6-luna max”,建任务时服务端会解析成同一对字段(任务详情里可核对)。"}),C?s.jsx("div",{className:"muted",children:"goal 模式:输入框内容即目标,不需要专用 prompt 与验收命令;不限轮次,模型确认达成后在回复末尾输出“目标已达成”即结束,未达成会一直运行,可用取消停止。"}):null,x?s.jsx("div",{className:"muted",children:"goal 模式无敌版:goal 模式 + 任务不限时(去掉 worker/docker 总超时;单轮 600 秒护栏还在,不限的是任务总时长与总轮次)。服务端默认的自动续跑与断点镜像同时生效。"}):null,s.jsxs(Dt,{children:[s.jsx("span",{className:"muted",children:"附件(放进任务工作区,图片与普通文件不区分):"}),s.jsx("input",{ref:Kt,type:"file",multiple:!0,title:"文件"}),s.jsx("input",{ref:Ft,type:"file",multiple:!0,title:"文件夹",webkitdirectory:""}),s.jsx("input",{ref:Wt,type:"file",accept:"image/*",multiple:!0,title:"图片"})]}),s.jsxs(Dt,{children:[s.jsx("button",{className:"seal",onClick:Y,children:"提交任务"}),s.jsx("span",{className:"muted",children:tl||i.dispatchMsg})]})]})}function ag(i){const r=(i.prompt||"").trim();return r?[...r][0]:i.id?[...i.id][0]:"?"}function zc(i){const r=Math.max(0,Math.floor(i));if(r<60)return r+"秒";if(r<3600){const _=Math.floor(r/60),C=r%60;return C?_+"分"+C+"秒":_+"分"}if(r<86400){const _=Math.floor(r/3600),C=Math.floor(r%3600/60);return C?_+"小时"+C+"分":_+"小时"}const m=Math.floor(r/86400),o=Math.floor(r%86400/3600);return o?m+"天"+o+"小时":m+"天"}function fu(i){if(i==null)return null;const r=Number(i);return Number.isFinite(r)?r:null}function ng(i){const r=Date.now()/1e3,m=fu(i.started_at),o=fu(i.finished_at),_=fu(i.created_at);return m!==null&&o!==null&&o>=m?[zc(o-m),"总耗时:开始→结束(末次认领的执行时长)"]:m!==null&&i.status==="running"?["进行中 "+zc(r-m),"总耗时:进行中,已用时长从开始算起,随列表刷新更新"]:_!==null&&o!==null&&o>=_?[zc(o-_),"总耗时:创建→完成(含排队等待;无开始时间时的兜底口径)"]:_!==null&&(i.status==="queued"||i.status==="running")?[(i.status==="queued"?"排队中 ":"进行中 ")+zc(r-_),"总耗时:从创建算起的已用时长(无开始时间时的兜底口径)"]:["—","总耗时:无可用时间戳"]}function ug(i){const r=fu(i.total_tokens);if(r!==null)return[r.toLocaleString()+" token","token:服务端列表 total_tokens"];const m=fu(i.actual_tokens),o=fu(i.estimated_tokens);return m!==null||o!==null?[((m||0)+(o||0)).toLocaleString()+" token","token:actual + estimated 分项加总"]:["token —","token:本行未带 token 字段;单任务真数在任务详情的成本里可查,本行不逐个调成本接口、不编造"]}function ig({onNew:i}){const r=Mt();return s.jsxs("div",{children:[s.jsxs("div",{className:"search-box",children:[s.jsxs("svg",{viewBox:"0 0 24 24",width:"18",height:"18",fill:"none",stroke:"currentColor",strokeWidth:"1.8",strokeLinecap:"round","aria-hidden":"true",children:[s.jsx("circle",{cx:"11",cy:"11",r:"7"}),s.jsx("path",{d:"M21 21l-4.3-4.3"})]}),s.jsx("input",{type:"text",placeholder:"搜索","aria-label":"搜索任务",value:r.searchText,onChange:m=>r.setSearchText(m.target.value)})]}),s.jsx("div",{className:"bar",children:s.jsxs("select",{"aria-label":"项目",value:r.projectId,onChange:m=>r.selectProject(m.target.value),children:[r.projects.length===0?s.jsx("option",{value:"",children:"(先加载项目)"}):null,r.projects.map(m=>s.jsxs("option",{value:m.id,children:[m.name," (",Me(m.id),")"]},m.id))]})}),s.jsxs("div",{className:"list-section-head",children:[s.jsx("span",{children:"主要聊天"}),s.jsx("button",{type:"button","aria-label":"派发新任务",title:"派发新任务",onClick:i,children:"+"})]}),s.jsxs("div",{className:"bar",children:[s.jsxs("select",{"aria-label":"状态筛选",value:r.statusFilter,onChange:m=>r.setStatusFilter(m.target.value),children:[s.jsx("option",{value:"",children:"状态:全部"}),["queued","running","completed","failed","interrupted","merged","merge-failed"].map(m=>s.jsx("option",{value:m,children:m},m))]}),s.jsxs("select",{"aria-label":"执行器筛选",value:r.executorFilter,onChange:m=>r.setExecutorFilter(m.target.value),children:[s.jsx("option",{value:"",children:"执行器:全部"}),s.jsx("option",{value:"local",children:"local"}),s.jsx("option",{value:"docker",children:"docker"}),s.jsx("option",{value:"cloud",children:"cloud"})]})]}),s.jsxs("div",{className:"bar",children:[s.jsx("button",{onClick:r.refreshTasks,children:"刷新"}),s.jsx("span",{className:"muted",children:r.taskCountText})]}),r.taskError?s.jsx("div",{className:"err",children:r.taskError}):r.projectId?r.filteredTasks.length===0?s.jsx("div",{className:"muted",children:r.tasks.length?"没有符合筛选条件的任务":"该项目暂无任务"}):s.jsx("ul",{className:"issue-list",children:r.filteredTasks.slice().reverse().map(m=>{var G;const[o,_]=ng(m),[C,N]=ug(m),x=ag(m);return s.jsxs("li",{className:"issue-item"+(((G=r.currentTask)==null?void 0:G.id)===m.id?" selected":""),onClick:()=>r.showDetail(m.id),children:[s.jsx("span",{className:"issue-state st-"+m.status,"aria-hidden":"true"}),s.jsx("span",{className:"issue-first",title:"任务首字:"+x,"aria-hidden":"true",children:x}),s.jsxs("span",{style:{flex:1,minWidth:0},children:[s.jsxs("div",{className:"issue-title",children:[m.plan?"[计划] ":"",m.forked_from?"[分支] ":"",m.coordinator_parts?"[协调] ":"",m.parent_task_id?"[协调子任务] ":"",String(m.prompt||Me(m.id)).slice(0,60)]}),s.jsxs("div",{className:"issue-meta",children:[Me(m.id)," · ",s.jsx("span",{className:"st-"+m.status,children:m.status})," · ",m.executor||"",m.updated_at?" · "+new Date(m.updated_at*1e3).toLocaleString():""," · ",s.jsxs("span",{title:_,children:["耗时 ",o]})," · ",s.jsx("span",{title:N,children:C})]})]})]},m.id)})}):s.jsx("div",{className:"muted",children:"先选项目"})]})}function cg(i){const r=v.useRef(null),[m,o]=v.useState(!0),_=v.useRef(!0),C=()=>{const N=r.current;if(!N)return;const x=N.scrollHeight-N.scrollTop-N.clientHeight<80;_.current=x,o(x)};return v.useEffect(()=>{_.current&&r.current&&(r.current.scrollTop=r.current.scrollHeight)},[i]),{ref:r,following:m,onScroll:C}}function sg({msg:i}){if(i.role==="status")return s.jsx("div",{className:"chat-status",children:i.text});const r=i.label||(i.role==="user"?"用户":i.role==="assistant"?"助手":"工具");return s.jsxs("div",{className:"gh-comment "+i.role,children:[s.jsx("div",{className:"gh-comment-header",children:s.jsx("span",{className:"gh-comment-author",children:r})}),s.jsx("div",{className:"gh-comment-body",children:i.text})]})}function kh({messages:i,empty:r}){const{ref:m,following:o,onScroll:_}=cg(i.length);return s.jsxs("div",{className:"chat-list",ref:m,onScroll:_,children:[i.length===0?s.jsx("div",{className:"muted",children:r||"(暂无消息)"}):i.map(C=>s.jsx(sg,{msg:C},C.id)),!o&&i.length>0?s.jsx("button",{className:"chat-follow",onClick:()=>{m.current&&(m.current.scrollTop=m.current.scrollHeight)},children:"回到最新 ↓"}):null]})}function fg({onSend:i,disabled:r,placeholder:m}){const[o,_]=v.useState(""),C=()=>{const N=o.trim();!N||r||(i(N),_(""))};return s.jsxs("div",{className:"gh-comment-box",children:[s.jsx("textarea",{"aria-label":"会话输入",rows:3,placeholder:m||"消息",value:o,disabled:r,onChange:N=>_(N.target.value),onKeyDown:N=>{N.key==="Enter"&&!N.shiftKey&&(N.preventDefault(),C())}}),s.jsx("div",{className:"gh-comment-footer",children:s.jsx("button",{type:"button",className:"primary","aria-label":"发送",disabled:r||!o.trim(),onClick:C,children:"发送"})})]})}function Xh(i,r){const m=i.id||"trace-"+r;if(i.kind==="message")return{id:m,role:"assistant",text:(i.phase?"["+i.phase+"] ":"")+(i.text||""),label:"助手"};if(i.kind==="exec"||i.kind==="file"||i.kind==="tool"||i.kind==="call"){const o=i.kind==="exec"?"命令":i.kind==="file"?"文件":i.kind==="tool"?"工具":"调用";return{id:m,role:"tool",text:or(i),label:o}}return i.kind==="plan"?{id:m,role:"status",text:i.text||""}:i.kind==="reasoning"?{id:m,role:"status",text:"思考:"+(i.summary||"")}:i.kind==="agent-start"?{id:m,role:"status",text:"子代理开始:"+(i.name||"")+(i.prompt?"("+i.prompt+")":"")}:i.kind==="agent-done"?{id:m,role:"status",text:"子代理结束:"+(i.name||"")+" "+(i.status||"")}:{id:m,role:"status",text:or(i)}}function rg(){const i=Mt(),r=v.useMemo(()=>{const m=i.currentTask,o=m&&m.prompt?[{id:"task-prompt",role:"user",text:m.prompt}]:[],C=(i.traceKind?i.traceEntries.filter(x=>x.kind===i.traceKind):i.traceEntries).map((x,G)=>Xh(x,G)),N=i.traceDone?[{id:"trace-done",role:"status",text:"链路已结束("+i.traceDone+")"}]:[];return[...o,...C,...N]},[i.currentTask,i.traceEntries,i.traceKind,i.traceDone]);return s.jsxs(s.Fragment,{children:[s.jsx("div",{className:"muted",children:"实时链路"}),s.jsxs(Dt,{children:[s.jsxs("select",{"aria-label":"链路类型",value:i.traceKind,onChange:m=>i.setTraceKind(m.target.value),children:[s.jsx("option",{value:"",children:"链路类型:全部"}),["exec","file","call","tool","message","reasoning","plan","agent-start","agent-done"].map(m=>s.jsx("option",{value:m,children:m==="exec"?"命令":m==="file"?"文件":m==="call"?"调用":m==="tool"?"工具":m==="message"?"消息":m==="reasoning"?"思考":m==="plan"?"计划":"子代理"},m))]}),s.jsx("button",{onClick:i.downloadTrace,children:"下载完整链路"}),s.jsx("span",{className:"muted",children:i.traceMsg})]}),i.traceNote?s.jsx("div",{children:s.jsx("span",{className:"err",children:i.traceNote})}):null,s.jsx("div",{className:"chat-column",children:s.jsx(kh,{messages:r,empty:"(暂无链路条目)"})}),s.jsx("div",{className:"muted",children:"对话时间线:首条是本任务 prompt(用户);助手回复来自 message 条目,命令/文件/工具/调用是工具评论框,计划/思考/子代理开始结束是时间线上的状态文字。流式进行中时新条目实时追加;你在底部时自动跟随,往上翻时暂停跟随,点“回到最新”恢复。"})]})}function og(){const i=Mt(),r=i.currentTask,m=v.useMemo(()=>{if(!r)return[];if(i.chatHistory)return i.chatHistory.messages.map(b=>({id:b.id,role:b.role,text:b.text,label:b.label}));const G=r.prompt?[{id:"task-prompt",role:"user",text:r.prompt}]:[],U=i.traceEntries.filter(b=>b.kind==="message").map((b,B)=>Xh(b,B)),Q=i.followups.map((b,B)=>({id:"followup-"+B,role:"user",text:String(b.body||""),label:"后续消息"}));return[...G,...U,...Q]},[r,i.chatHistory,i.traceEntries,i.followups]);if(!r)return s.jsx("div",{className:"muted",children:"点任务行查看详情后,这里显示继续会话"});const o=su.includes(r.status),_=["queued","running"].includes(r.status),C=o||_,N=o?"终态任务:发送走继续接口(同一会话再跑一轮)":_?"运行中/排队中:发送走后续消息直接发送(跑完当前轮后优先执行)":"merged/merge-failed 任务不可继续,也不可发后续消息",x=i.chatHistory?i.chatHistory.legacy_continue_count??null:null;return s.jsxs("div",{className:"chat-column",children:[x!==null&&x>0?s.jsxs("div",{className:"muted",children:["此前有 ",x," 条继续消息发生在持久化之前,未入库,上面历史不完整;部署后新发的继续消息会正常显示。"]}):null,i.chatHistory&&i.chatHistory.trace_note?s.jsx("div",{className:"muted",children:i.chatHistory.trace_note}):null,s.jsx(kh,{messages:m,empty:"(暂无会话消息)"}),s.jsx(fg,{disabled:!C,placeholder:"消息",onSend:G=>void i.sendChatMessage(G)}),s.jsxs("div",{className:"muted",children:[N,"。",i.chatMsg]})]})}function dg(){return Mt().currentTask?s.jsxs("div",{className:"conversation",children:[s.jsx(rg,{}),s.jsx("div",{className:"conv-divider"}),s.jsx(og,{})]}):s.jsx("div",{className:"conversation",children:s.jsx("div",{className:"muted",children:"在任务列表里点一条任务,对话和详情就切到该任务;也可以在上面派发一个新任务。"})})}function mg(){const i=Mt(),r=i.currentTask,[m,o]=v.useState("");if(!r)return null;const _=su.includes(r.status),C=["failed","interrupted","merge-failed"].includes(r.status);return s.jsxs(s.Fragment,{children:[s.jsxs(Dt,{children:[s.jsx("button",{disabled:!C,title:C?"":"仅 failed/interrupted/merge-failed 可重试",onClick:()=>i.doAction("retry"),children:"重试"}),s.jsx("button",{className:"danger",disabled:_,title:_?"终态任务不可取消":"",onClick:()=>i.doAction("cancel"),children:"取消"}),s.jsx("button",{disabled:r.status!=="completed",title:r.status!=="completed"?"仅 completed 可合并":"",onClick:()=>i.doAction("merge"),children:"合并(completed → merged)"}),s.jsx("button",{disabled:r.status!=="merged",title:r.status!=="merged"?"仅 merged 可提 PR":"",onClick:()=>i.doAction("pr"),children:"提 PR(须 merged)"}),_?s.jsx("button",{onClick:()=>i.setForkVisible(!0),children:"分支"}):null,s.jsx("button",{onClick:i.taskCost,children:"查成本"}),s.jsxs("span",{className:"muted",children:[i.actionMsg," ",i.actionReasons]})]}),i.forkVisible?s.jsxs("div",{children:[s.jsx("input",{type:"text",placeholder:"分支首轮消息(必填,带历史的新会话)",style:{width:"100%",boxSizing:"border-box",margin:"6px 0"},value:m,onChange:N=>o(N.target.value)}),s.jsxs(Dt,{children:[s.jsx("button",{className:"seal",onClick:async()=>{await i.forkTask(m),o("")},children:"创建分支任务"}),s.jsx("span",{className:"muted",children:i.forkMsg})]})]}):null]})}function hg(){const i=Mt(),[r,m]=v.useState("uncommittedChanges"),[o,_]=v.useState(""),[C,N]=v.useState(""),[x,G]=v.useState("");return s.jsxs(s.Fragment,{children:[s.jsx("h3",{children:"评审队列"}),s.jsxs("select",{"aria-label":"评审目标",value:r,onChange:U=>m(U.target.value),children:[s.jsx("option",{value:"uncommittedChanges",children:"未提交改动(工作区)"}),s.jsx("option",{value:"baseBranch",children:"与分支的差异"}),s.jsx("option",{value:"commit",children:"指定提交"}),s.jsx("option",{value:"custom",children:"自定义评审指令"})]}),s.jsx("input",{className:"stacked",placeholder:"与分支的差异用:分支名(如 main)",style:{width:"100%"},value:o,onChange:U=>_(U.target.value)}),s.jsx("input",{className:"stacked",placeholder:"指定提交用:提交 sha",style:{width:"100%"},value:C,onChange:U=>N(U.target.value)}),s.jsx("textarea",{placeholder:"自定义评审指令用:评审指令正文",value:x,onChange:U=>G(U.target.value)}),s.jsxs(Dt,{children:[s.jsx("button",{className:"primary",onClick:()=>void i.postReview({target_type:r,branch:o.trim()||void 0,sha:C.trim()||void 0,instructions:x.trim()||void 0}),children:"登记评审需求"}),s.jsx("button",{onClick:i.refreshReviews,children:"刷新队列"}),s.jsx("span",{className:"muted",children:i.reviewMsg})]}),s.jsx("div",{className:"muted",children:"本基线只登记评审需求(requested),不自动跑评审;评审是否执行、结果如何,以评审队列里的记录为准。"}),i.reviewError?s.jsx("div",{className:"muted",children:i.reviewError}):i.reviews.length===0?s.jsx("div",{className:"muted",children:"点任务行查看详情后,点“刷新队列”查看评审记录"}):s.jsx(ru,{children:i.reviews.map(U=>{let Q=U.target_type||"";return U.branch&&(Q+=":"+U.branch),U.sha&&(Q+=":"+String(U.sha).slice(0,12)),U.instructions&&(Q+=":"+String(U.instructions).slice(0,80)),s.jsxs("li",{children:[s.jsxs("span",{className:"row-main",children:[s.jsx("b",{children:"评审"})," ",Me(U.id)," | ",Q," | ",s.jsx("span",{className:"st-"+(U.status||""),children:U.status}),U.created_at?" | "+Xa(U.created_at):"",s.jsx("br",{}),U.findings?String(U.findings).slice(0,1e3):s.jsx("span",{className:"muted",children:"(暂无 findings:评审尚未执行或尚未写回)"})]}),s.jsx("button",{onClick:()=>i.submitFindings(U.id),children:"写 findings"})]},U.id)})})]})}function vg(){const i=Mt();return i.currentTask?i.currentTask.coordinator_parts?i.coordChildren.length===0?s.jsx("div",{className:"muted",children:"(子任务尚未创建)"}):s.jsx(ru,{children:i.coordChildren.map(r=>s.jsx("li",{children:s.jsxs("span",{className:"row-main",children:["分工 ",s.jsx("b",{children:r.coordinator_part||""})," | 状态 ",s.jsx("span",{className:"st-"+r.status,children:r.status})," | ",Me(r.id)," | ",String(r.prompt||"").slice(0,60)]})},r.id))}):s.jsx("div",{className:"muted",children:"(非协调员任务)"}):s.jsx("div",{className:"muted",children:"点任务行查看详情后,这里显示协调员的子任务"})}function yg(){const i=Mt();if(!i.currentTask)return s.jsx("div",{className:"muted",children:"点任务行查看详情后,这里显示子代理链路"});if(i.agents.length===0)return s.jsxs("div",{className:"muted",children:["(暂无子代理)",i.agentsNote?s.jsx("span",{className:"err",children:i.agentsNote}):null]});const r={main:"主任务",delegate:"委派子任务",model:"模型子代理"},m={running:"进行中",completed:"已完成",failed:"失败",interrupted:"已中断",unknown:"未知"};return s.jsx(ru,{children:i.agents.map((o,_)=>s.jsx("li",{children:s.jsxs("span",{className:"row-main",children:[s.jsxs("b",{children:[r[o.kind||""]||o.kind||"",":"]}),o.name||""," ",s.jsx("span",{className:"st-"+(o.status||""),children:m[o.status||""]||o.status||"?"})," | 过程调用 "+(o.calls?o.calls.length:0)+" 条",o.updated_ts?" | 最近活动 "+Xa(o.updated_ts/1e3):"",o.prompt?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),s.jsxs("span",{className:"muted",children:["任务:",o.prompt]})]}):null,o.calls&&o.calls.length?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),o.calls.slice(-50).map((C,N)=>s.jsx("div",{children:or(C)},N))]}):s.jsxs(s.Fragment,{children:[s.jsx("br",{}),s.jsx("span",{className:"muted",children:"(暂无过程调用)"})]})]})},_))})}function gg(){const i=Mt();return s.jsxs(s.Fragment,{children:[s.jsxs(Dt,{children:[s.jsx("button",{onClick:i.refreshApprovals,children:"刷新审批队列"}),s.jsx("span",{className:"muted",children:i.approvalMsg})]}),s.jsx("div",{className:"muted",children:"待审批项需人工点记录允许或记录拒绝;超时默认拒绝。当前版本只留回执,不拦截实时行为(实时请求的事前拦截待 host 接入后生效,在此之前实时行为不变)。"}),s.jsx(Dl,{empty:"(暂无审批项)",items:i.approvals.map(r=>s.jsxs("span",{className:"row-main",children:["[",r.kind,"|",r.status,"] ",r.summary,["approved","denied","expired"].includes(r.status||"")?"|回执:"+(r.decision||r.status)+" by "+String(r.decided_by||"").slice(0,8)+" "+Xa(r.decided_at):"",r.status==="pending"?s.jsxs(s.Fragment,{children:[" ",s.jsx("button",{onClick:()=>i.decideApproval(r.id,"approved"),children:"记录允许"})," ",s.jsx("button",{onClick:()=>i.decideApproval(r.id,"denied"),children:"记录拒绝"})]}):null]},r.id))})]})}function pg(){const i=Mt(),[r,m]=v.useState(""),[o,_]=v.useState("");return s.jsxs(s.Fragment,{children:[s.jsx("input",{placeholder:"目标任务 id(与本任务同项目;对方运行中排队执行、已终态则续跑复活,绝不打断当前轮)",style:{width:"100%"},value:r,onChange:C=>m(C.target.value)}),s.jsx("textarea",{placeholder:"消息正文(以本任务名义发出;对方 agent 会看到发送方任务 id,可照此回消息)",value:o,onChange:C=>_(C.target.value)}),s.jsxs(Dt,{children:[s.jsx("button",{onClick:async()=>{await i.postPeer("queued",r,o),_("")},children:"排队发给对方"}),s.jsx("button",{className:"primary",onClick:async()=>{await i.postPeer("direct",r,o),_("")},children:"直接发送"}),s.jsx("span",{className:"muted",children:i.peerMsg})]}),s.jsx(Dl,{empty:"(本任务尚未发出任务间消息)",items:i.peerMessages.map((C,N)=>s.jsxs("span",{className:"row-main",children:["[发给 ",String(C.task_id).slice(0,8),"|",C.mode,"|",C.status,"] ",String(C.body||"").slice(0,120)]},N))})]})}function Sg(){const i=Mt(),[r,m]=v.useState(""),o=i.currentTask?["completed","failed","interrupted"].includes(i.currentTask.status):!1;return s.jsxs(s.Fragment,{children:[s.jsx("div",{className:"muted",children:i.threadText||"点任务行查看详情后,这里显示线程元数据"}),s.jsxs(Dt,{children:[s.jsx("input",{type:"text",placeholder:"新线程名",value:r,onChange:_=>m(_.target.value)}),s.jsx("button",{disabled:!o,onClick:()=>i.threadAction("rename",r),children:"重命名"}),s.jsx("button",{disabled:!o,onClick:()=>i.threadAction("archive",""),children:"归档"}),s.jsx("button",{disabled:!o,onClick:()=>i.threadAction("unarchive",""),children:"取消归档"}),s.jsx("button",{disabled:!o,onClick:()=>i.threadAction("compact",""),children:"压缩"}),s.jsx("span",{className:"muted",children:i.threadGateNote}),s.jsx("span",{className:"muted",children:i.threadMsg})]}),s.jsx(Dl,{empty:"暂无其他线程",items:i.threadList.map((_,C)=>s.jsxs("span",{className:"row-main",children:[_.name||_.thread_name||_.id||""," [",_.id||"","]",_.archived?" [已归档]":"",_.cwd?" "+_.cwd:""]},C))}),s.jsx("div",{className:"muted",children:"限制:Docker/异机任务线程操作一律 409;merged/merge-failed 不可改;turn/steer 与 review/start 不在本批。"})]})}function bg(){const i=Mt(),r=i.currentTask;return r?s.jsxs(Ve,{id:"nav-detail",title:"任务详情",children:[s.jsxs("div",{children:["状态 ",s.jsx("b",{className:"st-"+r.status,children:r.status}),"(",Qy[r.status]||"未知状态",")",s.jsx("br",{}),"任务 ",r.id," | turn ",Me(r.turn_id)," | root turn ",Me(r.root_turn_id),s.jsx("br",{}),"executor ",r.executor||""," | 主机 ",r.worktree_host||"",s.jsx("br",{}),"模型 ",r.model||"默认"," | 强度 ",r.effort||"默认",r.child_model||r.child_effort?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),"子任务默认 ",(r.child_model||"跟随本任务")+" / "+(r.child_effort||"跟随本任务")]}):null,s.jsx("br",{}),"权限 ",r.approval||"默认"," | 项目 ",Me(r.project_id),s.jsx("br",{}),"更新 ",Xa(r.updated_at),r.plan?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),"计划模式(只读)"]}):null,r.forked_from?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),"分支自 ",Me(r.forked_from)]}):null,r.goal_mode?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),r.no_time_limit?"goal 模式无敌版(不限时)":"goal 模式(不限轮次,模型宣告完成)"]}):r.verify_command?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),"goal 模式(旧语义,验收命令)"]}):null,r.coordinator_parts?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),"协调员:",r.coordinator_parts.length," 个子任务(",r.coordinator_parts.map(m=>m.name).join("、"),")"]}):null,r.parent_task_id?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),"协调子任务,父任务 ",Me(r.parent_task_id),r.coordinator_part?"("+r.coordinator_part+")":""]}):null,r.attachments&&r.attachments.length?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),"附件:",r.attachments.join("、")]}):null,r.pr_url?s.jsxs(s.Fragment,{children:[s.jsx("br",{}),s.jsxs("a",{href:r.pr_url,target:"_blank",rel:"noopener noreferrer",children:["PR #",r.pr_number]})]}):null,s.jsx("br",{}),"prompt:",r.prompt||""]}),s.jsx(mg,{}),s.jsx("pre",{children:i.detailBody||"(暂无 result/error 输出)"}),i.costBox?s.jsx("pre",{children:i.costBox}):null,s.jsx("h3",{children:"协调子任务"}),s.jsx(vg,{}),s.jsx("h3",{children:"子代理链路"}),s.jsx(yg,{}),s.jsx("h3",{children:"评审队列"}),s.jsx(hg,{}),s.jsx("h3",{children:"运行时审批队列"}),s.jsx(gg,{}),s.jsx("h3",{children:"给其他任务发消息"}),s.jsx(pg,{}),s.jsx("h3",{children:"线程"}),s.jsx(Sg,{})]}):s.jsx(Ve,{id:"nav-detail",title:"任务详情",children:s.jsx("div",{className:"muted",children:"点任务行查看详情"})})}function jg(){const i=Mt(),[r,m]=v.useState(""),[o,_]=v.useState(""),[C,N]=v.useState([]),[x,G]=v.useState(null),[U,Q]=v.useState(!1),[b,B]=v.useState(""),[lt,at]=v.useState(""),I=v.useCallback(async()=>{if(!i.keySaved){N([]);return}try{const nt=await tt($.userKeys);N(nt.keys||[]),at("")}catch(nt){at(nt instanceof Error?nt.message:"加载 key 列表失败")}},[i.keySaved]);v.useEffect(()=>{I()},[I]);const V=async()=>{B(""),at(""),Q(!1);try{const nt=await tt($.userKeys,"POST",{label:o.trim()||"api-key"});G(nt),_(""),B("新 key 已生成,只显示一次:复制后请妥善保存。"),await I()}catch(nt){at(nt instanceof Error?nt.message:"生成 key 失败")}},dt=async()=>{if(x)try{await navigator.clipboard.writeText(x.api_key),Q(!0)}catch{at("复制失败:请手动选中上方 key 文本复制")}},Ot=()=>{x&&(i.saveKeyInput(x.api_key),G(null),B("已存为当前浏览器 key(原有 key 未被撤销,需要时可单独撤销)。"))},te=async nt=>{const Xt=(()=>{try{return _l().slice(-4)}catch{return""}})(),ee=nt.id==="legacy"?"(主 key:撤销后当前浏览器保存的 key 也可能随之失效,需重新登录拿新 key)":nt.hint&&nt.hint===Xt?"(这就是本浏览器当前在用的 key,撤销后本页会立刻未连接)":"";if(window.confirm(`撤销 key「${nt.label}」(后 4 位 ${nt.hint||"无"})吗?${ee}撤销不可恢复。`)){B(""),at("");try{await tt($.userKey(nt.id),"DELETE"),B(`已撤销 key「${nt.label}」。`),await I()}catch(ct){at(ct instanceof Error?ct.message:"撤销失败")}}};return s.jsxs(Ve,{id:"nav-key",title:"API Key",children:[s.jsxs(Dt,{children:[s.jsx("input",{type:"password",placeholder:"粘贴你的 X-API-Key(与调 API 用的同一个)",autoComplete:"off",value:r,onChange:nt=>m(nt.target.value)}),s.jsx("button",{className:"primary",onClick:()=>{i.saveKeyInput(r),m("")},children:"保存到本浏览器"}),s.jsx("button",{onClick:i.clearKeyInput,children:"清除"}),s.jsx("span",{className:"muted",children:i.keyState})]}),s.jsx("div",{children:s.jsx("span",{className:"muted",children:i.quotaText})}),!i.keySaved&&s.jsx("div",{children:s.jsx("span",{className:"muted",children:"先粘贴并保存已有 key,再生成或管理 key(生成接口本身也要用已有 key 鉴权)。"})}),i.keySaved&&s.jsxs(s.Fragment,{children:[s.jsxs(Dt,{children:[s.jsx("input",{placeholder:"新 key 备注(可选,默认 api-key,如 harness2-temp)",autoComplete:"off",value:o,onChange:nt=>_(nt.target.value)}),s.jsx("button",{className:"primary",onClick:()=>void V(),children:"生成新 key"}),s.jsx("button",{onClick:()=>void I(),children:"刷新列表"})]}),x&&s.jsxs("div",{children:[s.jsx("div",{children:s.jsxs("span",{className:"muted",children:["新 key(",x.label,",后 4 位 ",x.hint,"):只显示一次,关闭后无法再查看。"]})}),s.jsxs(Dt,{children:[s.jsx("input",{type:"text",readOnly:!0,value:x.api_key,onFocus:nt=>nt.target.select()}),s.jsx("button",{className:"primary",onClick:()=>void dt(),children:U?"已复制":"复制"}),s.jsx("button",{onClick:Ot,children:"存为当前浏览器 key"}),s.jsx("button",{onClick:()=>G(null),children:"关闭(不再显示)"})]})]}),b&&s.jsx("div",{children:s.jsx("span",{className:"muted",children:b})}),lt&&s.jsx("div",{children:s.jsx("span",{children:lt})}),s.jsxs("div",{children:[C.length===0&&s.jsx("span",{className:"muted",children:"暂无 key(或列表加载失败,见上方报错)。"}),C.map(nt=>s.jsxs("div",{children:[s.jsx("span",{children:nt.label})," ",s.jsxs("span",{className:"muted",children:["后 4 位 ",nt.hint||"无"," · 创建 ",Xa(nt.created_at)," · 最近使用 ",nt.last_used_at?Xa(nt.last_used_at):"未使用过"]})," ",nt.revoked_at?s.jsxs("span",{className:"muted",children:["已撤销(",Xa(nt.revoked_at),")"]}):s.jsx("button",{onClick:()=>void te(nt),children:"撤销"})]},nt.id))]})]})]})}function xg(){const i=Mt();return s.jsxs(Ve,{id:"adminCard",title:s.jsxs(s.Fragment,{children:["管理区 ",s.jsx("span",{className:"muted",children:"(仅全局管理员)"})]}),children:[s.jsx("div",{className:"muted",children:i.adminGate}),i.adminBody?s.jsxs(s.Fragment,{children:[s.jsxs(Dt,{children:[s.jsx("input",{type:"text",placeholder:"要查配额的用户 id",value:i.adminUserId,onChange:r=>i.setAdminUserId(r.target.value)}),s.jsx("button",{onClick:i.adminQuota,children:"查该用户配额"})]}),s.jsxs(Dt,{children:[s.jsx("button",{onClick:i.adminSweep,children:"手动触发 sweep(清理陈旧 running 任务)"}),s.jsx("button",{onClick:i.adminBackup,children:"手动触发备份"})]}),s.jsx("div",{className:"muted",children:i.adminResult})]}):null]})}function Tg(){const i=Mt(),[r,m]=v.useState(""),[o,_]=v.useState(""),[C,N]=v.useState("");return s.jsxs(Ve,{id:"nav-projects",title:"项目",children:[s.jsxs(Dt,{children:[s.jsxs("select",{"aria-label":"项目",value:i.projectId,onChange:x=>i.selectProject(x.target.value),children:[i.projects.length===0?s.jsx("option",{value:"",children:"(先加载项目)"}):null,i.projects.map(x=>s.jsxs("option",{value:x.id,children:[x.name," (",Me(x.id),")"]},x.id))]}),s.jsx("button",{onClick:i.refreshProjects,children:"刷新"})]}),s.jsxs(Dt,{children:[s.jsx("input",{type:"text",placeholder:"新项目名",value:r,onChange:x=>m(x.target.value)}),s.jsxs("select",{"aria-label":"从 GitHub 仓库选",value:o,onChange:x=>{_(x.target.value),x.target.value&&N(x.target.value)},children:[s.jsx("option",{value:"",children:"(从 GitHub App 安装仓库里选,或手动输入)"}),i.githubRepos.map(x=>s.jsx("option",{value:x.clone_url,children:x.repo_full_name},x.clone_url))]}),s.jsx("input",{type:"text",placeholder:"repo_url(可选)",value:C,onChange:x=>N(x.target.value)}),s.jsx("button",{onClick:async()=>{await i.createProject(r,C),m(""),N(""),_("")},children:"新建项目"})]}),s.jsx("span",{className:"muted",children:i.projectMsg})]})}function _g(){const i=Mt(),[r,m]=v.useState(""),[o,_]=v.useState("contributor"),C=i.projects.find(N=>N.id===i.projectId);return s.jsxs(Ve,{title:"项目成员",children:[C?s.jsxs("div",{className:"muted",children:[C.name," | ",C.id," | 仓库 ",C.repo_url||"未绑定"," | 我的角色 ",C.my_role||"未知"]}):s.jsx("div",{className:"muted",children:"先选项目"}),s.jsxs(Dt,{children:[s.jsx("input",{type:"text",placeholder:"成员 user_id 或用户名",value:r,onChange:N=>m(N.target.value)}),s.jsxs("select",{"aria-label":"邀请角色",value:o,onChange:N=>_(N.target.value),children:[s.jsx("option",{value:"viewer",children:"viewer(只读)"}),s.jsx("option",{value:"contributor",children:"contributor(可建任务)"}),s.jsx("option",{value:"maintainer",children:"maintainer(可批合并)"}),s.jsx("option",{value:"owner",children:"owner(可管项目)"})]}),s.jsx("button",{onClick:async()=>{await i.inviteMember(r,o),m("")},children:"邀请成员"}),s.jsx("button",{onClick:i.refreshMembers,children:"刷新成员"})]}),i.memberError?s.jsx("div",{className:"muted",children:i.memberError}):i.members.length===0?s.jsx("div",{className:"muted",children:"先选项目"}):s.jsx(ru,{children:i.members.map(N=>{const x=(N.username||"")+(N.username?"("+String(N.user_id).slice(0,8)+")":String(N.user_id).slice(0,8));return s.jsxs("li",{children:[s.jsx("span",{className:"row-main",children:x}),s.jsx("select",{"aria-label":"成员角色",value:N.role||"viewer",onChange:G=>void i.changeMemberRole(N.user_id,G.target.value),children:ky.map(G=>s.jsx("option",{value:G,children:G},G))})]},N.user_id)})}),s.jsx("span",{className:"muted",children:i.memberMsg})]})}function Eg(){const i=Mt(),[r,m]=v.useState(""),[o,_]=v.useState(""),[C,N]=v.useState("");return s.jsxs(Ve,{id:"nav-env",title:s.jsxs(s.Fragment,{children:["环境 ",s.jsx("span",{className:"muted",children:"(选、建、管:当前项目的基准检出)"})]}),children:[s.jsxs(Dt,{children:[s.jsx("button",{onClick:i.refreshEnvs,children:"刷新"}),s.jsx("span",{className:"muted",children:i.envCount})]}),i.envs.length===0?s.jsx("div",{className:"muted",children:"先选项目"}):s.jsx(ru,{children:i.envs.map(x=>s.jsxs("li",{children:[s.jsxs("span",{className:"row-main",children:[x.name,"(",Me(x.id),") / 分支 ",x.branch||"默认分支"," / 执行器 ",x.executor||"不限"," / 状态 ",x.status,x.status==="failed"&&x.error_text?" / 失败原因:"+x.error_text:""]}),x.status==="archived"?null:s.jsx("button",{onClick:()=>void i.archiveEnv(x.id),children:"归档"})]},x.id))}),s.jsxs(Dt,{children:[s.jsx("input",{type:"text",placeholder:"新环境名",value:r,onChange:x=>m(x.target.value)}),s.jsx("input",{type:"text",placeholder:"分支(可选,空=默认分支)",value:o,onChange:x=>_(x.target.value)}),s.jsxs("select",{"aria-label":"新环境执行器",value:C,onChange:x=>N(x.target.value),children:[s.jsx("option",{value:"",children:"执行器:不限"}),s.jsx("option",{value:"local",children:"local"}),s.jsx("option",{value:"docker",children:"docker"})]}),s.jsx("button",{onClick:async()=>{await i.createEnv(r,o,C),m(""),_("")},children:"新建环境"})]}),s.jsx("span",{className:"muted",children:i.envMsg})]})}function Ng(){const i=Mt(),[r,m]=v.useState("");return s.jsxs(Ve,{title:"仓库与环境上下文",children:[s.jsx("div",{className:"muted",children:i.envRepoLine||"先选项目"}),s.jsx("div",{className:"muted",children:i.envCount}),i.envRepoBar?s.jsxs(Dt,{children:[s.jsxs("select",{"aria-label":"补仓库",value:r,onChange:o=>m(o.target.value),children:[s.jsx("option",{value:"",children:"(从 GitHub App 安装仓库里选)"}),i.githubRepos.map(o=>s.jsx("option",{value:o.clone_url,children:o.repo_full_name},o.clone_url))]}),s.jsx("button",{onClick:()=>void i.saveEnvRepo(r),children:"保存仓库"})]}):null,s.jsx("span",{className:"muted",children:i.envRepoMsg})]})}function Cg(){const i=Mt();return s.jsxs(Ve,{id:"nav-workers",title:"Workers",children:[s.jsx(Dt,{children:s.jsx("button",{onClick:i.refreshWorkers,children:"刷新"})}),i.workersError?s.jsx("div",{className:"muted",children:i.workersError}):i.workers===null?s.jsx("div",{className:"muted",children:i.workersSummary||"未加载"}):i.workers.length===0?s.jsx("div",{className:"muted",children:"无 worker 注册"}):s.jsx(ru,{children:i.workers.map((r,m)=>{const o=r.running===void 0||r.running===null?"?":r.running,_=r.capacity===void 0||r.capacity===null?"?":r.capacity,C=Qh(r),N=r.alive!==void 0&&r.alive!==null?!!r.alive:C.age!==null&&C.age<=120,x=N?" / 在线":" / 离线(占用为上次上报值)";return s.jsx("li",{className:N?"is-online":"is-offline",children:s.jsxs("span",{className:"row-main",children:[r.worker_id||r.id||"?"," / ",r.executor||""," / 占用 ",o,"/",_,x," / 最后心跳 ",C.text]})},m)})})]})}function Ag(){const i=Mt();return s.jsxs(Ve,{title:"健康与配额",children:[s.jsxs(Dt,{children:[s.jsx("span",{className:"muted",children:i.healthText}),s.jsx("button",{onClick:i.refreshHealth,children:"刷新"})]}),s.jsx("div",{children:s.jsx("span",{className:"muted",children:i.quotaText})}),s.jsx("div",{className:"muted",children:i.workersSummary})]})}function Mg(){const i=Mt();return s.jsxs(Ve,{id:"nav-skills",title:"技能与 MCP",children:[s.jsx("div",{className:"muted",children:"反映宿主租户 home,容器内 home 可能不同;重载只在 schema 枚举里,当前 pin 的二进制拒绝时原样 502;OAuth 只发起登录,不代表登录完成。"}),s.jsxs(Dt,{children:[s.jsx("button",{onClick:i.refreshSkills,children:"刷新"}),s.jsx("span",{className:"muted",children:i.skillMsg})]}),s.jsx("h3",{children:"技能"}),i.skillError?s.jsx("div",{className:"muted",children:i.skillError}):s.jsx(Dl,{empty:"暂无技能",items:i.skills.map((r,m)=>s.jsxs("span",{className:"row-main",children:[r.name||r.id||"",r.description?" - "+r.description:"",r.detail?" - "+r.detail:""]},m))}),s.jsx("h3",{children:"应用"}),s.jsx(Dl,{empty:"暂无应用",items:i.apps.map((r,m)=>s.jsxs("span",{className:"row-main",children:[r.name||r.id||"",r.description?" - "+r.description:""]},m))}),s.jsx("h3",{children:"MCP 服务"}),s.jsx(Dl,{empty:"暂无 MCP 服务",items:i.mcpServers.map((r,m)=>{const o=r.detail||r.details||r.state||"";return s.jsxs("span",{className:"row-main",children:[r.name||r.id||""," | 状态 ",r.status||"",o?" | "+o:""]},m)})})]})}function Og(){const i=Mt(),[r,m]=v.useState("");return s.jsxs(Ve,{title:"MCP 操作",children:[s.jsxs(Dt,{children:[s.jsx("button",{onClick:i.reloadMcp,children:"重载配置"}),s.jsx("span",{className:"muted",children:i.mcpMsg})]}),s.jsxs(Dt,{children:[s.jsx("input",{type:"text",placeholder:"MCP 服务名",value:r,onChange:o=>m(o.target.value)}),s.jsx("button",{onClick:()=>void i.mcpOauth(r),children:"发起 OAuth 登录"}),s.jsx("span",{className:"muted",children:i.mcpOauthMsg})]}),i.mcpOauthBox?s.jsx("pre",{children:i.mcpOauthBox}):null]})}function zg(){const i=Mt(),[r,m]=v.useState(""),[o,_]=v.useState(""),[C,N]=v.useState(""),[x,G]=v.useState("");return s.jsxs(s.Fragment,{children:[s.jsx("input",{placeholder:"自动化名称(同项目内启用中的名称唯一)",style:{width:"100%"},value:r,onChange:U=>m(U.target.value)}),s.jsx("textarea",{placeholder:"定时任务内容(创建只登记定义与到期状态;执行由后续执行器经 claim 抢单后建任务,不另起调度器)",value:o,onChange:U=>_(U.target.value)}),s.jsxs(Dt,{children:[s.jsx("input",{placeholder:"间隔秒数(默认 86400,最小 60)",style:{width:"40%"},value:C,onChange:U=>N(U.target.value)}),s.jsx("input",{placeholder:"仓库(可选)",style:{width:"40%"},value:x,onChange:U=>G(U.target.value)}),s.jsx("button",{className:"primary",onClick:async()=>{await i.createAutomation(r,o,C,x),m(""),_(""),N(""),G("")},children:"创建自动化"}),s.jsx("button",{onClick:i.refreshAutomations,children:"刷新自动化"}),s.jsx("span",{className:"muted",children:i.autoMsg})]}),s.jsx(Dl,{empty:"(暂无自动化)",items:i.automations.map(U=>s.jsxs("span",{className:"row-main",children:["[",U.status,U.due?"|到期":"","] ",U.name,"|每 ",U.interval_seconds," 秒|下次 ",Xa(U.next_run_at),U.status==="active"?s.jsxs(s.Fragment,{children:[" ",s.jsx("button",{onClick:()=>i.cancelAutomation(U.id),children:"取消"})]}):null]},U.id))})]})}function Rg(){var m,o,_,C;const i=Mt(),r=i.triage;return i.triageError?s.jsx("div",{className:"muted",children:i.triageError}):r?s.jsxs(s.Fragment,{children:[s.jsxs("div",{className:"data-group",children:[s.jsxs("b",{children:["到期自动化(",((m=r.counts)==null?void 0:m.automations_due)||0,")"]}),s.jsx(Dl,{empty:"(无)",items:(r.automations_due||[]).map((N,x)=>s.jsxs("span",{className:"row-main",children:[N.name,"|每 ",N.interval_seconds," 秒"]},x))})]}),s.jsxs("div",{className:"data-group",children:[s.jsxs("b",{children:["失败/中断任务(",((o=r.counts)==null?void 0:o.failed_tasks)||0,")"]}),s.jsx(Dl,{empty:"(无)",items:(r.failed_tasks||[]).map((N,x)=>s.jsxs("span",{className:"row-main",children:[Me(N.id),"|",N.status,"|",String(N.prompt||"").slice(0,80)]},x))})]}),s.jsxs("div",{className:"data-group",children:[s.jsxs("b",{children:["待处理后续消息(",((_=r.counts)==null?void 0:_.pending_followups)||0,")"]}),s.jsx(Dl,{empty:"(无)",items:(r.pending_followups||[]).map((N,x)=>s.jsxs("span",{className:"row-main",children:["任务 ",Me(N.task_id),"|",String(N.body||"").slice(0,80)]},x))})]}),s.jsxs("div",{className:"data-group",children:[s.jsxs("b",{children:["最近自动化产出(",((C=r.counts)==null?void 0:C.recent_runs)||0,")"]}),s.jsx(Dl,{empty:"(无)",items:(r.recent_runs||[]).map((N,x)=>s.jsxs("span",{className:"row-main",children:[Me(N.automation_id),"|",N.status,N.task_id?"|任务 "+Me(N.task_id):"",N.task_status?"|"+N.task_status:""]},x))})]})]}):s.jsx("div",{className:"muted",children:"(先选项目)"})}function Dg(){return s.jsx(Ve,{id:"nav-automations",title:"自动化",children:s.jsx(zg,{})})}function Ug(){const i=Mt();return s.jsxs(Ve,{id:"nav-triage",title:"分诊收件箱",children:[s.jsx(Dt,{children:s.jsx("button",{onClick:i.refreshTriage,children:"刷新分诊"})}),s.jsx(Rg,{})]})}const Hg=[["#sec-dispatch","派发"],["#sec-tasks","任务"],["#sec-conv","对话"],["#sec-projects","项目"],["#sec-env","环境"],["#sec-workers","Workers"],["#sec-skills","技能"],["#sec-automations","自动化"],["#sec-triage","分诊"],["#sec-admin","管理区"],["#sec-key","API Key"]];function Bg(){return s.jsxs("header",{className:"dash-topbar",children:[s.jsxs("div",{className:"dash-topbar-row",children:[s.jsx("span",{className:"dash-brand",children:"Codex 命令台"}),s.jsx(qg,{}),s.jsx("span",{className:"muted dash-hide-sm",children:s.jsx(Yg,{})}),s.jsx("span",{className:"muted dash-hide-sm",children:s.jsx(wg,{})}),s.jsx("span",{className:"muted dash-stamp",children:s.jsx(Gg,{})})]}),s.jsx("nav",{className:"dash-nav","aria-label":"页面导航",children:Hg.map(([i,r])=>s.jsx("a",{href:i,children:r},i))})]})}function qg(){const i=Mt();return s.jsx("span",{className:i.keySaved?"dash-status ok":"dash-status off",children:i.keySaved?"● 已连接":"○ 未连接"})}function Yg(){return s.jsx(s.Fragment,{children:Mt().healthText})}function wg(){return s.jsx(s.Fragment,{children:Mt().quotaText})}function Gg(){return s.jsxs(s.Fragment,{children:["自动刷新 · 每 10 秒 · 已刷新 ",Mt().refreshStamp]})}function Qg(){const i=tg();$y(m=>i.refreshAll(m),1e4),Iy(()=>i.refreshSlow(),6e4);const r=()=>{var m;(m=document.getElementById("sec-dispatch"))==null||m.scrollIntoView({behavior:"smooth"})};return s.jsxs(Gh.Provider,{value:i,children:[s.jsx(Bg,{}),s.jsxs("main",{className:"dash-page",children:[s.jsx(eg,{}),s.jsxs("div",{className:"dash-col dash-col-left",children:[s.jsxs("section",{id:"sec-tasks",className:"dash-section card",children:[s.jsx("h2",{children:"任务列表"}),s.jsx(ig,{onNew:r})]}),s.jsxs("section",{id:"sec-projects",className:"dash-section",children:[s.jsx(Tg,{}),s.jsx(_g,{})]})]}),s.jsxs("div",{className:"dash-col dash-col-mid",children:[s.jsx("section",{id:"sec-dispatch",className:"dash-section",children:s.jsx(lg,{})}),s.jsxs("section",{id:"sec-conv",className:"dash-section",children:[s.jsxs("div",{className:"card",children:[s.jsx("h2",{children:"对话"}),s.jsx(dg,{})]}),s.jsx(bg,{})]})]}),s.jsxs("div",{className:"dash-col dash-col-right",children:[s.jsxs("section",{id:"sec-env",className:"dash-section",children:[s.jsx(Eg,{}),s.jsx(Ng,{})]}),s.jsxs("section",{id:"sec-workers",className:"dash-section",children:[s.jsx(Cg,{}),s.jsx(Ag,{})]}),s.jsxs("section",{id:"sec-skills",className:"dash-section",children:[s.jsx(Mg,{}),s.jsx(Og,{})]}),s.jsx("section",{id:"sec-automations",className:"dash-section",children:s.jsx(Dg,{})}),s.jsx("section",{id:"sec-triage",className:"dash-section",children:s.jsx(Ug,{})}),s.jsx("section",{id:"sec-admin",className:"dash-section",children:s.jsx(xg,{})}),s.jsx("section",{id:"sec-key",className:"dash-section",children:s.jsx(jg,{})})]})]})]})}wy.createRoot(document.getElementById("root")).render(s.jsx(zy.StrictMode,{children:s.jsx(Qg,{})})); diff --git a/cloud/server.py b/cloud/server.py index 006b536..6b20cee 100644 --- a/cloud/server.py +++ b/cloud/server.py @@ -1233,6 +1233,37 @@ def _attachments_map(conn, task_ids): return out +def _task_tokens_map(conn, task_ids): + """一批任务的 cost_records token 合计:{task_id: total_tokens}。 + + 任务列表接口用一次查询取全量,不按任务逐条调成本接口 + (一屏 N 个任务就是 N 个请求,会拖慢列表刷新、还容易撞 + 限流)。无成本记录的任务计 0(不是缺失);cost_records + 表还不存在时(全新库、CostStore 未建过表)同样全计 0, + 列表绝不因此失败——0 在这里就是“未记录到任何 token”, + 与 _metrics 的缺表按空统计口径一致。 + """ + out = {tid: 0 for tid in task_ids} + if not task_ids: + return out + placeholders = ", ".join("?" * len(task_ids)) + try: + rows = conn.execute( + "SELECT task_id, COALESCE(SUM(total_tokens),0) AS t" + " FROM cost_records WHERE task_id IN (%s)" + " GROUP BY task_id" % placeholders, + tuple(task_ids), + ).fetchall() + except (sqlite3.OperationalError, _PgOperationalError): + return out + for row in rows: + tid = row[0] if isinstance(row, tuple) else row["task_id"] + if tid in out: + out[tid] = int(row[1] if isinstance(row, tuple) + else row["t"] or 0) + return out + + def _ensure_task_checkpoints_table(conn): """幂等建断点表(P0 三件其一):server init_db(PG 与 SQLite 两支)与 worker 迁移兜底共用。DOUBLE PRECISION 在两后端都可用。 @@ -1663,7 +1694,7 @@ def _row_get(row, name): return None -def _task_to_dict(row, attachments=None): +def _task_to_dict(row, attachments=None, total_tokens=None): depends_on = row["depends_on"] try: depends_on = json.loads(depends_on) if depends_on else [] @@ -1719,6 +1750,9 @@ def _task_to_dict(row, attachments=None): "interrupted_reason": _row_get(row, "interrupted_reason"), "auto_resume_count": _row_get(row, "auto_resume_count") or 0, "no_time_limit": _row_get(row, "no_time_limit") or 0, + # 列表接口由 _task_tokens_map 一次聚合传入;其它调用方 + # 不传时为 None(前端显示占位),绝不把“未算”写成 0。 + "total_tokens": total_tokens, } @@ -4390,10 +4424,12 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler): (project_id,), ).fetchall() amap = _attachments_map(conn, [r["id"] for r in rows]) + tmap = _task_tokens_map(conn, [r["id"] for r in rows]) finally: conn.close() self._send_json(200, {"tasks": [ - _task_to_dict(r, amap[r["id"]]) for r in rows]}) + _task_to_dict(r, amap[r["id"]], tmap.get(r["id"], 0)) + for r in rows]}) def _fetch_task(self, task_id): conn = self._db() diff --git a/cloud/tests/test_task_list_tokens.py b/cloud/tests/test_task_list_tokens.py new file mode 100644 index 0000000..386ab33 --- /dev/null +++ b/cloud/tests/test_task_list_tokens.py @@ -0,0 +1,122 @@ +"""任务列表 total_tokens:一次聚合,不逐个调成本接口。 + +用真实 HTTP 服务 + 临时 SQLite:建两个任务,给其中一个记 +两条成本记录(actual + estimated),列表应一次回两行的 +total_tokens(有记录的加总、无记录的 0),且不回 404/500; +_task_tokens_map 在 cost_records 表不存在时也不炸列表。 +""" + +import http.client +import json +import os +import sys +import tempfile +import threading +import unittest +from pathlib import Path + +try: + from cloud.auth import AuthStore + from cloud.cost import CostStore + from cloud.server import ( + _task_to_dict, _task_tokens_map, create_server) +except ImportError: + sys.path.insert(0, str(Path(__file__).resolve().parents[2])) + from cloud.auth import AuthStore + from cloud.cost import CostStore + from cloud.server import ( + _task_to_dict, _task_tokens_map, create_server) + + +class ListTokensTestCase(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.db_path = os.path.join(self.tmp.name, "control.db") + 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.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 body is not None: + payload = json.dumps(body).encode("utf-8") + hdrs["Content-Type"] = "application/json" + if key is not None: + hdrs["X-API-Key"] = key + try: + conn.request(method, path, body=payload, headers=hdrs) + resp = conn.getresponse() + data = resp.read() + finally: + conn.close() + try: + parsed = json.loads(data.decode("utf-8")) if data else None + except ValueError: + parsed = None + return resp.status, parsed + + def test_list_returns_aggregated_total_tokens(self): + pid = self.project["id"] + _, t1 = self._req("POST", "/tasks", + {"project_id": pid, "prompt": "有成本"}, + self.owner_key) + _, t2 = self._req("POST", "/tasks", + {"project_id": pid, "prompt": "无成本"}, + self.owner_key) + store = CostStore(self.db_path) + try: + store.record(t1["id"], pid, "m", input_tokens=100, + output_tokens=50, source="actual") + store.record(t1["id"], pid, "m", input_tokens=20, + output_tokens=0, source="estimated") + finally: + store._conn.close() + status, body = self._req( + "GET", "/tasks?project_id=%s" % pid, key=self.owner_key) + self.assertEqual(status, 200, body) + by_id = {t["id"]: t for t in body["tasks"]} + self.assertEqual(by_id[t1["id"]]["total_tokens"], 170) + self.assertEqual(by_id[t2["id"]]["total_tokens"], 0) + + def test_tokens_map_missing_table_is_zero_not_error(self): + import sqlite3 + conn = sqlite3.connect(":memory:") + try: + self.assertEqual( + _task_tokens_map(conn, ["t1", "t2"]), + {"t1": 0, "t2": 0}) + finally: + conn.close() + + def test_task_to_dict_default_total_tokens_is_none(self): + row = {"id": "t1", "project_id": "p1", "prompt": "p", + "status": "completed", "idempotency_key": None, + "depends_on": "[]", "priority": 0, "created_by": "u", + "created_at": 1.0, "updated_at": 2.0, + "last_heartbeat": None} + self.assertIsNone(_task_to_dict(row, [])["total_tokens"]) + self.assertEqual( + _task_to_dict(row, [], 42)["total_tokens"], 42) + + +if __name__ == "__main__": + unittest.main()