diff --git a/frontend/components/store.tsx b/frontend/components/store.tsx index 62532d0..d74edb6 100644 --- a/frontend/components/store.tsx +++ b/frontend/components/store.tsx @@ -211,6 +211,10 @@ export function DashboardProvider({ const [events, setEvents] = useState([]); const eventsRef = useRef([]); eventsRef.current = events; + /* Bumped on every selectRun: an in-flight loadRun that resolves after the user picked + * a different run must drop its writes, or run A's checkmark/log/events land on run + * B's panel (the 5s tick keeps polls in flight constantly). */ + const runGenRef = useRef(0); const [approvals, setApprovals] = useState([]); const [hosts, setHosts] = useState([]); const [commands, setCommands] = useState([]); @@ -270,12 +274,15 @@ export function DashboardProvider({ const loadRun = useCallback(async (projectId: string, name: string) => { const path = `/projects/${encodeURIComponent(projectId)}/agents/${encodeURIComponent(name)}`; + const gen = runGenRef.current; + const stale = () => gen !== runGenRef.current; try { const cm = await clientRef.current.api(`${path}/checkmark`); + if (stale()) return; setCheckmark(cm); setCheckmarkMissing(false); } catch (e) { - if (e instanceof AuthError) return; + if (e instanceof AuthError || stale()) return; if ((e as ApiError).status === 404) { setCheckmark(null); setCheckmarkMissing(true); @@ -285,9 +292,10 @@ export function DashboardProvider({ const entries = await clientRef.current.api( `${path}/log?limit=${LOG_LIMIT}&offset=${logOffsetRef.current}`, ); + if (stale()) return; setLog(entries); } catch (e) { - swallow(e); + if (!stale()) swallow(e); } try { // Cursor poll: only events newer than what we already hold come back. @@ -296,9 +304,16 @@ export function DashboardProvider({ const fresh = await clientRef.current.api( `${path}/events?after_id=${after}&limit=500`, ); - if (fresh.length) setEvents((prev) => [...prev, ...fresh]); + if (stale() || fresh.length === 0) return; + // Dedup by id at append time: overlapping polls for the same run can both fetch + // from the same cursor; only genuinely-new rows may append. + setEvents((prev) => { + const last = prev.length ? prev[prev.length - 1].id : 0; + const add = fresh.filter((e) => e.id > last); + return add.length ? [...prev, ...add] : prev; + }); } catch (e) { - swallow(e); + if (!stale()) swallow(e); } }, []); @@ -419,6 +434,7 @@ export function DashboardProvider({ const selectRun = useCallback( (projectId: string, name: string) => { + runGenRef.current += 1; // invalidate any in-flight loadRun for the previous run setSelectedRun({ projectId, name }); setLogOffset(0); logOffsetRef.current = 0; diff --git a/src/handler/api/routes/login.py b/src/handler/api/routes/login.py index fbec472..297e832 100644 --- a/src/handler/api/routes/login.py +++ b/src/handler/api/routes/login.py @@ -50,7 +50,7 @@ def enqueue_login_submit(body: LoginSubmitIn, conn: Connection = Depends(db_conn nothing to paste into. No prior login_start leaves the pin empty (single-worker deployments behave exactly as before). """ - started = repo.get_latest_finished_command(conn, "login_start") + started = repo.get_latest_claimed_command(conn, "login_start") return repo.enqueue_command( conn, "login_submit", diff --git a/src/handler/api/static/404.html b/src/handler/api/static/404.html index 7ebb5db..bd71adb 100644 --- a/src/handler/api/static/404.html +++ b/src/handler/api/static/404.html @@ -1 +1 @@ -404: This page could not be found.Handler · Claude Activity

404

This page could not be found.

\ No newline at end of file +404: This page could not be found.Handler · Claude Activity

404

This page could not be found.

\ No newline at end of file diff --git a/src/handler/api/static/_next/static/chunks/app/page-0637b8cf149a88ec.js b/src/handler/api/static/_next/static/chunks/app/page-0637b8cf149a88ec.js deleted file mode 100644 index 493e613..0000000 --- a/src/handler/api/static/_next/static/chunks/app/page-0637b8cf149a88ec.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[931],{3963:function(e,t,s){Promise.resolve().then(s.bind(s,9859))},9859:function(e,t,s){"use strict";s.r(t),s.d(t,{default:function(){return er}});var a,n=s(7437),r=s(2265);let l=null!==(a=s(257).env.NEXT_PUBLIC_API_BASE)&&void 0!==a?a:"";class i extends Error{constructor(e="unauthorized"){super(e),this.name="AuthError"}}let c=(0,r.createContext)(null);function o(){let e=(0,r.useContext)(c);if(!e)throw Error("useDashboard outside provider");return e}function d(e){let{token:t,onUnauthorized:s,children:a}=e,o=(0,r.useMemo)(()=>(function(e,t){async function s(s,a){var n;let r=(null==a?void 0:a.body)!==void 0&&(null==a?void 0:a.body)!==null,c=await fetch(l+s,{method:null!==(n=null==a?void 0:a.method)&&void 0!==n?n:r?"POST":"GET",headers:{Authorization:"Bearer ".concat(e),...r?{"Content-Type":"application/json"}:{}},body:r?JSON.stringify(a.body):void 0});if(401===c.status)throw t(),new i;if(!c.ok){let e=c.statusText;try{let t=await c.json();t&&void 0!==t.detail&&(e="string"==typeof t.detail?t.detail:JSON.stringify(t.detail))}catch(e){}let t=Error(e);throw t.status=c.status,t}if(204===c.status)return;let o=await c.text();return o?JSON.parse(o):void 0}async function a(e,t){var a,n;let r=null!==(a=null==t?void 0:t.attempts)&&void 0!==a?a:60,l=null!==(n=null==t?void 0:t.intervalMs)&&void 0!==n?n:500;for(let t=0;tsetTimeout(e,l))}return null}return{api:s,trackCommand:a}})(t,s),[t,s]),d=(0,r.useRef)(o);d.current=o;let[u,h]=(0,r.useState)("runs"),[m,p]=(0,r.useState)([]),[x,v]=(0,r.useState)([]),[j,g]=(0,r.useState)(""),[y,f]=(0,r.useState)(null),[b,k]=(0,r.useState)(null),[w,N]=(0,r.useState)(!1),[_,C]=(0,r.useState)([]),[S,R]=(0,r.useState)(0),[I,T]=(0,r.useState)([]),A=(0,r.useRef)([]);A.current=I;let[z,E]=(0,r.useState)([]),[P,O]=(0,r.useState)([]),[M,L]=(0,r.useState)([]),[D,U]=(0,r.useState)([]),[F,B]=(0,r.useState)({log:[],context:[]}),[H,W]=(0,r.useState)({text:"",error:!1,busy:!1}),[q,G]=(0,r.useState)(""),[J,K]=(0,r.useState)(!0),[Y,V]=(0,r.useState)({status:"idle",url:"",message:""}),Q=(0,r.useRef)(u);Q.current=u;let X=(0,r.useRef)(j);X.current=j;let $=(0,r.useRef)(y);$.current=y;let Z=(0,r.useRef)(S);Z.current=S;let ee=e=>{e instanceof i||G(e.message)},et=(0,r.useCallback)(async()=>{try{let e=await d.current.api("/projects");p(e),G(""),g(t=>{var s,a;return t||(null!==(a=null===(s=e[0])||void 0===s?void 0:s.id)&&void 0!==a?a:"")})}catch(e){ee(e)}},[]),es=(0,r.useCallback)(async e=>{try{let t=await Promise.all(e.map(e=>d.current.api("/projects/".concat(encodeURIComponent(e.id),"/agents")).catch(()=>[])));v(t.flat())}catch(e){ee(e)}},[]),ea=(0,r.useCallback)(async(e,t)=>{let s="/projects/".concat(encodeURIComponent(e),"/agents/").concat(encodeURIComponent(t));try{let e=await d.current.api("".concat(s,"/checkmark"));k(e),N(!1)}catch(e){if(e instanceof i)return;404===e.status?(k(null),N(!0)):ee(e)}try{let e=await d.current.api("".concat(s,"/log?limit=").concat(100,"&offset=").concat(Z.current));C(e)}catch(e){ee(e)}try{let e=A.current,t=e.length?e[e.length-1].id:0,a=await d.current.api("".concat(s,"/events?after_id=").concat(t,"&limit=500"));a.length&&T(e=>[...e,...a])}catch(e){ee(e)}},[]),en=(0,r.useCallback)(async e=>{if(!e){E([]);return}try{E(await d.current.api("/projects/".concat(encodeURIComponent(e),"/approvals")))}catch(e){ee(e)}},[]),er=(0,r.useCallback)(async()=>{try{O(await d.current.api("/hosts"))}catch(e){ee(e)}},[]),el=(0,r.useCallback)(async()=>{try{L(await d.current.api("/commands?limit=50"))}catch(e){ee(e)}},[]),ei=(0,r.useCallback)(async()=>{try{U(await d.current.api("/schedules"))}catch(e){ee(e)}},[]),ec=(0,r.useCallback)(async()=>{try{let[e,t]=await Promise.all([d.current.api("/shared/log"),d.current.api("/shared/context")]);B({log:e,context:t})}catch(e){ee(e)}},[]),eo=(0,r.useCallback)(async()=>{let e=await d.current.api("/projects").catch(e=>(ee(e),null));e&&(p(e),g(t=>{var s,a;return t||(null!==(a=null===(s=e[0])||void 0===s?void 0:s.id)&&void 0!==a?a:"")}),await es(e));let t=Q.current,s=$.current;s&&await ea(s.projectId,s.name),"approvals"===t&&await en(X.current),"servers"===t&&await er(),"activity"===t&&await el(),"schedules"===t&&await ei(),"shared"===t&&await ec()},[es,ea,en,er,el,ei,ec]);(0,r.useEffect)(()=>{let e=!0;(async()=>{K(!0),await eo(),e&&K(!1)})();let t=setInterval(()=>{document.hidden||eo()},5e3);return()=>{e=!1,clearInterval(t)}},[eo]);let ed=(0,r.useCallback)(e=>{h(e),W({text:"",error:!1,busy:!1}),"approvals"===e&&en(X.current),"servers"===e&&er(),"activity"===e&&el(),"schedules"===e&&ei(),"shared"===e&&ec()},[en,er,el,ei,ec]),eu=(0,r.useCallback)(e=>{g(e),"approvals"===Q.current&&en(e)},[en]),eh=(0,r.useCallback)((e,t)=>{f({projectId:e,name:t}),R(0),Z.current=0,k(null),N(!1),C([]),T([]),A.current=[],ea(e,t)},[ea]),em=(0,r.useCallback)(e=>{let t=Math.max(0,S+100*e);if(t===S)return;R(t),Z.current=t;let s=$.current;s&&ea(s.projectId,s.name)},[S,ea]),ep=(0,r.useCallback)(()=>{eo()},[eo]),ex=(0,r.useCallback)(async(e,t,s)=>{W({text:"".concat(s,": queued…"),error:!1,busy:!0});try{let a=await d.current.api(e,{method:"POST",body:t}),n=await d.current.trackCommand(a.id);if(!n)return W({text:"".concat(s,": still running (see Activity). Is the worker up?"),error:!1,busy:!1}),null;let r="done"===n.status,l=n.error||(n.result?JSON.stringify(n.result):"");return W({text:"".concat(s," ").concat(r?"done":"failed").concat(l?" — "+l:""),error:!r,busy:!1}),n}catch(e){if(e instanceof i)return null;return W({text:"".concat(s," failed: ").concat(e.message),error:!0,busy:!1}),null}},[]),ev=(0,r.useCallback)(async e=>{let t={name:e.name.trim(),role:e.role||null,task:e.task.trim()||null};"worktree"===e.placement&&e.worktree.trim()&&(t.worktree=e.worktree.trim()),"subdir"===e.placement&&e.subdir.trim()&&(t.subdir=e.subdir.trim());let s=encodeURIComponent(X.current),a=await ex("/projects/".concat(s,"/agents/spawn"),t,"spawn ".concat(t.name));return await es(m),(null==a?void 0:a.status)==="done"},[ex,es,m]),ej=(0,r.useCallback)(async(e,t)=>{let s=encodeURIComponent(e);await ex("/projects/".concat(s,"/agents/").concat(encodeURIComponent(t),"/kill"),void 0,"kill ".concat(t)),await es(m)},[ex,es,m]),eg=(0,r.useCallback)(async(e,t)=>{let s=encodeURIComponent(e);try{var a;await d.current.api("/projects/".concat(s,"/agents/").concat(encodeURIComponent(t)),{method:"DELETE"}),W({text:"agent '".concat(t,"' row deleted"),error:!1,busy:!1}),(null===(a=$.current)||void 0===a?void 0:a.name)===t&&f(null),await es(m)}catch(e){if(e instanceof i)return;W({text:e.message,error:!0,busy:!1})}},[es,m]),ey=(0,r.useCallback)(async(e,t)=>{let s=$.current;if(!s)return!1;let a="/projects/".concat(encodeURIComponent(s.projectId),"/agents/").concat(encodeURIComponent(s.name));try{return await d.current.api("".concat(a,"/answer"),{method:"POST",body:{answer:e}}),t?await ex("".concat(a,"/resume"),{answer:e},"resume"):W({text:"Answer saved (agent still paused).",error:!1,busy:!1}),await es(m),await ea(s.projectId,s.name),!0}catch(e){if(e instanceof i)return!1;return W({text:e.message,error:!0,busy:!1}),!1}},[ex,es,ea,m]),ef=(0,r.useCallback)(async e=>{try{var t,s;let a="server"===e.mode?{git_server:e.git_server,repo:e.repo.trim(),id:e.id.trim()||null,credential_ref:e.credential_ref.trim()||null,init_mise:e.init_mise}:{id:e.id.trim(),root_dir:e.root_dir.trim(),git_remote:e.git_remote.trim()||null,credential_ref:e.credential_ref.trim()||null,init_mise:e.init_mise},n=await d.current.api("/projects",{method:"POST",body:a});if(await et(),null!=n.sync_command_id){W({text:"repository '".concat(n.id,"': cloning…"),error:!1,busy:!0});let e=await d.current.trackCommand(n.sync_command_id);e?"done"===e.status?W({text:"repository '".concat(n.id,"' registered and cloned"),error:!1,busy:!1}):W({text:"repository '".concat(n.id,"' registered but the clone failed — ").concat(null!==(t=e.error)&&void 0!==t?t:""),error:!0,busy:!1}):W({text:"repository '".concat(n.id,"' registered; clone still running (see Activity). Is the worker up?"),error:!1,busy:!1})}else W({text:"repository '".concat(n.id,"' registered"),error:!1,busy:!1});if(null!=n.mise_init_command_id){W({text:"repository '".concat(n.id,"': launching a mise-init agent to create .mise.toml…"),error:!1,busy:!0});let e=await d.current.trackCommand(n.mise_init_command_id);e?"done"===e.status?W({text:"repository '".concat(n.id,"' registered; a mise-init agent is now writing, committing, and pushing .mise.toml (watch it in Runs)."),error:!1,busy:!1}):W({text:"repository '".concat(n.id,"' registered but the mise-init agent failed to launch — ").concat(null!==(s=e.error)&&void 0!==s?s:""),error:!0,busy:!1}):W({text:"repository '".concat(n.id,"' registered; mise-init still starting (see Activity). Is the worker up?"),error:!1,busy:!1})}return!0}catch(e){if(e instanceof i)return!1;return W({text:e.message,error:!0,busy:!1}),!1}},[et]),eb=(0,r.useCallback)(async e=>{await ex("/projects/".concat(encodeURIComponent(e),"/sync"),void 0,"pull ".concat(e))},[ex]),ek=(0,r.useCallback)(async(e,t)=>{try{return await d.current.api("/projects/".concat(encodeURIComponent(e)),{method:"PATCH",body:{root_dir:t.root_dir.trim(),git_remote:t.git_remote.trim()||null,credential_ref:t.credential_ref.trim()||null}}),W({text:"repository '".concat(e,"' updated"),error:!1,busy:!1}),await et(),!0}catch(e){if(e instanceof i)return!1;return W({text:e.message,error:!0,busy:!1}),!1}},[et]),ew=(0,r.useCallback)(async e=>{try{await d.current.api("/projects/".concat(encodeURIComponent(e)),{method:"DELETE"}),W({text:"repository '".concat(e,"' removed"),error:!1,busy:!1}),g(t=>t===e?"":t),await et()}catch(e){if(e instanceof i)return;W({text:e.message,error:!0,busy:!1})}},[et]),eN=(0,r.useCallback)(async e=>{let t=encodeURIComponent(X.current);await ex("/projects/".concat(t,"/approvals"),{branch:e.branch.trim(),status:e.status,agent_name:e.agent_name.trim()||null,sha:e.sha.trim()||null,note:e.note.trim()||null},"".concat(e.status," ").concat(e.branch)),await en(X.current)},[ex,en]),e_=(0,r.useCallback)(async e=>{try{return await d.current.api("/hosts",{method:"POST",body:{hostname:e.hostname.trim(),forge_type:e.forge_type,token_env_var:e.token_env_var.trim()||null,base_url:e.base_url.trim()||null,token:e.token.trim()||null,generate_ssh_key:e.generate_ssh_key}}),W({text:"git server '".concat(e.hostname,"' added"),error:!1,busy:!1}),await er(),!0}catch(e){if(e instanceof i)return!1;return W({text:e.message,error:!0,busy:!1}),!1}},[er]),eC=(0,r.useCallback)(async(e,t)=>{try{let s={forge_type:t.forge_type,token_env_var:t.token_env_var.trim()||null,base_url:t.base_url.trim()||null};return t.token.trim()&&(s.token=t.token.trim()),t.generate_ssh_key&&(s.regenerate_ssh_key=!0),await d.current.api("/hosts/".concat(encodeURIComponent(e)),{method:"PATCH",body:s}),W({text:"git server '".concat(e,"' updated"),error:!1,busy:!1}),await er(),!0}catch(e){if(e instanceof i)return!1;return W({text:e.message,error:!0,busy:!1}),!1}},[er]),eS=(0,r.useCallback)(async(e,t)=>{try{return await d.current.api("/projects/".concat(encodeURIComponent(e),"/schedules"),{method:"POST",body:{name_prefix:t.name_prefix.trim(),task:t.task.trim(),interval_seconds:t.interval_seconds,role:t.role||null}}),W({text:"schedule '".concat(t.name_prefix,"' created — first run on the worker's next pass"),error:!1,busy:!1}),await ei(),!0}catch(e){if(e instanceof i)return!1;return W({text:e.message,error:!0,busy:!1}),!1}},[ei]),eR=(0,r.useCallback)(async(e,t)=>{try{return await d.current.api("/schedules/".concat(e),{method:"PATCH",body:t}),await ei(),!0}catch(e){if(e instanceof i)return!1;return W({text:e.message,error:!0,busy:!1}),!1}},[ei]),eI=(0,r.useCallback)(async e=>{try{await d.current.api("/schedules/".concat(e),{method:"DELETE"}),W({text:"schedule ".concat(e," removed"),error:!1,busy:!1}),await ei()}catch(e){if(e instanceof i)return;W({text:e.message,error:!0,busy:!1})}},[ei]),eT=(0,r.useCallback)(async e=>{try{await d.current.api("/hosts/".concat(encodeURIComponent(e)),{method:"DELETE"}),W({text:"git server '".concat(e,"' removed"),error:!1,busy:!1}),await er()}catch(e){if(e instanceof i)return;W({text:e.message,error:!0,busy:!1})}},[er]),eA=(0,r.useCallback)(async()=>{await ex("/poll-ci",void 0,"poll-ci (all projects)"),await el()},[ex,el]),ez=(0,r.useCallback)(async()=>{V({status:"starting",url:"",message:"Opening `claude /login` in the control container and selecting the subscription account…"});try{let e=await d.current.api("/login/start",{method:"POST"}),t=await d.current.trackCommand(e.id,{attempts:180});if(!t){V({status:"error",url:"",message:"Still starting (see Activity). Is the control worker running?"});return}if("done"!==t.status){V({status:"error",url:"",message:t.error||"Failed to start login."});return}let s=t.result&&"string"==typeof t.result.url?t.result.url:"";if(!s){V({status:"error",url:"",message:"No login URL was returned by claude."});return}V({status:"awaiting",url:s,message:"Authorize in the window below (or open it in a new tab), then paste the code claude gives you."})}catch(e){if(e instanceof i)return;V({status:"error",url:"",message:e.message})}},[]),eE=(0,r.useCallback)(async e=>{let t=e.trim();if(!t)return!1;V(e=>({...e,status:"submitting",message:"Submitting the authorization code…"}));try{let e=await d.current.api("/login/submit",{method:"POST",body:{code:t}}),s=await d.current.trackCommand(e.id,{attempts:60});if(!s)return V(e=>({...e,status:"awaiting",message:"Submit still running (see Activity). Is the control worker running?"})),!1;if("done"===s.status)return V({status:"done",url:"",message:"Claude Code is now logged in on the host — new agents will use this account."}),!0;return V(e=>({...e,status:"awaiting",message:s.error||"Login was not confirmed. Re-check the code, or restart the flow."})),!1}catch(e){if(e instanceof i)return!1;return V(t=>({...t,status:"awaiting",message:e.message})),!1}},[]),eP=(0,r.useCallback)(()=>{V({status:"idle",url:"",message:""})},[]),eO=(0,r.useCallback)(async(e,t)=>{try{return await d.current.api("/shared/context/".concat(encodeURIComponent(e)),{method:"PUT",body:{value:t}}),W({text:"shared context '".concat(e,"' set"),error:!1,busy:!1}),await ec(),!0}catch(e){if(e instanceof i)return!1;return W({text:e.message,error:!0,busy:!1}),!1}},[ec]);return(0,n.jsx)(c.Provider,{value:{section:u,setSection:ed,projects:m,agents:x,selectedProjectId:j,selectProject:eu,selectedRun:y,selectRun:eh,checkmark:b,checkmarkMissing:w,log:_,logOffset:S,pageLog:em,events:I,approvals:z,hosts:P,commands:M,schedules:D,shared:F,cmd:H,lastError:q,loading:J,refresh:ep,spawnAgent:ev,killAgent:ej,deleteAgent:eg,submitAnswer:ey,createProject:ef,updateProject:ek,deleteProject:ew,syncProject:eb,submitApproval:eN,createHost:e_,updateHost:eC,deleteHost:eT,createSchedule:eS,updateSchedule:eR,deleteSchedule:eI,pollCi:eA,setSharedKey:eO,claudeLogin:Y,startClaudeLogin:ez,submitClaudeCode:eE,resetClaudeLogin:eP},children:a})}function u(e){switch((null!=e?e:"").toLowerCase()){case"pass":case"done":case"completed":case"approved":case"success":return"success";case"fail":case"failed":case"blocked":case"rejected":case"error":case"crashed":return"danger";case"pending":case"queued":case"running":case"working":case"paused_for_input":return"warning";case"not_applicable":case"unknown":case"":return"neutral";default:return"info"}}let h={paused_for_input:"Needs input",not_applicable:"N/A"};function m(e){if(!e)return"—";let t=new Date(e);return Number.isNaN(t.getTime())?String(e):t.toLocaleString(void 0,{year:"numeric",month:"short",day:"numeric",hour:"numeric",minute:"2-digit"})}function p(e){return e?e.slice(0,7):"—"}function x(e){if(!e)return"—";let t=new Date(e).getTime();if(Number.isNaN(t))return"—";let s=Math.max(0,Math.floor((Date.now()-t)/1e3));if(s<60)return"".concat(s,"s");let a=Math.floor(s/60);if(a<60)return"".concat(a,"m");let n=Math.floor(a/60);if(n<24)return"".concat(n,"h");let r=Math.floor(n/24);if(r<30)return"".concat(r,"d");let l=Math.floor(r/30);return l<12?"".concat(l,"mo"):"".concat(Math.floor(l/12),"y")}function v(e){let{tone:t="neutral",pill:s=!1,dot:a=!1,children:r}=e;return(0,n.jsxs)("span",{className:"badge badge-".concat(t).concat(s?" pill":""),children:[a&&(0,n.jsx)("span",{className:"dot"}),r]})}function j(e){let{status:t}=e;return(0,n.jsx)(v,{tone:u(t),children:function(e){let t=(null!=e?e:"").trim();if(!t)return"—";let s=t.toLowerCase();return h[s]?h[s]:s.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase())}(t)})}function g(e){let{children:t,interactive:s=!1,onClick:a,className:r=""}=e;return(0,n.jsx)("div",{className:"card".concat(s?" interactive":""," ").concat(r).trim(),onClick:a,role:s?"button":void 0,tabIndex:s?0:void 0,children:t})}function y(e){let{variant:t="secondary",size:s="md",onClick:a,disabled:r,type:l="button",children:i}=e;return(0,n.jsx)("button",{type:l,className:"btn btn-".concat(t).concat("sm"===s?" btn-sm":""),onClick:a,disabled:r,children:i})}function f(e){let{label:t,children:s}=e;return(0,n.jsxs)("label",{className:"field",children:[t&&(0,n.jsx)("span",{className:"field-label",children:t}),s]})}function b(e){let{label:t,value:s,onChange:a,placeholder:r,type:l="text",disabled:i}=e;return(0,n.jsx)(f,{label:t,children:(0,n.jsx)("input",{className:"input",type:l,value:s,placeholder:r,disabled:i,onChange:e=>a(e.target.value)})})}function k(e){let{label:t,value:s,onChange:a,placeholder:r,rows:l=3}=e;return(0,n.jsx)(f,{label:t,children:(0,n.jsx)("textarea",{className:"textarea",value:s,rows:l,placeholder:r,onChange:e=>a(e.target.value)})})}function w(e){let{label:t,value:s,onChange:a,options:r}=e;return(0,n.jsx)(f,{label:t,children:(0,n.jsx)("select",{className:"select",value:s,onChange:e=>a(e.target.value),children:r.map(e=>(0,n.jsx)("option",{value:e.value,children:e.label},e.value))})})}function N(e){let{tabs:t,value:s,onChange:a}=e;return(0,n.jsx)("div",{className:"tabs",role:"tablist",children:t.map(e=>(0,n.jsx)("button",{role:"tab","aria-selected":s===e.value,className:"tab".concat(s===e.value?" active":""),onClick:()=>a(e.value),children:e.label},e.value))})}function _(e){let{value:t,label:s,sub:a,accent:r=!1}=e;return(0,n.jsxs)("div",{children:[(0,n.jsx)("div",{className:"stat-value".concat(r?" accent":""),children:t}),(0,n.jsx)("div",{className:"stat-label",children:s}),a&&(0,n.jsx)("div",{className:"stat-sub",children:a})]})}function C(e){let{tone:t="info",children:s}=e;return(0,n.jsx)("div",{className:"callout callout-".concat(t),children:s})}function S(e){let{on:t,onClick:s}=e;return(0,n.jsx)("button",{type:"button",className:"toggle".concat(t?" on":""),"aria-pressed":t,onClick:s,children:(0,n.jsx)("span",{className:"knob"})})}let R=[{value:"all",label:"All"},{value:"needs",label:"Needs Input"},{value:"working",label:"Working"},{value:"done",label:"Done"},{value:"crashed",label:"Crashed"}];function I(){let e=o(),[t,s]=(0,r.useState)("all"),a=(0,r.useMemo)(()=>[...e.agents.filter(e=>{var s;return s=e.status,"all"===t||("needs"===t?"paused_for_input"===s:"working"===t?"working"===s||"running"===s:"done"===t?"done"===s||"completed"===s:"crashed"!==t||"crashed"===s||"blocked"===s)})].sort((e,t)=>e.created_at"paused_for_input"===e.status).length,c=e.agents.filter(e=>"working"===e.status||"running"===e.status).length;return(0,n.jsxs)("div",{className:"runs",children:[(0,n.jsx)("div",{className:"runs-stats",children:(0,n.jsxs)("div",{className:"stat-row",children:[(0,n.jsx)("div",{className:"stat-cell",children:(0,n.jsx)(_,{value:e.agents.length,label:"Runs tracked"})}),(0,n.jsx)("div",{className:"stat-cell",children:(0,n.jsx)(_,{value:i,label:"Needs input",accent:!0})}),(0,n.jsx)("div",{className:"stat-cell",children:(0,n.jsx)(_,{value:c,label:"Working"})}),(0,n.jsx)("div",{className:"stat-cell",children:(0,n.jsx)(_,{value:e.projects.length,label:"Repositories"})})]})}),(0,n.jsxs)("div",{className:"split",children:[(0,n.jsxs)("div",{className:"split-list",children:[(0,n.jsxs)("div",{className:"split-list-head",children:[(0,n.jsx)("div",{className:"section-title",style:{fontSize:"var(--text-lg)"},children:"Runs"}),(0,n.jsx)(N,{tabs:R,value:t,onChange:s})]}),(0,n.jsxs)("div",{className:"split-list-scroll",children:[0===a.length&&(0,n.jsx)(C,{tone:"info",children:"No runs match this filter."}),a.map(t=>(0,n.jsx)(T,{agent:t,selected:(null==l?void 0:l.projectId)===t.project_id&&(null==l?void 0:l.name)===t.name,onSelect:()=>e.selectRun(t.project_id,t.name)},"".concat(t.project_id,"/").concat(t.name)))]})]}),(0,n.jsx)("div",{className:"split-detail",children:l?(0,n.jsx)(z,{}):(0,n.jsx)(A,{})})]})]})}function T(e){let{agent:t,selected:s,onSelect:a}=e;return(0,n.jsxs)("button",{className:"run-row".concat(s?" selected":""),onClick:a,children:[(0,n.jsxs)("div",{className:"run-row-top",children:[(0,n.jsx)("span",{className:"run-project",children:t.project_id}),(0,n.jsx)("span",{className:"faint mono",style:{fontSize:"var(--text-xs)"},children:x(t.created_at)})]}),(0,n.jsxs)("div",{className:"truncate muted",style:{fontSize:"var(--text-sm)"},children:[t.name,t.role?" \xb7 ".concat(t.role):""]}),(0,n.jsx)("div",{className:"hstack",style:{gap:8},children:(0,n.jsx)(j,{status:t.status})})]})}function A(){return(0,n.jsx)("div",{style:{padding:"60px 32px",color:"var(--text-muted)"},children:"Select a run to see its checkmark, log, and any open question."})}function z(){var e;let t=o(),s=t.selectedRun,a=t.agents.find(e=>e.project_id===s.projectId&&e.name===s.name),l=t.checkmark,[i,c]=(0,r.useState)(""),[d,h]=(0,r.useState)(!1),x=(null==a?void 0:a.status)==="paused_for_input",g=async e=>{if(!i.trim())return;h(!0);let s=await t.submitAnswer(i.trim(),e);h(!1),s&&c("")};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{style:{padding:"24px 28px",borderBottom:"1px solid var(--border-default)",display:"flex",flexDirection:"column",gap:10},children:[(0,n.jsxs)("div",{className:"hstack",children:[(0,n.jsx)("span",{style:{color:"var(--accent)",fontWeight:"var(--fw-bold)",fontSize:"var(--text-xl)"},children:s.projectId}),(0,n.jsx)("span",{className:"faint",children:"/"}),(0,n.jsx)("span",{style:{color:"var(--text-heading)",fontWeight:"var(--fw-semibold)",fontSize:"var(--text-lg)"},children:s.name}),(0,n.jsx)(j,{status:null==a?void 0:a.status}),(null==a?void 0:a.role)&&(0,n.jsx)(v,{tone:"info",children:a.role}),(0,n.jsx)("span",{className:"spacer"}),(0,n.jsx)(y,{size:"sm",variant:"secondary",onClick:()=>t.killAgent(s.projectId,s.name),children:"Kill"}),(0,n.jsx)(y,{size:"sm",variant:"danger",onClick:()=>t.deleteAgent(s.projectId,s.name),children:"Delete row"})]}),(0,n.jsxs)("div",{className:"mono faint",style:{fontSize:"var(--text-xs)"},children:[null!==(e=null==a?void 0:a.working_dir)&&void 0!==e?e:"—"," \xb7 created ",m(null==a?void 0:a.created_at)]})]}),(0,n.jsxs)("div",{style:{padding:"20px 28px",display:"flex",flexDirection:"column",gap:16},children:[(0,n.jsxs)("div",{children:[(0,n.jsx)("div",{className:"eyebrow",style:{marginBottom:10},children:"Checkmark"}),t.checkmarkMissing&&(0,n.jsx)(C,{tone:"info",children:"No checkpoint recorded yet."}),l&&!t.checkmarkMissing&&(0,n.jsxs)("dl",{className:"kv",children:[(0,n.jsx)("dt",{children:"Status"}),(0,n.jsx)("dd",{children:(0,n.jsx)(j,{status:l.status})}),(0,n.jsx)("dt",{children:"Where it stopped"}),(0,n.jsx)("dd",{children:l.where_it_stopped||"—"}),(0,n.jsx)("dt",{children:"Open question"}),(0,n.jsx)("dd",{children:l.open_question||"—"}),(0,n.jsx)("dt",{children:"Next steps"}),(0,n.jsx)("dd",{children:l.next_steps&&l.next_steps.length>0?(0,n.jsx)("ul",{children:l.next_steps.map((e,t)=>(0,n.jsx)("li",{children:e},t))}):"—"}),(0,n.jsx)("dt",{children:"Tests"}),(0,n.jsxs)("dd",{className:"hstack",children:[(0,n.jsx)(v,{tone:u(l.tests_status),children:l.tests_status}),(0,n.jsx)("span",{className:"faint mono",style:{fontSize:"var(--text-xs)"},children:l.tested_at?m(l.tested_at):""})]}),(0,n.jsx)("dt",{children:"Build"}),(0,n.jsxs)("dd",{className:"hstack",children:[(0,n.jsx)(v,{tone:u(l.build_status),children:l.build_status}),(0,n.jsx)("span",{className:"faint mono",style:{fontSize:"var(--text-xs)"},children:l.built_at?m(l.built_at):""})]}),(0,n.jsx)("dt",{children:"Checkpoint at"}),(0,n.jsx)("dd",{className:"faint",children:m(l.checkpoint_at)})]})]}),x&&(0,n.jsxs)("div",{style:{display:"flex",flexDirection:"column",gap:10},children:[(0,n.jsx)("div",{className:"eyebrow",children:"Answer this question"}),(0,n.jsx)(C,{tone:"danger",children:(null==l?void 0:l.open_question)||"(no question text on the checkmark)"}),(0,n.jsx)(k,{value:i,onChange:c,rows:3,placeholder:"Your answer…"}),(0,n.jsxs)("div",{className:"hstack",children:[(0,n.jsx)(y,{variant:"secondary",disabled:d||!i.trim(),onClick:()=>g(!1),children:"Answer"}),(0,n.jsx)(y,{variant:"primary",disabled:d||!i.trim(),onClick:()=>g(!0),children:"Answer & Resume"})]})]}),(null==a?void 0:a.session_id)&&(0,n.jsxs)("div",{style:{display:"flex",flexDirection:"column",gap:10},children:[(0,n.jsxs)("div",{className:"eyebrow",children:["Run events",a.worker_id?(0,n.jsxs)("span",{className:"faint mono",style:{fontSize:"var(--text-xs)",marginLeft:8},children:["on ",a.worker_id]}):null]}),0===t.events.length?(0,n.jsx)("div",{className:"empty",children:"No events yet."}):(0,n.jsx)("div",{style:{display:"flex",flexDirection:"column",gap:6,maxHeight:420,overflow:"auto",padding:"10px 12px",background:"var(--surface-2, rgba(0,0,0,0.25))",borderRadius:6},children:t.events.map(e=>(0,n.jsx)(E,{e:e},e.id))})]}),(0,n.jsxs)("div",{style:{display:"flex",flexDirection:"column",gap:10},children:[(0,n.jsx)("div",{className:"eyebrow",children:"Log \xb7 newest first"}),0===t.log.length?(0,n.jsx)("div",{className:"empty",children:"No log entries."}):(0,n.jsx)("div",{className:"table-wrap",children:(0,n.jsxs)("table",{className:"tbl",children:[(0,n.jsx)("thead",{children:(0,n.jsxs)("tr",{children:[(0,n.jsx)("th",{children:"When"}),(0,n.jsx)("th",{children:"Status"}),(0,n.jsx)("th",{children:"Summary"}),(0,n.jsx)("th",{children:"Q / A"}),(0,n.jsx)("th",{children:"Push"}),(0,n.jsx)("th",{children:"CI"})]})}),(0,n.jsx)("tbody",{children:t.log.map(e=>(0,n.jsxs)("tr",{children:[(0,n.jsx)("td",{className:"faint nowrap",children:m(e.created_at)}),(0,n.jsx)("td",{children:(0,n.jsx)(j,{status:e.status})}),(0,n.jsx)("td",{children:e.summary||"—"}),(0,n.jsxs)("td",{children:[e.question&&(0,n.jsxs)("div",{children:[(0,n.jsx)("strong",{children:"Q:"})," ",e.question]}),e.answer&&(0,n.jsxs)("div",{children:[(0,n.jsx)("strong",{children:"A:"})," ",e.answer]}),!e.question&&!e.answer&&"—"]}),(0,n.jsx)("td",{className:"mono",children:p(e.push_sha)}),(0,n.jsx)("td",{children:(0,n.jsx)(v,{tone:u(e.ci_status),children:e.ci_status})})]},e.id))})]})}),(0,n.jsxs)("div",{className:"pager",children:[(0,n.jsx)(y,{size:"sm",variant:"ghost",disabled:0===t.logOffset,onClick:()=>t.pageLog(-1),children:"‹ Newer"}),(0,n.jsxs)("span",{className:"faint mono",style:{fontSize:"var(--text-xs)"},children:["offset ",t.logOffset]}),(0,n.jsx)(y,{size:"sm",variant:"ghost",disabled:t.log.length<100,onClick:()=>t.pageLog(1),children:"Older ›"})]})]})]})]})}function E(e){var t,s,a,r;let{e:l}=e,i=null!==(t=l.payload)&&void 0!==t?t:{},c={fontSize:"var(--text-xs)"};if("system"===l.type)return(0,n.jsxs)("div",{className:"faint mono",style:c,children:["▸ session ",null!==(s=i.subtype)&&void 0!==s?s:"event",i.session_id?" \xb7 ".concat(String(i.session_id).slice(0,8)):"",Array.isArray(i.tools)?" \xb7 ".concat(i.tools.length," tools"):""]});if("assistant"===l.type){let e=null===(a=i.message)||void 0===a?void 0:a.content,t=Array.isArray(e)?e:[],s=t.filter(e=>(null==e?void 0:e.type)==="text"&&e.text).map(e=>e.text).join("\n"),r=t.filter(e=>(null==e?void 0:e.type)==="tool_use");return(0,n.jsxs)("div",{style:{display:"flex",flexDirection:"column",gap:4},children:[s&&(0,n.jsx)("div",{style:{fontSize:"var(--text-sm)",whiteSpace:"pre-wrap"},children:s}),r.length>0&&(0,n.jsx)("div",{className:"hstack",style:{gap:6,flexWrap:"wrap"},children:r.map((e,t)=>(0,n.jsxs)(v,{tone:"info",children:[e.name,e.input?": ".concat(function(e){let t="string"==typeof e?e:(null==e?void 0:e.command)?String(e.command):JSON.stringify(e);return t.length>80?"".concat(t.slice(0,77),"…"):t}(e.input)):""]},t))})]})}if("result"===l.type){let e=!!i.is_error;return(0,n.jsxs)("div",{className:"hstack",style:{gap:8,flexWrap:"wrap"},children:[(0,n.jsx)(v,{tone:e?"danger":"success",children:e?"run errored":"run finished"}),(0,n.jsxs)("span",{className:"faint mono",style:c,children:[null!=i.num_turns?"".concat(i.num_turns," turns"):"",null!=i.total_cost_usd?" \xb7 $".concat(Number(i.total_cost_usd).toFixed(4)):""]}),"string"==typeof i.result&&i.result&&(0,n.jsx)("span",{className:"muted",style:{...c,whiteSpace:"pre-wrap",width:"100%"},children:i.result})]})}return"worker"===l.type?(0,n.jsxs)(C,{tone:"danger",children:[null!==(r=i.notice)&&void 0!==r?r:"runner notice",i.stderr_tail?(0,n.jsx)("pre",{className:"mono",style:{...c,margin:"6px 0 0",whiteSpace:"pre-wrap"},children:i.stderr_tail}):null]}):"raw"===l.type?(0,n.jsx)("div",{className:"faint mono",style:{...c,whiteSpace:"pre-wrap"},children:"string"==typeof i.line?i.line.trimEnd():JSON.stringify(i)}):(0,n.jsxs)("div",{className:"faint mono",style:c,children:["▸ ",l.type]})}let P={mode:"server",git_server:"",repo:"",id:"",root_dir:"",git_remote:"",credential_ref:"",init_mise:!1};function O(){let e=o(),[t,s]=(0,r.useState)(P),[a,l]=(0,r.useState)(!1),i=(0,r.useMemo)(()=>{var t;let s=new Map;for(let a of e.agents)s.set(a.project_id,(null!==(t=s.get(a.project_id))&&void 0!==t?t:0)+1);return s},[e.agents]),c=(0,r.useMemo)(()=>[{value:"",label:e.hosts.length?"Pick a git server…":"No git servers configured"},...e.hosts.map(e=>({value:e.hostname,label:"".concat(e.hostname," (").concat(e.forge_type,")")}))],[e.hosts]),d=()=>{s(P),l(!1)},u=async()=>{(a?await e.updateProject(t.id,t):await e.createProject(t))&&d()},h=e=>{var t,a;s({...P,mode:"manual",id:e.id,root_dir:e.root_dir,git_remote:null!==(t=e.git_remote)&&void 0!==t?t:"",credential_ref:null!==(a=e.credential_ref)&&void 0!==a?a:""}),l(!0)},p=a?!!t.root_dir.trim():"server"===t.mode?!!t.git_server&&/^[\w.-]+\/[\w.-]+$/.test(t.repo.trim()):!!t.id.trim()&&!!t.root_dir.trim();return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"section-head",children:[(0,n.jsx)("div",{className:"section-title",children:"Repositories"}),(0,n.jsx)("div",{className:"section-desc",children:"Repos Handler manages. Each carries its own agents, history, and credentials."})]}),(0,n.jsxs)("div",{className:"section-body",children:[(0,n.jsxs)(g,{children:[(0,n.jsx)("div",{className:"card-head",style:{marginBottom:14},children:(0,n.jsx)("span",{className:"card-title",style:{fontSize:"var(--text-md)",color:"var(--text-heading)"},children:a?"Edit repository \xb7 ".concat(t.id):"Add a repository"})}),!a&&(0,n.jsx)("div",{style:{marginBottom:14},children:(0,n.jsx)(N,{tabs:[{value:"server",label:"From a git server"},{value:"manual",label:"Manual (existing checkout)"}],value:t.mode,onChange:e=>s({...t,mode:e})})}),a||"server"!==t.mode?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"form-grid",children:[(0,n.jsx)(b,{label:"ID / slug",value:t.id,onChange:e=>s({...t,id:e}),placeholder:"leeworks-api",disabled:a}),(0,n.jsx)(b,{label:"Root dir",value:t.root_dir,onChange:e=>s({...t,root_dir:e}),placeholder:"/var/lib/handler/projects/leeworks"}),(0,n.jsx)(b,{label:"Git remote",value:t.git_remote,onChange:e=>s({...t,git_remote:e}),placeholder:"git@github.com:user/repo.git (optional)"}),(0,n.jsx)(b,{label:"Credential ref",value:t.credential_ref,onChange:e=>s({...t,credential_ref:e}),placeholder:"env:VAR / file:/path / db:host:github.com"})]}),(0,n.jsx)("p",{className:"faint",style:{fontSize:"var(--text-xs)",margin:"10px 0 0"},children:"Optional override — projects on a configured git server use its stored token automatically. credential_ref is a pointer, never the token (env: / file: / db:host:)."})]}):(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"form-grid",children:[(0,n.jsx)(w,{label:"Git server",value:t.git_server,onChange:e=>s({...t,git_server:e}),options:c}),(0,n.jsx)(b,{label:"Repository (owner/name)",value:t.repo,onChange:e=>s({...t,repo:e}),placeholder:"me/coolproj"}),(0,n.jsx)(b,{label:"ID / slug (optional — defaults to the repo name)",value:t.id,onChange:e=>s({...t,id:e}),placeholder:"coolproj"})]}),(0,n.jsx)("p",{className:"faint",style:{fontSize:"var(--text-xs)",margin:"10px 0 0"},children:"The repo is always pulled: Handler derives the remote from the server (ssh when it has a deploy key, https via the stored token otherwise), clones it under PROJECTS_ROOT, and keeps it fresh before every run."})]}),!a&&(0,n.jsxs)("label",{className:"hstack",style:{gap:8,cursor:"pointer",marginTop:14},children:[(0,n.jsx)("input",{type:"checkbox",checked:t.init_mise,onChange:e=>s({...t,init_mise:e.target.checked})}),(0,n.jsxs)("span",{style:{fontSize:"var(--text-sm)"},children:["Initialize mise — after the clone, run an agent that writes a"," ",(0,n.jsx)("span",{className:"mono",children:".mise.toml"})," with a"," ",(0,n.jsx)("span",{className:"mono",children:"[tasks.test]"})," task for this repo’s stack, then commits and pushes it. Needed for repos that don’t define one yet."]})]}),!a&&t.init_mise&&"manual"===t.mode&&!t.git_remote.trim()&&(0,n.jsxs)("p",{className:"faint",style:{fontSize:"var(--text-xs)",margin:"6px 0 0"},children:["A git remote is required to push the new ",(0,n.jsx)("span",{className:"mono",children:".mise.toml"})," — add one above, or mise won’t be initialized."]}),(0,n.jsxs)("div",{className:"hstack mt14",children:[(0,n.jsx)(y,{variant:"primary",disabled:e.cmd.busy||!p,onClick:u,children:a?"Save changes":"server"===t.mode?"Add & pull":"Register"}),a&&(0,n.jsx)(y,{variant:"ghost",onClick:d,children:"Cancel"})]})]}),0===e.projects.length&&(0,n.jsx)("div",{className:"empty",children:"No repositories registered."}),e.projects.map(t=>{var s,a;return(0,n.jsxs)(g,{children:[(0,n.jsxs)("div",{className:"card-head",children:[(0,n.jsx)("span",{className:"card-title",children:t.id}),(0,n.jsxs)(v,{tone:"info",pill:!0,children:[null!==(s=i.get(t.id))&&void 0!==s?s:0," ",(null!==(a=i.get(t.id))&&void 0!==a?a:0)===1?"agent":"agents"]})]}),(0,n.jsxs)("div",{className:"mono faint",style:{fontSize:"var(--text-xs)",marginTop:4},children:[t.root_dir,t.git_remote?" \xb7 ".concat(t.git_remote):""]}),(0,n.jsxs)("div",{className:"hstack",style:{marginTop:12,justifyContent:"space-between"},children:[(0,n.jsxs)("span",{className:"faint mono",style:{fontSize:"var(--text-xs)"},children:["cred ",t.credential_ref||"server default"," \xb7 added ",m(t.created_at)]}),(0,n.jsxs)("div",{className:"hstack",children:[t.git_remote&&(0,n.jsx)(y,{size:"sm",variant:"secondary",onClick:()=>e.syncProject(t.id),children:"Pull now"}),(0,n.jsx)(y,{size:"sm",variant:"secondary",onClick:()=>h(t),children:"Edit"}),(0,n.jsx)(y,{size:"sm",variant:"danger",onClick:()=>e.deleteProject(t.id),children:"Remove"})]})]})]},t.id)})]})]})}let M=[{value:"",label:"Role — none"},{value:"junior",label:"junior"},{value:"senior",label:"senior"},{value:"deploy",label:"deploy"}],L=[{value:"worktree",label:"git worktree on branch"},{value:"subdir",label:"subdir under root"}],D={name:"",role:"",placement:"worktree",worktree:"",subdir:"",task:""};function U(){let e=o(),[t,s]=(0,r.useState)(D),a=(0,r.useMemo)(()=>e.projects.map(e=>({value:e.id,label:e.id})),[e.projects]),l=(0,r.useMemo)(()=>e.agents.filter(t=>t.project_id===e.selectedProjectId),[e.agents,e.selectedProjectId]),i=async()=>{await e.spawnAgent(t)&&s(D)};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"section-head",children:[(0,n.jsx)("div",{className:"section-title",children:"Agents"}),(0,n.jsx)("div",{className:"section-desc",children:"Spawn agents into a repository and manage running sessions."})]}),(0,n.jsx)("div",{className:"section-body",children:0===e.projects.length?(0,n.jsx)("div",{className:"empty",children:"Register a repository first."}):(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)("div",{className:"row",children:(0,n.jsx)("div",{style:{width:260},children:(0,n.jsx)(w,{label:"Repository",value:e.selectedProjectId,onChange:e.selectProject,options:a})})}),(0,n.jsxs)(g,{children:[(0,n.jsx)("div",{className:"card-head",style:{marginBottom:14},children:(0,n.jsx)("span",{className:"card-title",style:{fontSize:"var(--text-md)",color:"var(--text-heading)"},children:"Spawn an agent"})}),(0,n.jsxs)("div",{className:"form-grid",children:[(0,n.jsx)(b,{label:"Name",value:t.name,onChange:e=>s({...t,name:e}),placeholder:"junior"}),(0,n.jsx)(w,{label:"Role",value:t.role,onChange:e=>s({...t,role:e}),options:M}),(0,n.jsx)(w,{label:"Placement",value:t.placement,onChange:e=>s({...t,placement:e}),options:L}),"worktree"===t.placement?(0,n.jsx)(b,{label:"Branch",value:t.worktree,onChange:e=>s({...t,worktree:e}),placeholder:"feat/auth"}):(0,n.jsx)(b,{label:"Subdir",value:t.subdir,onChange:e=>s({...t,subdir:e}),placeholder:"api"})]}),(0,n.jsx)("div",{className:"mt14",children:(0,n.jsx)(k,{label:"Initial task",value:t.task,onChange:e=>s({...t,task:e}),rows:2,placeholder:"initial task / prompt (optional)"})}),(0,n.jsx)("div",{className:"hstack mt14",children:(0,n.jsx)(y,{variant:"primary",disabled:e.cmd.busy||!t.name.trim(),onClick:i,children:"Spawn"})})]}),0===l.length?(0,n.jsx)("div",{className:"empty",children:"No agents in this repository."}):(0,n.jsx)("div",{className:"table-wrap",children:(0,n.jsxs)("table",{className:"tbl",children:[(0,n.jsx)("thead",{children:(0,n.jsxs)("tr",{children:[(0,n.jsx)("th",{children:"Name"}),(0,n.jsx)("th",{children:"Role"}),(0,n.jsx)("th",{children:"Status"}),(0,n.jsx)("th",{children:"Working dir"}),(0,n.jsx)("th",{children:"Created"}),(0,n.jsx)("th",{})]})}),(0,n.jsx)("tbody",{children:l.map(t=>{var s;return(0,n.jsxs)(r.Fragment,{children:[(0,n.jsxs)("tr",{children:[(0,n.jsx)("td",{className:"mono",children:t.name}),(0,n.jsx)("td",{children:t.role?(0,n.jsx)(v,{tone:"info",children:t.role}):"—"}),(0,n.jsx)("td",{children:(0,n.jsx)(j,{status:t.status})}),(0,n.jsx)("td",{className:"mono faint",children:t.working_dir}),(0,n.jsx)("td",{className:"faint nowrap",children:m(t.created_at)}),(0,n.jsx)("td",{className:"nowrap",children:(0,n.jsxs)("div",{className:"hstack",children:[(0,n.jsx)(y,{size:"sm",variant:"ghost",onClick:()=>e.selectRun(t.project_id,t.name),children:"Open"}),(0,n.jsx)(y,{size:"sm",variant:"secondary",onClick:()=>e.killAgent(t.project_id,t.name),children:"Kill"}),(0,n.jsx)(y,{size:"sm",variant:"danger",onClick:()=>e.deleteAgent(t.project_id,t.name),children:"Delete"})]})})]}),("working"===t.status||"crashed"===t.status)&&(null===(s=t.last_output)||void 0===s?void 0:s.trim())&&(0,n.jsx)("tr",{children:(0,n.jsxs)("td",{colSpan:6,style:{paddingTop:0},children:[(0,n.jsxs)("div",{className:"faint",style:{fontSize:"var(--text-xs)",marginBottom:4},children:["crashed"===t.status?"last output before crash":"live output",t.output_at?" \xb7 ".concat(x(t.output_at)):""]}),(0,n.jsx)("pre",{className:"mono",style:{margin:0,padding:"8px 10px",background:"var(--surface-2, rgba(0,0,0,0.25))",borderRadius:6,fontSize:"var(--text-xs)",lineHeight:1.4,maxHeight:220,overflow:"auto",whiteSpace:"pre"},children:t.last_output})]})})]},t.id)})})]})})]})})]})}let F=[{value:"",label:"Role — none"},{value:"junior",label:"junior"},{value:"senior",label:"senior"},{value:"deploy",label:"deploy"}],B=[{value:"900",label:"every 15 minutes"},{value:"1800",label:"every 30 minutes"},{value:"3600",label:"every hour"},{value:"21600",label:"every 6 hours"},{value:"86400",label:"every day"},{value:"604800",label:"every week"}],H={name_prefix:"",task:"",interval:"3600",role:""};function W(){let e=o(),[t,s]=(0,r.useState)(H),a=(0,r.useMemo)(()=>e.projects.map(e=>({value:e.id,label:e.id})),[e.projects]),l=async()=>{await e.createSchedule(e.selectedProjectId,{name_prefix:t.name_prefix,task:t.task,interval_seconds:Number(t.interval),role:t.role})&&s(H)};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"section-head",children:[(0,n.jsx)("div",{className:"section-title",children:"Schedules"}),(0,n.jsx)("div",{className:"section-desc",children:"Spawn a fresh agent on an interval. Each run is stateless — keep continuity in a file the prompt reads and overwrites."})]}),(0,n.jsx)("div",{className:"section-body",children:0===e.projects.length?(0,n.jsx)("div",{className:"empty",children:"Register a repository first."}):(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)("div",{className:"row",children:(0,n.jsx)("div",{style:{width:260},children:(0,n.jsx)(w,{label:"Repository",value:e.selectedProjectId,onChange:e.selectProject,options:a})})}),(0,n.jsxs)(g,{children:[(0,n.jsx)("div",{className:"card-head",style:{marginBottom:14},children:(0,n.jsx)("span",{className:"card-title",style:{fontSize:"var(--text-md)",color:"var(--text-heading)"},children:"New schedule"})}),(0,n.jsxs)("div",{className:"form-grid",children:[(0,n.jsx)(b,{label:"Name prefix",value:t.name_prefix,onChange:e=>s({...t,name_prefix:e}),placeholder:"nightly"}),(0,n.jsx)(w,{label:"Interval",value:t.interval,onChange:e=>s({...t,interval:e}),options:B}),(0,n.jsx)(w,{label:"Role",value:t.role,onChange:e=>s({...t,role:e}),options:F})]}),(0,n.jsx)("div",{className:"mt14",children:(0,n.jsx)(k,{label:"Prompt (the task every run starts with)",value:t.task,onChange:e=>s({...t,task:e}),rows:3,placeholder:"Read @notes.md and continue from where it left off. Before finishing, overwrite @notes.md with the current state so the next run can pick up from there."})}),(0,n.jsxs)("p",{className:"faint",style:{fontSize:"var(--text-xs)",margin:"10px 0 0"},children:["Runs are named ",(0,n.jsxs)("span",{className:"mono",children:[t.name_prefix.trim()||"prefix","-YYYYMMDD-HHMMSS"]}),". The repo is pulled before every run; the first run fires on the worker's next pass."]}),(0,n.jsx)("div",{className:"hstack mt14",children:(0,n.jsx)(y,{variant:"primary",disabled:e.cmd.busy||!t.name_prefix.trim()||!t.task.trim(),onClick:l,children:"Create schedule"})})]}),0===e.schedules.length?(0,n.jsx)("div",{className:"empty",children:"No schedules yet."}):(0,n.jsx)("div",{className:"table-wrap",children:(0,n.jsxs)("table",{className:"tbl",children:[(0,n.jsx)("thead",{children:(0,n.jsxs)("tr",{children:[(0,n.jsx)("th",{children:"On"}),(0,n.jsx)("th",{children:"Name"}),(0,n.jsx)("th",{children:"Repository"}),(0,n.jsx)("th",{children:"Interval"}),(0,n.jsx)("th",{children:"Prompt"}),(0,n.jsx)("th",{children:"Next run"}),(0,n.jsx)("th",{children:"Last run"}),(0,n.jsx)("th",{})]})}),(0,n.jsx)("tbody",{children:e.schedules.map(t=>(0,n.jsxs)("tr",{children:[(0,n.jsx)("td",{children:(0,n.jsx)(S,{on:t.enabled,onClick:()=>e.updateSchedule(t.id,{enabled:!t.enabled})})}),(0,n.jsxs)("td",{className:"mono",children:[t.name_prefix,t.role?(0,n.jsxs)(n.Fragment,{children:[" ",(0,n.jsx)(v,{tone:"info",children:t.role})]}):null]}),(0,n.jsx)("td",{className:"mono faint",children:t.project_id}),(0,n.jsx)("td",{className:"nowrap",children:function(e){let t=B.find(t=>Number(t.value)===e);return t?t.label:e%3600==0?"every ".concat(e/3600,"h"):e%60==0?"every ".concat(e/60,"m"):"every ".concat(e,"s")}(t.interval_seconds)}),(0,n.jsx)("td",{className:"faint",style:{maxWidth:340},children:(0,n.jsx)("span",{className:"truncate",style:{display:"block"},title:t.task,children:t.task})}),(0,n.jsx)("td",{className:"faint nowrap",children:t.enabled?m(t.next_run_at):"paused"}),(0,n.jsx)("td",{className:"faint nowrap",children:m(t.last_run_at)}),(0,n.jsx)("td",{className:"nowrap",children:(0,n.jsx)(y,{size:"sm",variant:"danger",onClick:()=>e.deleteSchedule(t.id),children:"Delete"})})]},t.id))})]})})]})})]})}let q=[{value:"approved",label:"approve"},{value:"rejected",label:"reject"}],G={branch:"",status:"approved",agent_name:"",sha:"",note:""};function J(){let e=o(),[t,s]=(0,r.useState)(G),a=(0,r.useMemo)(()=>e.projects.map(e=>({value:e.id,label:e.id})),[e.projects]),l=async()=>{await e.submitApproval(t),s(G)};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"section-head",children:[(0,n.jsx)("div",{className:"section-title",children:"Approvals"}),(0,n.jsx)("div",{className:"section-desc",children:"A merge is denied unless a standing approval exists — made by a different agent, pinned to the reviewed commit."})]}),(0,n.jsx)("div",{className:"section-body",children:0===e.projects.length?(0,n.jsx)("div",{className:"empty",children:"Register a repository first."}):(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)("div",{className:"row",children:(0,n.jsx)("div",{style:{width:260},children:(0,n.jsx)(w,{label:"Repository",value:e.selectedProjectId,onChange:e.selectProject,options:a})})}),(0,n.jsxs)(g,{children:[(0,n.jsx)("div",{className:"card-head",style:{marginBottom:14},children:(0,n.jsx)("span",{className:"card-title",style:{fontSize:"var(--text-md)",color:"var(--text-heading)"},children:"Record a verdict"})}),(0,n.jsxs)("div",{className:"form-grid",children:[(0,n.jsx)(b,{label:"Branch",value:t.branch,onChange:e=>s({...t,branch:e}),placeholder:"feat/auth"}),(0,n.jsx)(w,{label:"Verdict",value:t.status,onChange:e=>s({...t,status:e}),options:q}),(0,n.jsx)(b,{label:"Agent",value:t.agent_name,onChange:e=>s({...t,agent_name:e}),placeholder:"reads its HEAD (optional)"}),(0,n.jsx)(b,{label:"SHA",value:t.sha,onChange:e=>s({...t,sha:e}),placeholder:"pins the approval (optional)"})]}),(0,n.jsx)("div",{className:"mt14",children:(0,n.jsx)(b,{label:"Note",value:t.note,onChange:e=>s({...t,note:e}),placeholder:"optional"})}),(0,n.jsx)("div",{className:"hstack mt14",children:(0,n.jsx)(y,{variant:"primary",disabled:e.cmd.busy||!t.branch.trim(),onClick:l,children:"Enqueue verdict"})})]}),0===e.approvals.length?(0,n.jsx)("div",{className:"empty",children:"No approvals recorded."}):(0,n.jsx)("div",{className:"table-wrap",children:(0,n.jsxs)("table",{className:"tbl",children:[(0,n.jsx)("thead",{children:(0,n.jsxs)("tr",{children:[(0,n.jsx)("th",{children:"When"}),(0,n.jsx)("th",{children:"Branch"}),(0,n.jsx)("th",{children:"Verdict"}),(0,n.jsx)("th",{children:"By"}),(0,n.jsx)("th",{children:"SHA"}),(0,n.jsx)("th",{children:"Note"})]})}),(0,n.jsx)("tbody",{children:e.approvals.map(e=>(0,n.jsxs)("tr",{children:[(0,n.jsx)("td",{className:"faint nowrap",children:m(e.created_at)}),(0,n.jsx)("td",{className:"mono",children:e.branch}),(0,n.jsx)("td",{children:(0,n.jsx)(j,{status:e.status})}),(0,n.jsx)("td",{children:e.approved_by_agent_id?"agent ".concat(e.approved_by_agent_id):e.actor||"—"}),(0,n.jsx)("td",{className:"mono",children:p(e.approved_sha)}),(0,n.jsx)("td",{children:e.note||"—"})]},e.id))})]})})]})})]})}let K=[{value:"github",label:"github"},{value:"gitlab",label:"gitlab"},{value:"gitea",label:"gitea"},{value:"forgejo",label:"forgejo"},{value:"bitbucket",label:"bitbucket"}],Y={hostname:"",forge_type:"github",token_env_var:"",base_url:"",token:"",generate_ssh_key:!0};function V(e){let{value:t}=e,[s,a]=(0,r.useState)(!1),l=async()=>{try{await navigator.clipboard.writeText(t),a(!0),setTimeout(()=>a(!1),1500)}catch(e){}};return(0,n.jsxs)("div",{style:{marginTop:10},children:[(0,n.jsxs)("div",{className:"hstack",style:{justifyContent:"space-between"},children:[(0,n.jsx)("span",{className:"eyebrow",children:"SSH public key — add it to the forge (deploy key)"}),(0,n.jsx)(y,{size:"sm",variant:"secondary",onClick:l,children:s?"Copied":"Copy"})]}),(0,n.jsx)("pre",{className:"mono",style:{fontSize:"var(--text-xs)",whiteSpace:"pre-wrap",wordBreak:"break-all",margin:"6px 0 0",padding:8,border:"1px solid var(--border-default)",borderRadius:6,userSelect:"all"},children:t})]})}function Q(){let e=o(),[t,s]=(0,r.useState)(Y),[a,l]=(0,r.useState)(!1),i=()=>{s(Y),l(!1)},c=async()=>{(a?await e.updateHost(t.hostname,t):await e.createHost(t))&&i()},d=e=>{var t,a;s({hostname:e.hostname,forge_type:e.forge_type,token_env_var:null!==(t=e.token_env_var)&&void 0!==t?t:"",base_url:null!==(a=e.base_url)&&void 0!==a?a:"",token:"",generate_ssh_key:!1}),l(!0)};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"section-head",children:[(0,n.jsx)("div",{className:"section-title",children:"Git Servers"}),(0,n.jsxs)("div",{className:"section-desc",children:["Each server carries its own credentials: a forge token (encrypted at rest, used by agents' ",(0,n.jsx)("span",{className:"mono",children:"forge"})," + git) and an SSH deploy key — paste the public key into the forge. New repositories are added by picking a server and typing owner/name."]})]}),(0,n.jsxs)("div",{className:"section-body",children:[(0,n.jsxs)(g,{children:[(0,n.jsx)("div",{className:"card-head",style:{marginBottom:14},children:(0,n.jsx)("span",{className:"card-title",style:{fontSize:"var(--text-md)",color:"var(--text-heading)"},children:a?"Edit server \xb7 ".concat(t.hostname):"Add a git server"})}),(0,n.jsxs)("div",{className:"form-grid",children:[(0,n.jsx)(b,{label:"Hostname",value:t.hostname,onChange:e=>s({...t,hostname:e}),placeholder:"github.com",disabled:a}),(0,n.jsx)(w,{label:"Type",value:t.forge_type,onChange:e=>s({...t,forge_type:e}),options:K}),(0,n.jsx)(b,{label:a?"Forge token (blank = keep current)":"Forge token",type:"password",value:t.token,onChange:e=>s({...t,token:e}),placeholder:"stored encrypted; used by forge + git"}),(0,n.jsx)(b,{label:"Base URL (optional)",value:t.base_url,onChange:e=>s({...t,base_url:e}),placeholder:"https://git.corp.internal:8443"}),(0,n.jsx)(b,{label:"Token env var override (optional)",value:t.token_env_var,onChange:e=>s({...t,token_env_var:e}),placeholder:"GITEA_TOKEN"}),(0,n.jsxs)("label",{className:"field",children:[(0,n.jsx)("span",{className:"field-label",children:"SSH deploy key"}),(0,n.jsxs)("label",{className:"hstack",style:{gap:8,cursor:"pointer"},children:[(0,n.jsx)("input",{type:"checkbox",checked:t.generate_ssh_key,onChange:e=>s({...t,generate_ssh_key:e.target.checked})}),(0,n.jsx)("span",{style:{fontSize:"var(--text-sm)"},children:a?"Regenerate keypair (replaces the current key)":"Generate a keypair"})]})]})]}),(0,n.jsxs)("div",{className:"hstack mt14",children:[(0,n.jsx)(y,{variant:"primary",disabled:e.cmd.busy||!t.hostname.trim(),onClick:c,children:a?"Save changes":"Add server"}),a&&(0,n.jsx)(y,{variant:"ghost",onClick:i,children:"Cancel"})]})]}),0===e.hosts.length&&(0,n.jsx)("div",{className:"empty",children:"No git servers registered (built-in host map still applies)."}),e.hosts.map(t=>(0,n.jsxs)(g,{children:[(0,n.jsxs)("div",{className:"card-head",children:[(0,n.jsx)("span",{className:"mono",style:{fontWeight:"var(--fw-bold)",fontSize:"var(--text-lg)",color:"var(--text-heading)"},children:t.hostname}),(0,n.jsxs)("div",{className:"hstack",children:[(0,n.jsx)(v,{tone:"info",children:t.forge_type}),(0,n.jsx)(v,{tone:t.has_token?"success":"neutral",children:t.has_token?"token stored":"no token"}),(0,n.jsx)(v,{tone:t.ssh_public_key?"success":"neutral",children:t.ssh_public_key?"ssh key":"no ssh key"}),(0,n.jsx)(y,{size:"sm",variant:"secondary",onClick:()=>d(t),children:"Edit"}),(0,n.jsx)(y,{size:"sm",variant:"danger",onClick:()=>e.deleteHost(t.hostname),children:"Remove"})]})]}),(0,n.jsxs)("div",{className:"mono faint",style:{fontSize:"var(--text-xs)",marginTop:8},children:["token env ",t.token_env_var||"—",t.base_url?" \xb7 ".concat(t.base_url):""]}),t.ssh_public_key&&(0,n.jsx)(V,{value:t.ssh_public_key})]},t.hostname))]})]})}function X(){let e=o();return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)("div",{className:"section-head",children:(0,n.jsxs)("div",{className:"hstack",style:{justifyContent:"space-between"},children:[(0,n.jsxs)("div",{children:[(0,n.jsx)("div",{className:"section-title",children:"Activity"}),(0,n.jsx)("div",{className:"section-desc",children:"Control commands the worker drains from the queue."})]}),(0,n.jsx)(y,{variant:"secondary",disabled:e.cmd.busy,onClick:()=>e.pollCi(),children:"Sweep CI now"})]})}),(0,n.jsx)("div",{className:"section-body",children:0===e.commands.length?(0,n.jsx)("div",{className:"empty",children:"No commands yet."}):(0,n.jsx)("div",{className:"table-wrap",children:(0,n.jsxs)("table",{className:"tbl",children:[(0,n.jsx)("thead",{children:(0,n.jsxs)("tr",{children:[(0,n.jsx)("th",{children:"When"}),(0,n.jsx)("th",{children:"Type"}),(0,n.jsx)("th",{children:"Repository"}),(0,n.jsx)("th",{children:"Agent"}),(0,n.jsx)("th",{children:"Status"}),(0,n.jsx)("th",{children:"Result / Error"})]})}),(0,n.jsx)("tbody",{children:e.commands.map(e=>(0,n.jsxs)("tr",{children:[(0,n.jsx)("td",{className:"faint nowrap",children:m(e.created_at)}),(0,n.jsx)("td",{className:"mono",children:e.type}),(0,n.jsx)("td",{className:"mono",children:e.project_id||"—"}),(0,n.jsx)("td",{className:"mono",children:e.agent_name||"—"}),(0,n.jsx)("td",{children:(0,n.jsx)(j,{status:e.status})}),(0,n.jsx)("td",{className:"mono faint",style:{fontSize:"var(--text-xs)"},children:e.error||(e.result?JSON.stringify(e.result):"—")})]},e.id))})]})})})]})}function $(){let e=o(),[t,s]=(0,r.useState)(""),[a,l]=(0,r.useState)(""),i=async()=>{t.trim()&&a.trim()&&await e.setSharedKey(t.trim(),a.trim())&&(s(""),l(""))};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"section-head",children:[(0,n.jsx)("div",{className:"section-title",children:"Shared"}),(0,n.jsx)("div",{className:"section-desc",children:"The cross-project global feed and shared facts."})]}),(0,n.jsxs)("div",{className:"section-body",children:[(0,n.jsxs)("div",{children:[(0,n.jsx)("div",{className:"eyebrow",style:{marginBottom:10},children:"Global feed"}),0===e.shared.log.length?(0,n.jsx)("div",{className:"empty",children:"No global log entries."}):(0,n.jsx)("div",{className:"table-wrap",children:(0,n.jsxs)("table",{className:"tbl",children:[(0,n.jsx)("thead",{children:(0,n.jsxs)("tr",{children:[(0,n.jsx)("th",{children:"When"}),(0,n.jsx)("th",{children:"Agent"}),(0,n.jsx)("th",{children:"Status"}),(0,n.jsx)("th",{children:"Summary"}),(0,n.jsx)("th",{children:"CI"})]})}),(0,n.jsx)("tbody",{children:e.shared.log.map(e=>(0,n.jsxs)("tr",{children:[(0,n.jsx)("td",{className:"faint nowrap",children:m(e.created_at)}),(0,n.jsx)("td",{className:"mono",children:e.agent_id}),(0,n.jsx)("td",{children:(0,n.jsx)(j,{status:e.status})}),(0,n.jsx)("td",{children:e.summary||"—"}),(0,n.jsx)("td",{children:(0,n.jsx)(j,{status:e.ci_status})})]},e.id))})]})})]}),(0,n.jsxs)(g,{children:[(0,n.jsx)("div",{className:"card-head",style:{marginBottom:14},children:(0,n.jsx)("span",{className:"card-title",style:{fontSize:"var(--text-md)",color:"var(--text-heading)"},children:"Set a shared key"})}),(0,n.jsxs)("div",{className:"form-grid",children:[(0,n.jsx)(b,{label:"Key",value:t,onChange:s,placeholder:"key"}),(0,n.jsx)(b,{label:"Value",value:a,onChange:l,placeholder:"value"})]}),(0,n.jsx)("p",{className:"faint",style:{fontSize:"var(--text-xs)",margin:"10px 0 0"},children:"Requires the shared-context write token (or admin/global if unset)."}),(0,n.jsx)("div",{className:"hstack mt14",children:(0,n.jsx)(y,{variant:"primary",disabled:!t.trim()||!a.trim(),onClick:i,children:"Set"})})]}),e.shared.context.length>0&&(0,n.jsx)("div",{className:"table-wrap",children:(0,n.jsxs)("table",{className:"tbl",children:[(0,n.jsx)("thead",{children:(0,n.jsxs)("tr",{children:[(0,n.jsx)("th",{children:"Key"}),(0,n.jsx)("th",{children:"Value"}),(0,n.jsx)("th",{children:"Updated"})]})}),(0,n.jsx)("tbody",{children:e.shared.context.map(e=>(0,n.jsxs)("tr",{children:[(0,n.jsx)("td",{className:"mono",children:e.key}),(0,n.jsx)("td",{children:e.value}),(0,n.jsx)("td",{className:"faint nowrap",children:m(e.updated_at)})]},e.key))})]})})]})]})}function Z(e){let t=window.screenX+Math.max(0,(window.outerWidth-520)/2),s=window.screenY+Math.max(0,(window.outerHeight-760)/2);return window.open(e,"claude-login","popup=yes,width=".concat(520,",height=").concat(760,",left=").concat(Math.round(t),",top=").concat(Math.round(s)))}function ee(){let e=o(),{status:t,url:s,message:a}=e.claudeLogin,[l,i]=(0,r.useState)(""),c=(0,r.useRef)(null),d="starting"===t||"submitting"===t,u="awaiting"===t||"submitting"===t;(0,r.useEffect)(()=>{if("awaiting"===t&&s&&c.current&&!c.current.closed)try{c.current.location.href=s}catch(e){}if("done"===t||"error"===t){var e;null===(e=c.current)||void 0===e||e.close(),c.current=null}},[t,s]);let h=()=>{c.current=Z("about:blank"),e.startClaudeLogin()},m=async()=>{await e.submitClaudeCode(l)&&i("")};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"section-head",children:[(0,n.jsx)("div",{className:"section-title",children:"Claude Login"}),(0,n.jsxs)("div",{className:"section-desc",children:["Log Claude Code in on the host so agents can run. This drives"," ",(0,n.jsx)("span",{className:"mono",children:"claude /login"})," in the control container and picks the Claude account with a subscription."]})]}),(0,n.jsxs)("div",{className:"section-body",style:{display:"flex",flexDirection:"column",gap:16},children:[a&&(0,n.jsx)(C,{tone:"error"===t?"danger":"done"===t?"success":"info",children:a}),"done"===t?(0,n.jsx)("div",{children:(0,n.jsx)(y,{variant:"secondary",onClick:e.resetClaudeLogin,children:"Log in again"})}):u?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(C,{tone:"info",children:"A Claude sign-in window should have opened. Authorize there, copy the code Claude shows you, and paste it below. If the window didn't open (popups blocked), use the button."}),(0,n.jsxs)("div",{className:"hstack",style:{gap:10,flexWrap:"wrap"},children:[(0,n.jsx)(y,{variant:"secondary",disabled:!s,onClick:()=>{s&&(c.current=Z(s))},children:"Open Claude sign-in window ↗"}),s&&(0,n.jsx)("a",{className:"btn btn-ghost",href:s,target:"_blank",rel:"noopener noreferrer",children:"Open in a new tab"}),(0,n.jsx)(y,{variant:"ghost",disabled:d,onClick:h,children:"Restart"})]}),(0,n.jsxs)("div",{className:"hstack",style:{gap:10,alignItems:"flex-end",flexWrap:"wrap"},children:[(0,n.jsx)("div",{style:{flex:"1 1 320px"},children:(0,n.jsx)(b,{label:"Authorization code",value:l,onChange:i,placeholder:"Paste the code from claude.com",disabled:"submitting"===t})}),(0,n.jsx)(y,{variant:"primary",disabled:"submitting"===t||!l.trim(),onClick:m,children:"submitting"===t?"Submitting…":"Finish login"})]})]}):(0,n.jsxs)("div",{className:"hstack",style:{gap:10},children:[(0,n.jsx)(y,{variant:"primary",disabled:d,onClick:h,children:"starting"===t?"Starting…":"Log in to Claude"}),"error"===t&&(0,n.jsx)(y,{variant:"ghost",disabled:d,onClick:h,children:"Retry"})]})]})]})}let et=[{key:"runs",label:"Runs",count:e=>e.agents.length,accent:e=>e.agents.some(e=>"paused_for_input"===e.status)},{key:"repositories",label:"Repositories",count:e=>e.projects.length},{key:"agents",label:"Agents",count:e=>e.agents.length},{key:"schedules",label:"Schedules",count:e=>e.schedules.length},{key:"approvals",label:"Approvals",count:e=>e.approvals.length},{key:"servers",label:"Git Servers",count:e=>e.hosts.length},{key:"activity",label:"Activity",count:e=>e.commands.length},{key:"shared",label:"Shared",count:e=>e.shared.context.length},{key:"login",label:"Claude Login",count:()=>0,accent:e=>"done"!==e.claudeLogin.status}];function es(e){let{onSignOut:t}=e,s=o();return(0,n.jsxs)("div",{className:"app",children:[(0,n.jsxs)("aside",{className:"sidebar",children:[(0,n.jsxs)("div",{className:"brand",children:[(0,n.jsx)("span",{className:"logo"}),"Claude Monitor"]}),et.map(e=>{var t,a;let r=e.count(s),l=null!==(a=null===(t=e.accent)||void 0===t?void 0:t.call(e,s))&&void 0!==a&&a;return(0,n.jsxs)("button",{className:"nav-item".concat(s.section===e.key?" active":""),onClick:()=>s.setSection(e.key),children:[(0,n.jsx)("span",{children:e.label}),(0,n.jsx)("span",{className:"count",style:l?{color:"var(--lw-warning-fg)"}:void 0,children:r||""})]},e.key)}),(0,n.jsx)("div",{className:"sidebar-spacer"}),(0,n.jsxs)("div",{className:"sidebar-foot",children:[(0,n.jsxs)("button",{className:"nav-item",onClick:s.refresh,title:"Refresh now",children:[(0,n.jsx)("span",{children:"Refresh"}),(0,n.jsx)("span",{className:"count",children:"↻"})]}),(0,n.jsx)("button",{className:"nav-item",onClick:t,title:"Sign out / change token",children:(0,n.jsx)("span",{children:"Sign out"})})]})]}),(0,n.jsxs)("main",{className:"main",children:[s.cmd.text&&(0,n.jsx)("p",{className:"banner ".concat(s.cmd.error?"err":"ok"),style:{marginTop:16},children:s.cmd.text}),s.lastError&&(0,n.jsx)("p",{className:"banner err",style:{marginTop:12},children:s.lastError}),"runs"===s.section?(0,n.jsx)(I,{}):(0,n.jsxs)("div",{className:"main-scroll",children:["repositories"===s.section&&(0,n.jsx)(O,{}),"agents"===s.section&&(0,n.jsx)(U,{}),"schedules"===s.section&&(0,n.jsx)(W,{}),"approvals"===s.section&&(0,n.jsx)(J,{}),"servers"===s.section&&(0,n.jsx)(Q,{}),"activity"===s.section&&(0,n.jsx)(X,{}),"shared"===s.section&&(0,n.jsx)($,{}),"login"===s.section&&(0,n.jsx)(ee,{})]})]})]})}function ea(e){let{error:t,onSubmit:s}=e,[a,l]=(0,r.useState)("");return(0,n.jsx)("div",{className:"gate",children:(0,n.jsxs)("form",{className:"gate-card",onSubmit:e=>{e.preventDefault();let t=a.trim();t&&s(t)},children:[(0,n.jsxs)("div",{className:"gate-brand",children:[(0,n.jsx)("span",{className:"logo",style:{width:26,height:26,borderRadius:7}}),"Claude Monitor"]}),(0,n.jsx)("p",{className:"muted",style:{fontSize:"var(--text-sm)",margin:0},children:"Paste your API token to continue. Management actions require the admin token; read-only views work with the plain auth token."}),(0,n.jsx)("input",{className:"input",type:"password",autoComplete:"current-password",placeholder:"API token",value:a,onChange:e=>l(e.target.value),autoFocus:!0}),t&&(0,n.jsx)("p",{className:"callout callout-danger",style:{margin:0},children:t}),(0,n.jsx)("button",{className:"btn btn-primary",type:"submit",children:"Continue"})]})})}let en="handler_token";function er(){let[e,t]=(0,r.useState)(null),[s,a]=(0,r.useState)("");(0,r.useEffect)(()=>{let e=window.localStorage.getItem(en);e&&t(e)},[]);let l=(0,r.useCallback)(e=>{window.localStorage.setItem(en,e),a(""),t(e)},[]),i=(0,r.useCallback)(()=>{window.localStorage.removeItem(en),t(null)},[]),c=(0,r.useCallback)(()=>{window.localStorage.removeItem(en),t(null),a("Invalid token — please try again.")},[]);return e?(0,n.jsx)(d,{token:e,onUnauthorized:c,children:(0,n.jsx)(es,{onSignOut:i})}):(0,n.jsx)(ea,{error:s,onSubmit:l})}},257:function(e,t,s){"use strict";var a,n;e.exports=(null==(a=s.g.process)?void 0:a.env)&&"object"==typeof(null==(n=s.g.process)?void 0:n.env)?s.g.process:s(4227)},4227:function(e){!function(){var t={229:function(e){var t,s,a,n=e.exports={};function r(){throw Error("setTimeout has not been defined")}function l(){throw Error("clearTimeout has not been defined")}function i(e){if(t===setTimeout)return setTimeout(e,0);if((t===r||!t)&&setTimeout)return t=setTimeout,setTimeout(e,0);try{return t(e,0)}catch(s){try{return t.call(null,e,0)}catch(s){return t.call(this,e,0)}}}!function(){try{t="function"==typeof setTimeout?setTimeout:r}catch(e){t=r}try{s="function"==typeof clearTimeout?clearTimeout:l}catch(e){s=l}}();var c=[],o=!1,d=-1;function u(){o&&a&&(o=!1,a.length?c=a.concat(c):d=-1,c.length&&h())}function h(){if(!o){var e=i(u);o=!0;for(var t=c.length;t;){for(a=c,c=[];++d1)for(var s=1;s(function(e,t){async function s(s,a){var n;let r=(null==a?void 0:a.body)!==void 0&&(null==a?void 0:a.body)!==null,c=await fetch(l+s,{method:null!==(n=null==a?void 0:a.method)&&void 0!==n?n:r?"POST":"GET",headers:{Authorization:"Bearer ".concat(e),...r?{"Content-Type":"application/json"}:{}},body:r?JSON.stringify(a.body):void 0});if(401===c.status)throw t(),new i;if(!c.ok){let e=c.statusText;try{let t=await c.json();t&&void 0!==t.detail&&(e="string"==typeof t.detail?t.detail:JSON.stringify(t.detail))}catch(e){}let t=Error(e);throw t.status=c.status,t}if(204===c.status)return;let o=await c.text();return o?JSON.parse(o):void 0}async function a(e,t){var a,n;let r=null!==(a=null==t?void 0:t.attempts)&&void 0!==a?a:60,l=null!==(n=null==t?void 0:t.intervalMs)&&void 0!==n?n:500;for(let t=0;tsetTimeout(e,l))}return null}return{api:s,trackCommand:a}})(t,s),[t,s]),d=(0,r.useRef)(o);d.current=o;let[u,h]=(0,r.useState)("runs"),[m,p]=(0,r.useState)([]),[x,v]=(0,r.useState)([]),[j,g]=(0,r.useState)(""),[y,f]=(0,r.useState)(null),[b,k]=(0,r.useState)(null),[w,N]=(0,r.useState)(!1),[_,C]=(0,r.useState)([]),[S,R]=(0,r.useState)(0),[I,T]=(0,r.useState)([]),A=(0,r.useRef)([]);A.current=I;let z=(0,r.useRef)(0),[E,P]=(0,r.useState)([]),[O,M]=(0,r.useState)([]),[L,D]=(0,r.useState)([]),[U,F]=(0,r.useState)([]),[B,H]=(0,r.useState)({log:[],context:[]}),[W,q]=(0,r.useState)({text:"",error:!1,busy:!1}),[G,J]=(0,r.useState)(""),[K,Y]=(0,r.useState)(!0),[V,Q]=(0,r.useState)({status:"idle",url:"",message:""}),X=(0,r.useRef)(u);X.current=u;let $=(0,r.useRef)(j);$.current=j;let Z=(0,r.useRef)(y);Z.current=y;let ee=(0,r.useRef)(S);ee.current=S;let et=e=>{e instanceof i||J(e.message)},es=(0,r.useCallback)(async()=>{try{let e=await d.current.api("/projects");p(e),J(""),g(t=>{var s,a;return t||(null!==(a=null===(s=e[0])||void 0===s?void 0:s.id)&&void 0!==a?a:"")})}catch(e){et(e)}},[]),ea=(0,r.useCallback)(async e=>{try{let t=await Promise.all(e.map(e=>d.current.api("/projects/".concat(encodeURIComponent(e.id),"/agents")).catch(()=>[])));v(t.flat())}catch(e){et(e)}},[]),en=(0,r.useCallback)(async(e,t)=>{let s="/projects/".concat(encodeURIComponent(e),"/agents/").concat(encodeURIComponent(t)),a=z.current;try{let e=await d.current.api("".concat(s,"/checkmark"));if(a!==z.current)return;k(e),N(!1)}catch(e){if(e instanceof i||a!==z.current)return;404===e.status?(k(null),N(!0)):et(e)}try{let e=await d.current.api("".concat(s,"/log?limit=").concat(100,"&offset=").concat(ee.current));if(a!==z.current)return;C(e)}catch(e){a!==z.current||et(e)}try{let e=A.current,t=e.length?e[e.length-1].id:0,n=await d.current.api("".concat(s,"/events?after_id=").concat(t,"&limit=500"));if(a!==z.current||0===n.length)return;T(e=>{let t=e.length?e[e.length-1].id:0,s=n.filter(e=>e.id>t);return s.length?[...e,...s]:e})}catch(e){a!==z.current||et(e)}},[]),er=(0,r.useCallback)(async e=>{if(!e){P([]);return}try{P(await d.current.api("/projects/".concat(encodeURIComponent(e),"/approvals")))}catch(e){et(e)}},[]),el=(0,r.useCallback)(async()=>{try{M(await d.current.api("/hosts"))}catch(e){et(e)}},[]),ei=(0,r.useCallback)(async()=>{try{D(await d.current.api("/commands?limit=50"))}catch(e){et(e)}},[]),ec=(0,r.useCallback)(async()=>{try{F(await d.current.api("/schedules"))}catch(e){et(e)}},[]),eo=(0,r.useCallback)(async()=>{try{let[e,t]=await Promise.all([d.current.api("/shared/log"),d.current.api("/shared/context")]);H({log:e,context:t})}catch(e){et(e)}},[]),ed=(0,r.useCallback)(async()=>{let e=await d.current.api("/projects").catch(e=>(et(e),null));e&&(p(e),g(t=>{var s,a;return t||(null!==(a=null===(s=e[0])||void 0===s?void 0:s.id)&&void 0!==a?a:"")}),await ea(e));let t=X.current,s=Z.current;s&&await en(s.projectId,s.name),"approvals"===t&&await er($.current),"servers"===t&&await el(),"activity"===t&&await ei(),"schedules"===t&&await ec(),"shared"===t&&await eo()},[ea,en,er,el,ei,ec,eo]);(0,r.useEffect)(()=>{let e=!0;(async()=>{Y(!0),await ed(),e&&Y(!1)})();let t=setInterval(()=>{document.hidden||ed()},5e3);return()=>{e=!1,clearInterval(t)}},[ed]);let eu=(0,r.useCallback)(e=>{h(e),q({text:"",error:!1,busy:!1}),"approvals"===e&&er($.current),"servers"===e&&el(),"activity"===e&&ei(),"schedules"===e&&ec(),"shared"===e&&eo()},[er,el,ei,ec,eo]),eh=(0,r.useCallback)(e=>{g(e),"approvals"===X.current&&er(e)},[er]),em=(0,r.useCallback)((e,t)=>{z.current+=1,f({projectId:e,name:t}),R(0),ee.current=0,k(null),N(!1),C([]),T([]),A.current=[],en(e,t)},[en]),ep=(0,r.useCallback)(e=>{let t=Math.max(0,S+100*e);if(t===S)return;R(t),ee.current=t;let s=Z.current;s&&en(s.projectId,s.name)},[S,en]),ex=(0,r.useCallback)(()=>{ed()},[ed]),ev=(0,r.useCallback)(async(e,t,s)=>{q({text:"".concat(s,": queued…"),error:!1,busy:!0});try{let a=await d.current.api(e,{method:"POST",body:t}),n=await d.current.trackCommand(a.id);if(!n)return q({text:"".concat(s,": still running (see Activity). Is the worker up?"),error:!1,busy:!1}),null;let r="done"===n.status,l=n.error||(n.result?JSON.stringify(n.result):"");return q({text:"".concat(s," ").concat(r?"done":"failed").concat(l?" — "+l:""),error:!r,busy:!1}),n}catch(e){if(e instanceof i)return null;return q({text:"".concat(s," failed: ").concat(e.message),error:!0,busy:!1}),null}},[]),ej=(0,r.useCallback)(async e=>{let t={name:e.name.trim(),role:e.role||null,task:e.task.trim()||null};"worktree"===e.placement&&e.worktree.trim()&&(t.worktree=e.worktree.trim()),"subdir"===e.placement&&e.subdir.trim()&&(t.subdir=e.subdir.trim());let s=encodeURIComponent($.current),a=await ev("/projects/".concat(s,"/agents/spawn"),t,"spawn ".concat(t.name));return await ea(m),(null==a?void 0:a.status)==="done"},[ev,ea,m]),eg=(0,r.useCallback)(async(e,t)=>{let s=encodeURIComponent(e);await ev("/projects/".concat(s,"/agents/").concat(encodeURIComponent(t),"/kill"),void 0,"kill ".concat(t)),await ea(m)},[ev,ea,m]),ey=(0,r.useCallback)(async(e,t)=>{let s=encodeURIComponent(e);try{var a;await d.current.api("/projects/".concat(s,"/agents/").concat(encodeURIComponent(t)),{method:"DELETE"}),q({text:"agent '".concat(t,"' row deleted"),error:!1,busy:!1}),(null===(a=Z.current)||void 0===a?void 0:a.name)===t&&f(null),await ea(m)}catch(e){if(e instanceof i)return;q({text:e.message,error:!0,busy:!1})}},[ea,m]),ef=(0,r.useCallback)(async(e,t)=>{let s=Z.current;if(!s)return!1;let a="/projects/".concat(encodeURIComponent(s.projectId),"/agents/").concat(encodeURIComponent(s.name));try{return await d.current.api("".concat(a,"/answer"),{method:"POST",body:{answer:e}}),t?await ev("".concat(a,"/resume"),{answer:e},"resume"):q({text:"Answer saved (agent still paused).",error:!1,busy:!1}),await ea(m),await en(s.projectId,s.name),!0}catch(e){if(e instanceof i)return!1;return q({text:e.message,error:!0,busy:!1}),!1}},[ev,ea,en,m]),eb=(0,r.useCallback)(async e=>{try{var t,s;let a="server"===e.mode?{git_server:e.git_server,repo:e.repo.trim(),id:e.id.trim()||null,credential_ref:e.credential_ref.trim()||null,init_mise:e.init_mise}:{id:e.id.trim(),root_dir:e.root_dir.trim(),git_remote:e.git_remote.trim()||null,credential_ref:e.credential_ref.trim()||null,init_mise:e.init_mise},n=await d.current.api("/projects",{method:"POST",body:a});if(await es(),null!=n.sync_command_id){q({text:"repository '".concat(n.id,"': cloning…"),error:!1,busy:!0});let e=await d.current.trackCommand(n.sync_command_id);e?"done"===e.status?q({text:"repository '".concat(n.id,"' registered and cloned"),error:!1,busy:!1}):q({text:"repository '".concat(n.id,"' registered but the clone failed — ").concat(null!==(t=e.error)&&void 0!==t?t:""),error:!0,busy:!1}):q({text:"repository '".concat(n.id,"' registered; clone still running (see Activity). Is the worker up?"),error:!1,busy:!1})}else q({text:"repository '".concat(n.id,"' registered"),error:!1,busy:!1});if(null!=n.mise_init_command_id){q({text:"repository '".concat(n.id,"': launching a mise-init agent to create .mise.toml…"),error:!1,busy:!0});let e=await d.current.trackCommand(n.mise_init_command_id);e?"done"===e.status?q({text:"repository '".concat(n.id,"' registered; a mise-init agent is now writing, committing, and pushing .mise.toml (watch it in Runs)."),error:!1,busy:!1}):q({text:"repository '".concat(n.id,"' registered but the mise-init agent failed to launch — ").concat(null!==(s=e.error)&&void 0!==s?s:""),error:!0,busy:!1}):q({text:"repository '".concat(n.id,"' registered; mise-init still starting (see Activity). Is the worker up?"),error:!1,busy:!1})}return!0}catch(e){if(e instanceof i)return!1;return q({text:e.message,error:!0,busy:!1}),!1}},[es]),ek=(0,r.useCallback)(async e=>{await ev("/projects/".concat(encodeURIComponent(e),"/sync"),void 0,"pull ".concat(e))},[ev]),ew=(0,r.useCallback)(async(e,t)=>{try{return await d.current.api("/projects/".concat(encodeURIComponent(e)),{method:"PATCH",body:{root_dir:t.root_dir.trim(),git_remote:t.git_remote.trim()||null,credential_ref:t.credential_ref.trim()||null}}),q({text:"repository '".concat(e,"' updated"),error:!1,busy:!1}),await es(),!0}catch(e){if(e instanceof i)return!1;return q({text:e.message,error:!0,busy:!1}),!1}},[es]),eN=(0,r.useCallback)(async e=>{try{await d.current.api("/projects/".concat(encodeURIComponent(e)),{method:"DELETE"}),q({text:"repository '".concat(e,"' removed"),error:!1,busy:!1}),g(t=>t===e?"":t),await es()}catch(e){if(e instanceof i)return;q({text:e.message,error:!0,busy:!1})}},[es]),e_=(0,r.useCallback)(async e=>{let t=encodeURIComponent($.current);await ev("/projects/".concat(t,"/approvals"),{branch:e.branch.trim(),status:e.status,agent_name:e.agent_name.trim()||null,sha:e.sha.trim()||null,note:e.note.trim()||null},"".concat(e.status," ").concat(e.branch)),await er($.current)},[ev,er]),eC=(0,r.useCallback)(async e=>{try{return await d.current.api("/hosts",{method:"POST",body:{hostname:e.hostname.trim(),forge_type:e.forge_type,token_env_var:e.token_env_var.trim()||null,base_url:e.base_url.trim()||null,token:e.token.trim()||null,generate_ssh_key:e.generate_ssh_key}}),q({text:"git server '".concat(e.hostname,"' added"),error:!1,busy:!1}),await el(),!0}catch(e){if(e instanceof i)return!1;return q({text:e.message,error:!0,busy:!1}),!1}},[el]),eS=(0,r.useCallback)(async(e,t)=>{try{let s={forge_type:t.forge_type,token_env_var:t.token_env_var.trim()||null,base_url:t.base_url.trim()||null};return t.token.trim()&&(s.token=t.token.trim()),t.generate_ssh_key&&(s.regenerate_ssh_key=!0),await d.current.api("/hosts/".concat(encodeURIComponent(e)),{method:"PATCH",body:s}),q({text:"git server '".concat(e,"' updated"),error:!1,busy:!1}),await el(),!0}catch(e){if(e instanceof i)return!1;return q({text:e.message,error:!0,busy:!1}),!1}},[el]),eR=(0,r.useCallback)(async(e,t)=>{try{return await d.current.api("/projects/".concat(encodeURIComponent(e),"/schedules"),{method:"POST",body:{name_prefix:t.name_prefix.trim(),task:t.task.trim(),interval_seconds:t.interval_seconds,role:t.role||null}}),q({text:"schedule '".concat(t.name_prefix,"' created — first run on the worker's next pass"),error:!1,busy:!1}),await ec(),!0}catch(e){if(e instanceof i)return!1;return q({text:e.message,error:!0,busy:!1}),!1}},[ec]),eI=(0,r.useCallback)(async(e,t)=>{try{return await d.current.api("/schedules/".concat(e),{method:"PATCH",body:t}),await ec(),!0}catch(e){if(e instanceof i)return!1;return q({text:e.message,error:!0,busy:!1}),!1}},[ec]),eT=(0,r.useCallback)(async e=>{try{await d.current.api("/schedules/".concat(e),{method:"DELETE"}),q({text:"schedule ".concat(e," removed"),error:!1,busy:!1}),await ec()}catch(e){if(e instanceof i)return;q({text:e.message,error:!0,busy:!1})}},[ec]),eA=(0,r.useCallback)(async e=>{try{await d.current.api("/hosts/".concat(encodeURIComponent(e)),{method:"DELETE"}),q({text:"git server '".concat(e,"' removed"),error:!1,busy:!1}),await el()}catch(e){if(e instanceof i)return;q({text:e.message,error:!0,busy:!1})}},[el]),ez=(0,r.useCallback)(async()=>{await ev("/poll-ci",void 0,"poll-ci (all projects)"),await ei()},[ev,ei]),eE=(0,r.useCallback)(async()=>{Q({status:"starting",url:"",message:"Opening `claude /login` in the control container and selecting the subscription account…"});try{let e=await d.current.api("/login/start",{method:"POST"}),t=await d.current.trackCommand(e.id,{attempts:180});if(!t){Q({status:"error",url:"",message:"Still starting (see Activity). Is the control worker running?"});return}if("done"!==t.status){Q({status:"error",url:"",message:t.error||"Failed to start login."});return}let s=t.result&&"string"==typeof t.result.url?t.result.url:"";if(!s){Q({status:"error",url:"",message:"No login URL was returned by claude."});return}Q({status:"awaiting",url:s,message:"Authorize in the window below (or open it in a new tab), then paste the code claude gives you."})}catch(e){if(e instanceof i)return;Q({status:"error",url:"",message:e.message})}},[]),eP=(0,r.useCallback)(async e=>{let t=e.trim();if(!t)return!1;Q(e=>({...e,status:"submitting",message:"Submitting the authorization code…"}));try{let e=await d.current.api("/login/submit",{method:"POST",body:{code:t}}),s=await d.current.trackCommand(e.id,{attempts:60});if(!s)return Q(e=>({...e,status:"awaiting",message:"Submit still running (see Activity). Is the control worker running?"})),!1;if("done"===s.status)return Q({status:"done",url:"",message:"Claude Code is now logged in on the host — new agents will use this account."}),!0;return Q(e=>({...e,status:"awaiting",message:s.error||"Login was not confirmed. Re-check the code, or restart the flow."})),!1}catch(e){if(e instanceof i)return!1;return Q(t=>({...t,status:"awaiting",message:e.message})),!1}},[]),eO=(0,r.useCallback)(()=>{Q({status:"idle",url:"",message:""})},[]),eM=(0,r.useCallback)(async(e,t)=>{try{return await d.current.api("/shared/context/".concat(encodeURIComponent(e)),{method:"PUT",body:{value:t}}),q({text:"shared context '".concat(e,"' set"),error:!1,busy:!1}),await eo(),!0}catch(e){if(e instanceof i)return!1;return q({text:e.message,error:!0,busy:!1}),!1}},[eo]);return(0,n.jsx)(c.Provider,{value:{section:u,setSection:eu,projects:m,agents:x,selectedProjectId:j,selectProject:eh,selectedRun:y,selectRun:em,checkmark:b,checkmarkMissing:w,log:_,logOffset:S,pageLog:ep,events:I,approvals:E,hosts:O,commands:L,schedules:U,shared:B,cmd:W,lastError:G,loading:K,refresh:ex,spawnAgent:ej,killAgent:eg,deleteAgent:ey,submitAnswer:ef,createProject:eb,updateProject:ew,deleteProject:eN,syncProject:ek,submitApproval:e_,createHost:eC,updateHost:eS,deleteHost:eA,createSchedule:eR,updateSchedule:eI,deleteSchedule:eT,pollCi:ez,setSharedKey:eM,claudeLogin:V,startClaudeLogin:eE,submitClaudeCode:eP,resetClaudeLogin:eO},children:a})}function u(e){switch((null!=e?e:"").toLowerCase()){case"pass":case"done":case"completed":case"approved":case"success":return"success";case"fail":case"failed":case"blocked":case"rejected":case"error":case"crashed":return"danger";case"pending":case"queued":case"running":case"working":case"paused_for_input":return"warning";case"not_applicable":case"unknown":case"":return"neutral";default:return"info"}}let h={paused_for_input:"Needs input",not_applicable:"N/A"};function m(e){if(!e)return"—";let t=new Date(e);return Number.isNaN(t.getTime())?String(e):t.toLocaleString(void 0,{year:"numeric",month:"short",day:"numeric",hour:"numeric",minute:"2-digit"})}function p(e){return e?e.slice(0,7):"—"}function x(e){if(!e)return"—";let t=new Date(e).getTime();if(Number.isNaN(t))return"—";let s=Math.max(0,Math.floor((Date.now()-t)/1e3));if(s<60)return"".concat(s,"s");let a=Math.floor(s/60);if(a<60)return"".concat(a,"m");let n=Math.floor(a/60);if(n<24)return"".concat(n,"h");let r=Math.floor(n/24);if(r<30)return"".concat(r,"d");let l=Math.floor(r/30);return l<12?"".concat(l,"mo"):"".concat(Math.floor(l/12),"y")}function v(e){let{tone:t="neutral",pill:s=!1,dot:a=!1,children:r}=e;return(0,n.jsxs)("span",{className:"badge badge-".concat(t).concat(s?" pill":""),children:[a&&(0,n.jsx)("span",{className:"dot"}),r]})}function j(e){let{status:t}=e;return(0,n.jsx)(v,{tone:u(t),children:function(e){let t=(null!=e?e:"").trim();if(!t)return"—";let s=t.toLowerCase();return h[s]?h[s]:s.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase())}(t)})}function g(e){let{children:t,interactive:s=!1,onClick:a,className:r=""}=e;return(0,n.jsx)("div",{className:"card".concat(s?" interactive":""," ").concat(r).trim(),onClick:a,role:s?"button":void 0,tabIndex:s?0:void 0,children:t})}function y(e){let{variant:t="secondary",size:s="md",onClick:a,disabled:r,type:l="button",children:i}=e;return(0,n.jsx)("button",{type:l,className:"btn btn-".concat(t).concat("sm"===s?" btn-sm":""),onClick:a,disabled:r,children:i})}function f(e){let{label:t,children:s}=e;return(0,n.jsxs)("label",{className:"field",children:[t&&(0,n.jsx)("span",{className:"field-label",children:t}),s]})}function b(e){let{label:t,value:s,onChange:a,placeholder:r,type:l="text",disabled:i}=e;return(0,n.jsx)(f,{label:t,children:(0,n.jsx)("input",{className:"input",type:l,value:s,placeholder:r,disabled:i,onChange:e=>a(e.target.value)})})}function k(e){let{label:t,value:s,onChange:a,placeholder:r,rows:l=3}=e;return(0,n.jsx)(f,{label:t,children:(0,n.jsx)("textarea",{className:"textarea",value:s,rows:l,placeholder:r,onChange:e=>a(e.target.value)})})}function w(e){let{label:t,value:s,onChange:a,options:r}=e;return(0,n.jsx)(f,{label:t,children:(0,n.jsx)("select",{className:"select",value:s,onChange:e=>a(e.target.value),children:r.map(e=>(0,n.jsx)("option",{value:e.value,children:e.label},e.value))})})}function N(e){let{tabs:t,value:s,onChange:a}=e;return(0,n.jsx)("div",{className:"tabs",role:"tablist",children:t.map(e=>(0,n.jsx)("button",{role:"tab","aria-selected":s===e.value,className:"tab".concat(s===e.value?" active":""),onClick:()=>a(e.value),children:e.label},e.value))})}function _(e){let{value:t,label:s,sub:a,accent:r=!1}=e;return(0,n.jsxs)("div",{children:[(0,n.jsx)("div",{className:"stat-value".concat(r?" accent":""),children:t}),(0,n.jsx)("div",{className:"stat-label",children:s}),a&&(0,n.jsx)("div",{className:"stat-sub",children:a})]})}function C(e){let{tone:t="info",children:s}=e;return(0,n.jsx)("div",{className:"callout callout-".concat(t),children:s})}function S(e){let{on:t,onClick:s}=e;return(0,n.jsx)("button",{type:"button",className:"toggle".concat(t?" on":""),"aria-pressed":t,onClick:s,children:(0,n.jsx)("span",{className:"knob"})})}let R=[{value:"all",label:"All"},{value:"needs",label:"Needs Input"},{value:"working",label:"Working"},{value:"done",label:"Done"},{value:"crashed",label:"Crashed"}];function I(){let e=o(),[t,s]=(0,r.useState)("all"),a=(0,r.useMemo)(()=>[...e.agents.filter(e=>{var s;return s=e.status,"all"===t||("needs"===t?"paused_for_input"===s:"working"===t?"working"===s||"running"===s:"done"===t?"done"===s||"completed"===s:"crashed"!==t||"crashed"===s||"blocked"===s)})].sort((e,t)=>e.created_at"paused_for_input"===e.status).length,c=e.agents.filter(e=>"working"===e.status||"running"===e.status).length;return(0,n.jsxs)("div",{className:"runs",children:[(0,n.jsx)("div",{className:"runs-stats",children:(0,n.jsxs)("div",{className:"stat-row",children:[(0,n.jsx)("div",{className:"stat-cell",children:(0,n.jsx)(_,{value:e.agents.length,label:"Runs tracked"})}),(0,n.jsx)("div",{className:"stat-cell",children:(0,n.jsx)(_,{value:i,label:"Needs input",accent:!0})}),(0,n.jsx)("div",{className:"stat-cell",children:(0,n.jsx)(_,{value:c,label:"Working"})}),(0,n.jsx)("div",{className:"stat-cell",children:(0,n.jsx)(_,{value:e.projects.length,label:"Repositories"})})]})}),(0,n.jsxs)("div",{className:"split",children:[(0,n.jsxs)("div",{className:"split-list",children:[(0,n.jsxs)("div",{className:"split-list-head",children:[(0,n.jsx)("div",{className:"section-title",style:{fontSize:"var(--text-lg)"},children:"Runs"}),(0,n.jsx)(N,{tabs:R,value:t,onChange:s})]}),(0,n.jsxs)("div",{className:"split-list-scroll",children:[0===a.length&&(0,n.jsx)(C,{tone:"info",children:"No runs match this filter."}),a.map(t=>(0,n.jsx)(T,{agent:t,selected:(null==l?void 0:l.projectId)===t.project_id&&(null==l?void 0:l.name)===t.name,onSelect:()=>e.selectRun(t.project_id,t.name)},"".concat(t.project_id,"/").concat(t.name)))]})]}),(0,n.jsx)("div",{className:"split-detail",children:l?(0,n.jsx)(z,{}):(0,n.jsx)(A,{})})]})]})}function T(e){let{agent:t,selected:s,onSelect:a}=e;return(0,n.jsxs)("button",{className:"run-row".concat(s?" selected":""),onClick:a,children:[(0,n.jsxs)("div",{className:"run-row-top",children:[(0,n.jsx)("span",{className:"run-project",children:t.project_id}),(0,n.jsx)("span",{className:"faint mono",style:{fontSize:"var(--text-xs)"},children:x(t.created_at)})]}),(0,n.jsxs)("div",{className:"truncate muted",style:{fontSize:"var(--text-sm)"},children:[t.name,t.role?" \xb7 ".concat(t.role):""]}),(0,n.jsx)("div",{className:"hstack",style:{gap:8},children:(0,n.jsx)(j,{status:t.status})})]})}function A(){return(0,n.jsx)("div",{style:{padding:"60px 32px",color:"var(--text-muted)"},children:"Select a run to see its checkmark, log, and any open question."})}function z(){var e;let t=o(),s=t.selectedRun,a=t.agents.find(e=>e.project_id===s.projectId&&e.name===s.name),l=t.checkmark,[i,c]=(0,r.useState)(""),[d,h]=(0,r.useState)(!1),x=(null==a?void 0:a.status)==="paused_for_input",g=async e=>{if(!i.trim())return;h(!0);let s=await t.submitAnswer(i.trim(),e);h(!1),s&&c("")};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{style:{padding:"24px 28px",borderBottom:"1px solid var(--border-default)",display:"flex",flexDirection:"column",gap:10},children:[(0,n.jsxs)("div",{className:"hstack",children:[(0,n.jsx)("span",{style:{color:"var(--accent)",fontWeight:"var(--fw-bold)",fontSize:"var(--text-xl)"},children:s.projectId}),(0,n.jsx)("span",{className:"faint",children:"/"}),(0,n.jsx)("span",{style:{color:"var(--text-heading)",fontWeight:"var(--fw-semibold)",fontSize:"var(--text-lg)"},children:s.name}),(0,n.jsx)(j,{status:null==a?void 0:a.status}),(null==a?void 0:a.role)&&(0,n.jsx)(v,{tone:"info",children:a.role}),(0,n.jsx)("span",{className:"spacer"}),(0,n.jsx)(y,{size:"sm",variant:"secondary",onClick:()=>t.killAgent(s.projectId,s.name),children:"Kill"}),(0,n.jsx)(y,{size:"sm",variant:"danger",onClick:()=>t.deleteAgent(s.projectId,s.name),children:"Delete row"})]}),(0,n.jsxs)("div",{className:"mono faint",style:{fontSize:"var(--text-xs)"},children:[null!==(e=null==a?void 0:a.working_dir)&&void 0!==e?e:"—"," \xb7 created ",m(null==a?void 0:a.created_at)]})]}),(0,n.jsxs)("div",{style:{padding:"20px 28px",display:"flex",flexDirection:"column",gap:16},children:[(0,n.jsxs)("div",{children:[(0,n.jsx)("div",{className:"eyebrow",style:{marginBottom:10},children:"Checkmark"}),t.checkmarkMissing&&(0,n.jsx)(C,{tone:"info",children:"No checkpoint recorded yet."}),l&&!t.checkmarkMissing&&(0,n.jsxs)("dl",{className:"kv",children:[(0,n.jsx)("dt",{children:"Status"}),(0,n.jsx)("dd",{children:(0,n.jsx)(j,{status:l.status})}),(0,n.jsx)("dt",{children:"Where it stopped"}),(0,n.jsx)("dd",{children:l.where_it_stopped||"—"}),(0,n.jsx)("dt",{children:"Open question"}),(0,n.jsx)("dd",{children:l.open_question||"—"}),(0,n.jsx)("dt",{children:"Next steps"}),(0,n.jsx)("dd",{children:l.next_steps&&l.next_steps.length>0?(0,n.jsx)("ul",{children:l.next_steps.map((e,t)=>(0,n.jsx)("li",{children:e},t))}):"—"}),(0,n.jsx)("dt",{children:"Tests"}),(0,n.jsxs)("dd",{className:"hstack",children:[(0,n.jsx)(v,{tone:u(l.tests_status),children:l.tests_status}),(0,n.jsx)("span",{className:"faint mono",style:{fontSize:"var(--text-xs)"},children:l.tested_at?m(l.tested_at):""})]}),(0,n.jsx)("dt",{children:"Build"}),(0,n.jsxs)("dd",{className:"hstack",children:[(0,n.jsx)(v,{tone:u(l.build_status),children:l.build_status}),(0,n.jsx)("span",{className:"faint mono",style:{fontSize:"var(--text-xs)"},children:l.built_at?m(l.built_at):""})]}),(0,n.jsx)("dt",{children:"Checkpoint at"}),(0,n.jsx)("dd",{className:"faint",children:m(l.checkpoint_at)})]})]}),x&&(0,n.jsxs)("div",{style:{display:"flex",flexDirection:"column",gap:10},children:[(0,n.jsx)("div",{className:"eyebrow",children:"Answer this question"}),(0,n.jsx)(C,{tone:"danger",children:(null==l?void 0:l.open_question)||"(no question text on the checkmark)"}),(0,n.jsx)(k,{value:i,onChange:c,rows:3,placeholder:"Your answer…"}),(0,n.jsxs)("div",{className:"hstack",children:[(0,n.jsx)(y,{variant:"secondary",disabled:d||!i.trim(),onClick:()=>g(!1),children:"Answer"}),(0,n.jsx)(y,{variant:"primary",disabled:d||!i.trim(),onClick:()=>g(!0),children:"Answer & Resume"})]})]}),(null==a?void 0:a.session_id)&&(0,n.jsxs)("div",{style:{display:"flex",flexDirection:"column",gap:10},children:[(0,n.jsxs)("div",{className:"eyebrow",children:["Run events",a.worker_id?(0,n.jsxs)("span",{className:"faint mono",style:{fontSize:"var(--text-xs)",marginLeft:8},children:["on ",a.worker_id]}):null]}),0===t.events.length?(0,n.jsx)("div",{className:"empty",children:"No events yet."}):(0,n.jsx)("div",{style:{display:"flex",flexDirection:"column",gap:6,maxHeight:420,overflow:"auto",padding:"10px 12px",background:"var(--surface-2, rgba(0,0,0,0.25))",borderRadius:6},children:t.events.map(e=>(0,n.jsx)(E,{e:e},e.id))})]}),(0,n.jsxs)("div",{style:{display:"flex",flexDirection:"column",gap:10},children:[(0,n.jsx)("div",{className:"eyebrow",children:"Log \xb7 newest first"}),0===t.log.length?(0,n.jsx)("div",{className:"empty",children:"No log entries."}):(0,n.jsx)("div",{className:"table-wrap",children:(0,n.jsxs)("table",{className:"tbl",children:[(0,n.jsx)("thead",{children:(0,n.jsxs)("tr",{children:[(0,n.jsx)("th",{children:"When"}),(0,n.jsx)("th",{children:"Status"}),(0,n.jsx)("th",{children:"Summary"}),(0,n.jsx)("th",{children:"Q / A"}),(0,n.jsx)("th",{children:"Push"}),(0,n.jsx)("th",{children:"CI"})]})}),(0,n.jsx)("tbody",{children:t.log.map(e=>(0,n.jsxs)("tr",{children:[(0,n.jsx)("td",{className:"faint nowrap",children:m(e.created_at)}),(0,n.jsx)("td",{children:(0,n.jsx)(j,{status:e.status})}),(0,n.jsx)("td",{children:e.summary||"—"}),(0,n.jsxs)("td",{children:[e.question&&(0,n.jsxs)("div",{children:[(0,n.jsx)("strong",{children:"Q:"})," ",e.question]}),e.answer&&(0,n.jsxs)("div",{children:[(0,n.jsx)("strong",{children:"A:"})," ",e.answer]}),!e.question&&!e.answer&&"—"]}),(0,n.jsx)("td",{className:"mono",children:p(e.push_sha)}),(0,n.jsx)("td",{children:(0,n.jsx)(v,{tone:u(e.ci_status),children:e.ci_status})})]},e.id))})]})}),(0,n.jsxs)("div",{className:"pager",children:[(0,n.jsx)(y,{size:"sm",variant:"ghost",disabled:0===t.logOffset,onClick:()=>t.pageLog(-1),children:"‹ Newer"}),(0,n.jsxs)("span",{className:"faint mono",style:{fontSize:"var(--text-xs)"},children:["offset ",t.logOffset]}),(0,n.jsx)(y,{size:"sm",variant:"ghost",disabled:t.log.length<100,onClick:()=>t.pageLog(1),children:"Older ›"})]})]})]})]})}function E(e){var t,s,a,r;let{e:l}=e,i=null!==(t=l.payload)&&void 0!==t?t:{},c={fontSize:"var(--text-xs)"};if("system"===l.type)return(0,n.jsxs)("div",{className:"faint mono",style:c,children:["▸ session ",null!==(s=i.subtype)&&void 0!==s?s:"event",i.session_id?" \xb7 ".concat(String(i.session_id).slice(0,8)):"",Array.isArray(i.tools)?" \xb7 ".concat(i.tools.length," tools"):""]});if("assistant"===l.type){let e=null===(a=i.message)||void 0===a?void 0:a.content,t=Array.isArray(e)?e:[],s=t.filter(e=>(null==e?void 0:e.type)==="text"&&e.text).map(e=>e.text).join("\n"),r=t.filter(e=>(null==e?void 0:e.type)==="tool_use");return(0,n.jsxs)("div",{style:{display:"flex",flexDirection:"column",gap:4},children:[s&&(0,n.jsx)("div",{style:{fontSize:"var(--text-sm)",whiteSpace:"pre-wrap"},children:s}),r.length>0&&(0,n.jsx)("div",{className:"hstack",style:{gap:6,flexWrap:"wrap"},children:r.map((e,t)=>(0,n.jsxs)(v,{tone:"info",children:[e.name,e.input?": ".concat(function(e){let t="string"==typeof e?e:(null==e?void 0:e.command)?String(e.command):JSON.stringify(e);return t.length>80?"".concat(t.slice(0,77),"…"):t}(e.input)):""]},t))})]})}if("result"===l.type){let e=!!i.is_error;return(0,n.jsxs)("div",{className:"hstack",style:{gap:8,flexWrap:"wrap"},children:[(0,n.jsx)(v,{tone:e?"danger":"success",children:e?"run errored":"run finished"}),(0,n.jsxs)("span",{className:"faint mono",style:c,children:[null!=i.num_turns?"".concat(i.num_turns," turns"):"",null!=i.total_cost_usd?" \xb7 $".concat(Number(i.total_cost_usd).toFixed(4)):""]}),"string"==typeof i.result&&i.result&&(0,n.jsx)("span",{className:"muted",style:{...c,whiteSpace:"pre-wrap",width:"100%"},children:i.result})]})}return"worker"===l.type?(0,n.jsxs)(C,{tone:"danger",children:[null!==(r=i.notice)&&void 0!==r?r:"runner notice",i.stderr_tail?(0,n.jsx)("pre",{className:"mono",style:{...c,margin:"6px 0 0",whiteSpace:"pre-wrap"},children:i.stderr_tail}):null]}):"raw"===l.type?(0,n.jsx)("div",{className:"faint mono",style:{...c,whiteSpace:"pre-wrap"},children:"string"==typeof i.line?i.line.trimEnd():JSON.stringify(i)}):(0,n.jsxs)("div",{className:"faint mono",style:c,children:["▸ ",l.type]})}let P={mode:"server",git_server:"",repo:"",id:"",root_dir:"",git_remote:"",credential_ref:"",init_mise:!1};function O(){let e=o(),[t,s]=(0,r.useState)(P),[a,l]=(0,r.useState)(!1),i=(0,r.useMemo)(()=>{var t;let s=new Map;for(let a of e.agents)s.set(a.project_id,(null!==(t=s.get(a.project_id))&&void 0!==t?t:0)+1);return s},[e.agents]),c=(0,r.useMemo)(()=>[{value:"",label:e.hosts.length?"Pick a git server…":"No git servers configured"},...e.hosts.map(e=>({value:e.hostname,label:"".concat(e.hostname," (").concat(e.forge_type,")")}))],[e.hosts]),d=()=>{s(P),l(!1)},u=async()=>{(a?await e.updateProject(t.id,t):await e.createProject(t))&&d()},h=e=>{var t,a;s({...P,mode:"manual",id:e.id,root_dir:e.root_dir,git_remote:null!==(t=e.git_remote)&&void 0!==t?t:"",credential_ref:null!==(a=e.credential_ref)&&void 0!==a?a:""}),l(!0)},p=a?!!t.root_dir.trim():"server"===t.mode?!!t.git_server&&/^[\w.-]+\/[\w.-]+$/.test(t.repo.trim()):!!t.id.trim()&&!!t.root_dir.trim();return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"section-head",children:[(0,n.jsx)("div",{className:"section-title",children:"Repositories"}),(0,n.jsx)("div",{className:"section-desc",children:"Repos Handler manages. Each carries its own agents, history, and credentials."})]}),(0,n.jsxs)("div",{className:"section-body",children:[(0,n.jsxs)(g,{children:[(0,n.jsx)("div",{className:"card-head",style:{marginBottom:14},children:(0,n.jsx)("span",{className:"card-title",style:{fontSize:"var(--text-md)",color:"var(--text-heading)"},children:a?"Edit repository \xb7 ".concat(t.id):"Add a repository"})}),!a&&(0,n.jsx)("div",{style:{marginBottom:14},children:(0,n.jsx)(N,{tabs:[{value:"server",label:"From a git server"},{value:"manual",label:"Manual (existing checkout)"}],value:t.mode,onChange:e=>s({...t,mode:e})})}),a||"server"!==t.mode?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"form-grid",children:[(0,n.jsx)(b,{label:"ID / slug",value:t.id,onChange:e=>s({...t,id:e}),placeholder:"leeworks-api",disabled:a}),(0,n.jsx)(b,{label:"Root dir",value:t.root_dir,onChange:e=>s({...t,root_dir:e}),placeholder:"/var/lib/handler/projects/leeworks"}),(0,n.jsx)(b,{label:"Git remote",value:t.git_remote,onChange:e=>s({...t,git_remote:e}),placeholder:"git@github.com:user/repo.git (optional)"}),(0,n.jsx)(b,{label:"Credential ref",value:t.credential_ref,onChange:e=>s({...t,credential_ref:e}),placeholder:"env:VAR / file:/path / db:host:github.com"})]}),(0,n.jsx)("p",{className:"faint",style:{fontSize:"var(--text-xs)",margin:"10px 0 0"},children:"Optional override — projects on a configured git server use its stored token automatically. credential_ref is a pointer, never the token (env: / file: / db:host:)."})]}):(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"form-grid",children:[(0,n.jsx)(w,{label:"Git server",value:t.git_server,onChange:e=>s({...t,git_server:e}),options:c}),(0,n.jsx)(b,{label:"Repository (owner/name)",value:t.repo,onChange:e=>s({...t,repo:e}),placeholder:"me/coolproj"}),(0,n.jsx)(b,{label:"ID / slug (optional — defaults to the repo name)",value:t.id,onChange:e=>s({...t,id:e}),placeholder:"coolproj"})]}),(0,n.jsx)("p",{className:"faint",style:{fontSize:"var(--text-xs)",margin:"10px 0 0"},children:"The repo is always pulled: Handler derives the remote from the server (ssh when it has a deploy key, https via the stored token otherwise), clones it under PROJECTS_ROOT, and keeps it fresh before every run."})]}),!a&&(0,n.jsxs)("label",{className:"hstack",style:{gap:8,cursor:"pointer",marginTop:14},children:[(0,n.jsx)("input",{type:"checkbox",checked:t.init_mise,onChange:e=>s({...t,init_mise:e.target.checked})}),(0,n.jsxs)("span",{style:{fontSize:"var(--text-sm)"},children:["Initialize mise — after the clone, run an agent that writes a"," ",(0,n.jsx)("span",{className:"mono",children:".mise.toml"})," with a"," ",(0,n.jsx)("span",{className:"mono",children:"[tasks.test]"})," task for this repo’s stack, then commits and pushes it. Needed for repos that don’t define one yet."]})]}),!a&&t.init_mise&&"manual"===t.mode&&!t.git_remote.trim()&&(0,n.jsxs)("p",{className:"faint",style:{fontSize:"var(--text-xs)",margin:"6px 0 0"},children:["A git remote is required to push the new ",(0,n.jsx)("span",{className:"mono",children:".mise.toml"})," — add one above, or mise won’t be initialized."]}),(0,n.jsxs)("div",{className:"hstack mt14",children:[(0,n.jsx)(y,{variant:"primary",disabled:e.cmd.busy||!p,onClick:u,children:a?"Save changes":"server"===t.mode?"Add & pull":"Register"}),a&&(0,n.jsx)(y,{variant:"ghost",onClick:d,children:"Cancel"})]})]}),0===e.projects.length&&(0,n.jsx)("div",{className:"empty",children:"No repositories registered."}),e.projects.map(t=>{var s,a;return(0,n.jsxs)(g,{children:[(0,n.jsxs)("div",{className:"card-head",children:[(0,n.jsx)("span",{className:"card-title",children:t.id}),(0,n.jsxs)(v,{tone:"info",pill:!0,children:[null!==(s=i.get(t.id))&&void 0!==s?s:0," ",(null!==(a=i.get(t.id))&&void 0!==a?a:0)===1?"agent":"agents"]})]}),(0,n.jsxs)("div",{className:"mono faint",style:{fontSize:"var(--text-xs)",marginTop:4},children:[t.root_dir,t.git_remote?" \xb7 ".concat(t.git_remote):""]}),(0,n.jsxs)("div",{className:"hstack",style:{marginTop:12,justifyContent:"space-between"},children:[(0,n.jsxs)("span",{className:"faint mono",style:{fontSize:"var(--text-xs)"},children:["cred ",t.credential_ref||"server default"," \xb7 added ",m(t.created_at)]}),(0,n.jsxs)("div",{className:"hstack",children:[t.git_remote&&(0,n.jsx)(y,{size:"sm",variant:"secondary",onClick:()=>e.syncProject(t.id),children:"Pull now"}),(0,n.jsx)(y,{size:"sm",variant:"secondary",onClick:()=>h(t),children:"Edit"}),(0,n.jsx)(y,{size:"sm",variant:"danger",onClick:()=>e.deleteProject(t.id),children:"Remove"})]})]})]},t.id)})]})]})}let M=[{value:"",label:"Role — none"},{value:"junior",label:"junior"},{value:"senior",label:"senior"},{value:"deploy",label:"deploy"}],L=[{value:"worktree",label:"git worktree on branch"},{value:"subdir",label:"subdir under root"}],D={name:"",role:"",placement:"worktree",worktree:"",subdir:"",task:""};function U(){let e=o(),[t,s]=(0,r.useState)(D),a=(0,r.useMemo)(()=>e.projects.map(e=>({value:e.id,label:e.id})),[e.projects]),l=(0,r.useMemo)(()=>e.agents.filter(t=>t.project_id===e.selectedProjectId),[e.agents,e.selectedProjectId]),i=async()=>{await e.spawnAgent(t)&&s(D)};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"section-head",children:[(0,n.jsx)("div",{className:"section-title",children:"Agents"}),(0,n.jsx)("div",{className:"section-desc",children:"Spawn agents into a repository and manage running sessions."})]}),(0,n.jsx)("div",{className:"section-body",children:0===e.projects.length?(0,n.jsx)("div",{className:"empty",children:"Register a repository first."}):(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)("div",{className:"row",children:(0,n.jsx)("div",{style:{width:260},children:(0,n.jsx)(w,{label:"Repository",value:e.selectedProjectId,onChange:e.selectProject,options:a})})}),(0,n.jsxs)(g,{children:[(0,n.jsx)("div",{className:"card-head",style:{marginBottom:14},children:(0,n.jsx)("span",{className:"card-title",style:{fontSize:"var(--text-md)",color:"var(--text-heading)"},children:"Spawn an agent"})}),(0,n.jsxs)("div",{className:"form-grid",children:[(0,n.jsx)(b,{label:"Name",value:t.name,onChange:e=>s({...t,name:e}),placeholder:"junior"}),(0,n.jsx)(w,{label:"Role",value:t.role,onChange:e=>s({...t,role:e}),options:M}),(0,n.jsx)(w,{label:"Placement",value:t.placement,onChange:e=>s({...t,placement:e}),options:L}),"worktree"===t.placement?(0,n.jsx)(b,{label:"Branch",value:t.worktree,onChange:e=>s({...t,worktree:e}),placeholder:"feat/auth"}):(0,n.jsx)(b,{label:"Subdir",value:t.subdir,onChange:e=>s({...t,subdir:e}),placeholder:"api"})]}),(0,n.jsx)("div",{className:"mt14",children:(0,n.jsx)(k,{label:"Initial task",value:t.task,onChange:e=>s({...t,task:e}),rows:2,placeholder:"initial task / prompt (optional)"})}),(0,n.jsx)("div",{className:"hstack mt14",children:(0,n.jsx)(y,{variant:"primary",disabled:e.cmd.busy||!t.name.trim(),onClick:i,children:"Spawn"})})]}),0===l.length?(0,n.jsx)("div",{className:"empty",children:"No agents in this repository."}):(0,n.jsx)("div",{className:"table-wrap",children:(0,n.jsxs)("table",{className:"tbl",children:[(0,n.jsx)("thead",{children:(0,n.jsxs)("tr",{children:[(0,n.jsx)("th",{children:"Name"}),(0,n.jsx)("th",{children:"Role"}),(0,n.jsx)("th",{children:"Status"}),(0,n.jsx)("th",{children:"Working dir"}),(0,n.jsx)("th",{children:"Created"}),(0,n.jsx)("th",{})]})}),(0,n.jsx)("tbody",{children:l.map(t=>{var s;return(0,n.jsxs)(r.Fragment,{children:[(0,n.jsxs)("tr",{children:[(0,n.jsx)("td",{className:"mono",children:t.name}),(0,n.jsx)("td",{children:t.role?(0,n.jsx)(v,{tone:"info",children:t.role}):"—"}),(0,n.jsx)("td",{children:(0,n.jsx)(j,{status:t.status})}),(0,n.jsx)("td",{className:"mono faint",children:t.working_dir}),(0,n.jsx)("td",{className:"faint nowrap",children:m(t.created_at)}),(0,n.jsx)("td",{className:"nowrap",children:(0,n.jsxs)("div",{className:"hstack",children:[(0,n.jsx)(y,{size:"sm",variant:"ghost",onClick:()=>e.selectRun(t.project_id,t.name),children:"Open"}),(0,n.jsx)(y,{size:"sm",variant:"secondary",onClick:()=>e.killAgent(t.project_id,t.name),children:"Kill"}),(0,n.jsx)(y,{size:"sm",variant:"danger",onClick:()=>e.deleteAgent(t.project_id,t.name),children:"Delete"})]})})]}),("working"===t.status||"crashed"===t.status)&&(null===(s=t.last_output)||void 0===s?void 0:s.trim())&&(0,n.jsx)("tr",{children:(0,n.jsxs)("td",{colSpan:6,style:{paddingTop:0},children:[(0,n.jsxs)("div",{className:"faint",style:{fontSize:"var(--text-xs)",marginBottom:4},children:["crashed"===t.status?"last output before crash":"live output",t.output_at?" \xb7 ".concat(x(t.output_at)):""]}),(0,n.jsx)("pre",{className:"mono",style:{margin:0,padding:"8px 10px",background:"var(--surface-2, rgba(0,0,0,0.25))",borderRadius:6,fontSize:"var(--text-xs)",lineHeight:1.4,maxHeight:220,overflow:"auto",whiteSpace:"pre"},children:t.last_output})]})})]},t.id)})})]})})]})})]})}let F=[{value:"",label:"Role — none"},{value:"junior",label:"junior"},{value:"senior",label:"senior"},{value:"deploy",label:"deploy"}],B=[{value:"900",label:"every 15 minutes"},{value:"1800",label:"every 30 minutes"},{value:"3600",label:"every hour"},{value:"21600",label:"every 6 hours"},{value:"86400",label:"every day"},{value:"604800",label:"every week"}],H={name_prefix:"",task:"",interval:"3600",role:""};function W(){let e=o(),[t,s]=(0,r.useState)(H),a=(0,r.useMemo)(()=>e.projects.map(e=>({value:e.id,label:e.id})),[e.projects]),l=async()=>{await e.createSchedule(e.selectedProjectId,{name_prefix:t.name_prefix,task:t.task,interval_seconds:Number(t.interval),role:t.role})&&s(H)};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"section-head",children:[(0,n.jsx)("div",{className:"section-title",children:"Schedules"}),(0,n.jsx)("div",{className:"section-desc",children:"Spawn a fresh agent on an interval. Each run is stateless — keep continuity in a file the prompt reads and overwrites."})]}),(0,n.jsx)("div",{className:"section-body",children:0===e.projects.length?(0,n.jsx)("div",{className:"empty",children:"Register a repository first."}):(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)("div",{className:"row",children:(0,n.jsx)("div",{style:{width:260},children:(0,n.jsx)(w,{label:"Repository",value:e.selectedProjectId,onChange:e.selectProject,options:a})})}),(0,n.jsxs)(g,{children:[(0,n.jsx)("div",{className:"card-head",style:{marginBottom:14},children:(0,n.jsx)("span",{className:"card-title",style:{fontSize:"var(--text-md)",color:"var(--text-heading)"},children:"New schedule"})}),(0,n.jsxs)("div",{className:"form-grid",children:[(0,n.jsx)(b,{label:"Name prefix",value:t.name_prefix,onChange:e=>s({...t,name_prefix:e}),placeholder:"nightly"}),(0,n.jsx)(w,{label:"Interval",value:t.interval,onChange:e=>s({...t,interval:e}),options:B}),(0,n.jsx)(w,{label:"Role",value:t.role,onChange:e=>s({...t,role:e}),options:F})]}),(0,n.jsx)("div",{className:"mt14",children:(0,n.jsx)(k,{label:"Prompt (the task every run starts with)",value:t.task,onChange:e=>s({...t,task:e}),rows:3,placeholder:"Read @notes.md and continue from where it left off. Before finishing, overwrite @notes.md with the current state so the next run can pick up from there."})}),(0,n.jsxs)("p",{className:"faint",style:{fontSize:"var(--text-xs)",margin:"10px 0 0"},children:["Runs are named ",(0,n.jsxs)("span",{className:"mono",children:[t.name_prefix.trim()||"prefix","-YYYYMMDD-HHMMSS"]}),". The repo is pulled before every run; the first run fires on the worker's next pass."]}),(0,n.jsx)("div",{className:"hstack mt14",children:(0,n.jsx)(y,{variant:"primary",disabled:e.cmd.busy||!t.name_prefix.trim()||!t.task.trim(),onClick:l,children:"Create schedule"})})]}),0===e.schedules.length?(0,n.jsx)("div",{className:"empty",children:"No schedules yet."}):(0,n.jsx)("div",{className:"table-wrap",children:(0,n.jsxs)("table",{className:"tbl",children:[(0,n.jsx)("thead",{children:(0,n.jsxs)("tr",{children:[(0,n.jsx)("th",{children:"On"}),(0,n.jsx)("th",{children:"Name"}),(0,n.jsx)("th",{children:"Repository"}),(0,n.jsx)("th",{children:"Interval"}),(0,n.jsx)("th",{children:"Prompt"}),(0,n.jsx)("th",{children:"Next run"}),(0,n.jsx)("th",{children:"Last run"}),(0,n.jsx)("th",{})]})}),(0,n.jsx)("tbody",{children:e.schedules.map(t=>(0,n.jsxs)("tr",{children:[(0,n.jsx)("td",{children:(0,n.jsx)(S,{on:t.enabled,onClick:()=>e.updateSchedule(t.id,{enabled:!t.enabled})})}),(0,n.jsxs)("td",{className:"mono",children:[t.name_prefix,t.role?(0,n.jsxs)(n.Fragment,{children:[" ",(0,n.jsx)(v,{tone:"info",children:t.role})]}):null]}),(0,n.jsx)("td",{className:"mono faint",children:t.project_id}),(0,n.jsx)("td",{className:"nowrap",children:function(e){let t=B.find(t=>Number(t.value)===e);return t?t.label:e%3600==0?"every ".concat(e/3600,"h"):e%60==0?"every ".concat(e/60,"m"):"every ".concat(e,"s")}(t.interval_seconds)}),(0,n.jsx)("td",{className:"faint",style:{maxWidth:340},children:(0,n.jsx)("span",{className:"truncate",style:{display:"block"},title:t.task,children:t.task})}),(0,n.jsx)("td",{className:"faint nowrap",children:t.enabled?m(t.next_run_at):"paused"}),(0,n.jsx)("td",{className:"faint nowrap",children:m(t.last_run_at)}),(0,n.jsx)("td",{className:"nowrap",children:(0,n.jsx)(y,{size:"sm",variant:"danger",onClick:()=>e.deleteSchedule(t.id),children:"Delete"})})]},t.id))})]})})]})})]})}let q=[{value:"approved",label:"approve"},{value:"rejected",label:"reject"}],G={branch:"",status:"approved",agent_name:"",sha:"",note:""};function J(){let e=o(),[t,s]=(0,r.useState)(G),a=(0,r.useMemo)(()=>e.projects.map(e=>({value:e.id,label:e.id})),[e.projects]),l=async()=>{await e.submitApproval(t),s(G)};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"section-head",children:[(0,n.jsx)("div",{className:"section-title",children:"Approvals"}),(0,n.jsx)("div",{className:"section-desc",children:"A merge is denied unless a standing approval exists — made by a different agent, pinned to the reviewed commit."})]}),(0,n.jsx)("div",{className:"section-body",children:0===e.projects.length?(0,n.jsx)("div",{className:"empty",children:"Register a repository first."}):(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)("div",{className:"row",children:(0,n.jsx)("div",{style:{width:260},children:(0,n.jsx)(w,{label:"Repository",value:e.selectedProjectId,onChange:e.selectProject,options:a})})}),(0,n.jsxs)(g,{children:[(0,n.jsx)("div",{className:"card-head",style:{marginBottom:14},children:(0,n.jsx)("span",{className:"card-title",style:{fontSize:"var(--text-md)",color:"var(--text-heading)"},children:"Record a verdict"})}),(0,n.jsxs)("div",{className:"form-grid",children:[(0,n.jsx)(b,{label:"Branch",value:t.branch,onChange:e=>s({...t,branch:e}),placeholder:"feat/auth"}),(0,n.jsx)(w,{label:"Verdict",value:t.status,onChange:e=>s({...t,status:e}),options:q}),(0,n.jsx)(b,{label:"Agent",value:t.agent_name,onChange:e=>s({...t,agent_name:e}),placeholder:"reads its HEAD (optional)"}),(0,n.jsx)(b,{label:"SHA",value:t.sha,onChange:e=>s({...t,sha:e}),placeholder:"pins the approval (optional)"})]}),(0,n.jsx)("div",{className:"mt14",children:(0,n.jsx)(b,{label:"Note",value:t.note,onChange:e=>s({...t,note:e}),placeholder:"optional"})}),(0,n.jsx)("div",{className:"hstack mt14",children:(0,n.jsx)(y,{variant:"primary",disabled:e.cmd.busy||!t.branch.trim(),onClick:l,children:"Enqueue verdict"})})]}),0===e.approvals.length?(0,n.jsx)("div",{className:"empty",children:"No approvals recorded."}):(0,n.jsx)("div",{className:"table-wrap",children:(0,n.jsxs)("table",{className:"tbl",children:[(0,n.jsx)("thead",{children:(0,n.jsxs)("tr",{children:[(0,n.jsx)("th",{children:"When"}),(0,n.jsx)("th",{children:"Branch"}),(0,n.jsx)("th",{children:"Verdict"}),(0,n.jsx)("th",{children:"By"}),(0,n.jsx)("th",{children:"SHA"}),(0,n.jsx)("th",{children:"Note"})]})}),(0,n.jsx)("tbody",{children:e.approvals.map(e=>(0,n.jsxs)("tr",{children:[(0,n.jsx)("td",{className:"faint nowrap",children:m(e.created_at)}),(0,n.jsx)("td",{className:"mono",children:e.branch}),(0,n.jsx)("td",{children:(0,n.jsx)(j,{status:e.status})}),(0,n.jsx)("td",{children:e.approved_by_agent_id?"agent ".concat(e.approved_by_agent_id):e.actor||"—"}),(0,n.jsx)("td",{className:"mono",children:p(e.approved_sha)}),(0,n.jsx)("td",{children:e.note||"—"})]},e.id))})]})})]})})]})}let K=[{value:"github",label:"github"},{value:"gitlab",label:"gitlab"},{value:"gitea",label:"gitea"},{value:"forgejo",label:"forgejo"},{value:"bitbucket",label:"bitbucket"}],Y={hostname:"",forge_type:"github",token_env_var:"",base_url:"",token:"",generate_ssh_key:!0};function V(e){let{value:t}=e,[s,a]=(0,r.useState)(!1),l=async()=>{try{await navigator.clipboard.writeText(t),a(!0),setTimeout(()=>a(!1),1500)}catch(e){}};return(0,n.jsxs)("div",{style:{marginTop:10},children:[(0,n.jsxs)("div",{className:"hstack",style:{justifyContent:"space-between"},children:[(0,n.jsx)("span",{className:"eyebrow",children:"SSH public key — add it to the forge (deploy key)"}),(0,n.jsx)(y,{size:"sm",variant:"secondary",onClick:l,children:s?"Copied":"Copy"})]}),(0,n.jsx)("pre",{className:"mono",style:{fontSize:"var(--text-xs)",whiteSpace:"pre-wrap",wordBreak:"break-all",margin:"6px 0 0",padding:8,border:"1px solid var(--border-default)",borderRadius:6,userSelect:"all"},children:t})]})}function Q(){let e=o(),[t,s]=(0,r.useState)(Y),[a,l]=(0,r.useState)(!1),i=()=>{s(Y),l(!1)},c=async()=>{(a?await e.updateHost(t.hostname,t):await e.createHost(t))&&i()},d=e=>{var t,a;s({hostname:e.hostname,forge_type:e.forge_type,token_env_var:null!==(t=e.token_env_var)&&void 0!==t?t:"",base_url:null!==(a=e.base_url)&&void 0!==a?a:"",token:"",generate_ssh_key:!1}),l(!0)};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"section-head",children:[(0,n.jsx)("div",{className:"section-title",children:"Git Servers"}),(0,n.jsxs)("div",{className:"section-desc",children:["Each server carries its own credentials: a forge token (encrypted at rest, used by agents' ",(0,n.jsx)("span",{className:"mono",children:"forge"})," + git) and an SSH deploy key — paste the public key into the forge. New repositories are added by picking a server and typing owner/name."]})]}),(0,n.jsxs)("div",{className:"section-body",children:[(0,n.jsxs)(g,{children:[(0,n.jsx)("div",{className:"card-head",style:{marginBottom:14},children:(0,n.jsx)("span",{className:"card-title",style:{fontSize:"var(--text-md)",color:"var(--text-heading)"},children:a?"Edit server \xb7 ".concat(t.hostname):"Add a git server"})}),(0,n.jsxs)("div",{className:"form-grid",children:[(0,n.jsx)(b,{label:"Hostname",value:t.hostname,onChange:e=>s({...t,hostname:e}),placeholder:"github.com",disabled:a}),(0,n.jsx)(w,{label:"Type",value:t.forge_type,onChange:e=>s({...t,forge_type:e}),options:K}),(0,n.jsx)(b,{label:a?"Forge token (blank = keep current)":"Forge token",type:"password",value:t.token,onChange:e=>s({...t,token:e}),placeholder:"stored encrypted; used by forge + git"}),(0,n.jsx)(b,{label:"Base URL (optional)",value:t.base_url,onChange:e=>s({...t,base_url:e}),placeholder:"https://git.corp.internal:8443"}),(0,n.jsx)(b,{label:"Token env var override (optional)",value:t.token_env_var,onChange:e=>s({...t,token_env_var:e}),placeholder:"GITEA_TOKEN"}),(0,n.jsxs)("label",{className:"field",children:[(0,n.jsx)("span",{className:"field-label",children:"SSH deploy key"}),(0,n.jsxs)("label",{className:"hstack",style:{gap:8,cursor:"pointer"},children:[(0,n.jsx)("input",{type:"checkbox",checked:t.generate_ssh_key,onChange:e=>s({...t,generate_ssh_key:e.target.checked})}),(0,n.jsx)("span",{style:{fontSize:"var(--text-sm)"},children:a?"Regenerate keypair (replaces the current key)":"Generate a keypair"})]})]})]}),(0,n.jsxs)("div",{className:"hstack mt14",children:[(0,n.jsx)(y,{variant:"primary",disabled:e.cmd.busy||!t.hostname.trim(),onClick:c,children:a?"Save changes":"Add server"}),a&&(0,n.jsx)(y,{variant:"ghost",onClick:i,children:"Cancel"})]})]}),0===e.hosts.length&&(0,n.jsx)("div",{className:"empty",children:"No git servers registered (built-in host map still applies)."}),e.hosts.map(t=>(0,n.jsxs)(g,{children:[(0,n.jsxs)("div",{className:"card-head",children:[(0,n.jsx)("span",{className:"mono",style:{fontWeight:"var(--fw-bold)",fontSize:"var(--text-lg)",color:"var(--text-heading)"},children:t.hostname}),(0,n.jsxs)("div",{className:"hstack",children:[(0,n.jsx)(v,{tone:"info",children:t.forge_type}),(0,n.jsx)(v,{tone:t.has_token?"success":"neutral",children:t.has_token?"token stored":"no token"}),(0,n.jsx)(v,{tone:t.ssh_public_key?"success":"neutral",children:t.ssh_public_key?"ssh key":"no ssh key"}),(0,n.jsx)(y,{size:"sm",variant:"secondary",onClick:()=>d(t),children:"Edit"}),(0,n.jsx)(y,{size:"sm",variant:"danger",onClick:()=>e.deleteHost(t.hostname),children:"Remove"})]})]}),(0,n.jsxs)("div",{className:"mono faint",style:{fontSize:"var(--text-xs)",marginTop:8},children:["token env ",t.token_env_var||"—",t.base_url?" \xb7 ".concat(t.base_url):""]}),t.ssh_public_key&&(0,n.jsx)(V,{value:t.ssh_public_key})]},t.hostname))]})]})}function X(){let e=o();return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)("div",{className:"section-head",children:(0,n.jsxs)("div",{className:"hstack",style:{justifyContent:"space-between"},children:[(0,n.jsxs)("div",{children:[(0,n.jsx)("div",{className:"section-title",children:"Activity"}),(0,n.jsx)("div",{className:"section-desc",children:"Control commands the worker drains from the queue."})]}),(0,n.jsx)(y,{variant:"secondary",disabled:e.cmd.busy,onClick:()=>e.pollCi(),children:"Sweep CI now"})]})}),(0,n.jsx)("div",{className:"section-body",children:0===e.commands.length?(0,n.jsx)("div",{className:"empty",children:"No commands yet."}):(0,n.jsx)("div",{className:"table-wrap",children:(0,n.jsxs)("table",{className:"tbl",children:[(0,n.jsx)("thead",{children:(0,n.jsxs)("tr",{children:[(0,n.jsx)("th",{children:"When"}),(0,n.jsx)("th",{children:"Type"}),(0,n.jsx)("th",{children:"Repository"}),(0,n.jsx)("th",{children:"Agent"}),(0,n.jsx)("th",{children:"Status"}),(0,n.jsx)("th",{children:"Result / Error"})]})}),(0,n.jsx)("tbody",{children:e.commands.map(e=>(0,n.jsxs)("tr",{children:[(0,n.jsx)("td",{className:"faint nowrap",children:m(e.created_at)}),(0,n.jsx)("td",{className:"mono",children:e.type}),(0,n.jsx)("td",{className:"mono",children:e.project_id||"—"}),(0,n.jsx)("td",{className:"mono",children:e.agent_name||"—"}),(0,n.jsx)("td",{children:(0,n.jsx)(j,{status:e.status})}),(0,n.jsx)("td",{className:"mono faint",style:{fontSize:"var(--text-xs)"},children:e.error||(e.result?JSON.stringify(e.result):"—")})]},e.id))})]})})})]})}function $(){let e=o(),[t,s]=(0,r.useState)(""),[a,l]=(0,r.useState)(""),i=async()=>{t.trim()&&a.trim()&&await e.setSharedKey(t.trim(),a.trim())&&(s(""),l(""))};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"section-head",children:[(0,n.jsx)("div",{className:"section-title",children:"Shared"}),(0,n.jsx)("div",{className:"section-desc",children:"The cross-project global feed and shared facts."})]}),(0,n.jsxs)("div",{className:"section-body",children:[(0,n.jsxs)("div",{children:[(0,n.jsx)("div",{className:"eyebrow",style:{marginBottom:10},children:"Global feed"}),0===e.shared.log.length?(0,n.jsx)("div",{className:"empty",children:"No global log entries."}):(0,n.jsx)("div",{className:"table-wrap",children:(0,n.jsxs)("table",{className:"tbl",children:[(0,n.jsx)("thead",{children:(0,n.jsxs)("tr",{children:[(0,n.jsx)("th",{children:"When"}),(0,n.jsx)("th",{children:"Agent"}),(0,n.jsx)("th",{children:"Status"}),(0,n.jsx)("th",{children:"Summary"}),(0,n.jsx)("th",{children:"CI"})]})}),(0,n.jsx)("tbody",{children:e.shared.log.map(e=>(0,n.jsxs)("tr",{children:[(0,n.jsx)("td",{className:"faint nowrap",children:m(e.created_at)}),(0,n.jsx)("td",{className:"mono",children:e.agent_id}),(0,n.jsx)("td",{children:(0,n.jsx)(j,{status:e.status})}),(0,n.jsx)("td",{children:e.summary||"—"}),(0,n.jsx)("td",{children:(0,n.jsx)(j,{status:e.ci_status})})]},e.id))})]})})]}),(0,n.jsxs)(g,{children:[(0,n.jsx)("div",{className:"card-head",style:{marginBottom:14},children:(0,n.jsx)("span",{className:"card-title",style:{fontSize:"var(--text-md)",color:"var(--text-heading)"},children:"Set a shared key"})}),(0,n.jsxs)("div",{className:"form-grid",children:[(0,n.jsx)(b,{label:"Key",value:t,onChange:s,placeholder:"key"}),(0,n.jsx)(b,{label:"Value",value:a,onChange:l,placeholder:"value"})]}),(0,n.jsx)("p",{className:"faint",style:{fontSize:"var(--text-xs)",margin:"10px 0 0"},children:"Requires the shared-context write token (or admin/global if unset)."}),(0,n.jsx)("div",{className:"hstack mt14",children:(0,n.jsx)(y,{variant:"primary",disabled:!t.trim()||!a.trim(),onClick:i,children:"Set"})})]}),e.shared.context.length>0&&(0,n.jsx)("div",{className:"table-wrap",children:(0,n.jsxs)("table",{className:"tbl",children:[(0,n.jsx)("thead",{children:(0,n.jsxs)("tr",{children:[(0,n.jsx)("th",{children:"Key"}),(0,n.jsx)("th",{children:"Value"}),(0,n.jsx)("th",{children:"Updated"})]})}),(0,n.jsx)("tbody",{children:e.shared.context.map(e=>(0,n.jsxs)("tr",{children:[(0,n.jsx)("td",{className:"mono",children:e.key}),(0,n.jsx)("td",{children:e.value}),(0,n.jsx)("td",{className:"faint nowrap",children:m(e.updated_at)})]},e.key))})]})})]})]})}function Z(e){let t=window.screenX+Math.max(0,(window.outerWidth-520)/2),s=window.screenY+Math.max(0,(window.outerHeight-760)/2);return window.open(e,"claude-login","popup=yes,width=".concat(520,",height=").concat(760,",left=").concat(Math.round(t),",top=").concat(Math.round(s)))}function ee(){let e=o(),{status:t,url:s,message:a}=e.claudeLogin,[l,i]=(0,r.useState)(""),c=(0,r.useRef)(null),d="starting"===t||"submitting"===t,u="awaiting"===t||"submitting"===t;(0,r.useEffect)(()=>{if("awaiting"===t&&s&&c.current&&!c.current.closed)try{c.current.location.href=s}catch(e){}if("done"===t||"error"===t){var e;null===(e=c.current)||void 0===e||e.close(),c.current=null}},[t,s]);let h=()=>{c.current=Z("about:blank"),e.startClaudeLogin()},m=async()=>{await e.submitClaudeCode(l)&&i("")};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"section-head",children:[(0,n.jsx)("div",{className:"section-title",children:"Claude Login"}),(0,n.jsxs)("div",{className:"section-desc",children:["Log Claude Code in on the host so agents can run. This drives"," ",(0,n.jsx)("span",{className:"mono",children:"claude /login"})," in the control container and picks the Claude account with a subscription."]})]}),(0,n.jsxs)("div",{className:"section-body",style:{display:"flex",flexDirection:"column",gap:16},children:[a&&(0,n.jsx)(C,{tone:"error"===t?"danger":"done"===t?"success":"info",children:a}),"done"===t?(0,n.jsx)("div",{children:(0,n.jsx)(y,{variant:"secondary",onClick:e.resetClaudeLogin,children:"Log in again"})}):u?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(C,{tone:"info",children:"A Claude sign-in window should have opened. Authorize there, copy the code Claude shows you, and paste it below. If the window didn't open (popups blocked), use the button."}),(0,n.jsxs)("div",{className:"hstack",style:{gap:10,flexWrap:"wrap"},children:[(0,n.jsx)(y,{variant:"secondary",disabled:!s,onClick:()=>{s&&(c.current=Z(s))},children:"Open Claude sign-in window ↗"}),s&&(0,n.jsx)("a",{className:"btn btn-ghost",href:s,target:"_blank",rel:"noopener noreferrer",children:"Open in a new tab"}),(0,n.jsx)(y,{variant:"ghost",disabled:d,onClick:h,children:"Restart"})]}),(0,n.jsxs)("div",{className:"hstack",style:{gap:10,alignItems:"flex-end",flexWrap:"wrap"},children:[(0,n.jsx)("div",{style:{flex:"1 1 320px"},children:(0,n.jsx)(b,{label:"Authorization code",value:l,onChange:i,placeholder:"Paste the code from claude.com",disabled:"submitting"===t})}),(0,n.jsx)(y,{variant:"primary",disabled:"submitting"===t||!l.trim(),onClick:m,children:"submitting"===t?"Submitting…":"Finish login"})]})]}):(0,n.jsxs)("div",{className:"hstack",style:{gap:10},children:[(0,n.jsx)(y,{variant:"primary",disabled:d,onClick:h,children:"starting"===t?"Starting…":"Log in to Claude"}),"error"===t&&(0,n.jsx)(y,{variant:"ghost",disabled:d,onClick:h,children:"Retry"})]})]})]})}let et=[{key:"runs",label:"Runs",count:e=>e.agents.length,accent:e=>e.agents.some(e=>"paused_for_input"===e.status)},{key:"repositories",label:"Repositories",count:e=>e.projects.length},{key:"agents",label:"Agents",count:e=>e.agents.length},{key:"schedules",label:"Schedules",count:e=>e.schedules.length},{key:"approvals",label:"Approvals",count:e=>e.approvals.length},{key:"servers",label:"Git Servers",count:e=>e.hosts.length},{key:"activity",label:"Activity",count:e=>e.commands.length},{key:"shared",label:"Shared",count:e=>e.shared.context.length},{key:"login",label:"Claude Login",count:()=>0,accent:e=>"done"!==e.claudeLogin.status}];function es(e){let{onSignOut:t}=e,s=o();return(0,n.jsxs)("div",{className:"app",children:[(0,n.jsxs)("aside",{className:"sidebar",children:[(0,n.jsxs)("div",{className:"brand",children:[(0,n.jsx)("span",{className:"logo"}),"Claude Monitor"]}),et.map(e=>{var t,a;let r=e.count(s),l=null!==(a=null===(t=e.accent)||void 0===t?void 0:t.call(e,s))&&void 0!==a&&a;return(0,n.jsxs)("button",{className:"nav-item".concat(s.section===e.key?" active":""),onClick:()=>s.setSection(e.key),children:[(0,n.jsx)("span",{children:e.label}),(0,n.jsx)("span",{className:"count",style:l?{color:"var(--lw-warning-fg)"}:void 0,children:r||""})]},e.key)}),(0,n.jsx)("div",{className:"sidebar-spacer"}),(0,n.jsxs)("div",{className:"sidebar-foot",children:[(0,n.jsxs)("button",{className:"nav-item",onClick:s.refresh,title:"Refresh now",children:[(0,n.jsx)("span",{children:"Refresh"}),(0,n.jsx)("span",{className:"count",children:"↻"})]}),(0,n.jsx)("button",{className:"nav-item",onClick:t,title:"Sign out / change token",children:(0,n.jsx)("span",{children:"Sign out"})})]})]}),(0,n.jsxs)("main",{className:"main",children:[s.cmd.text&&(0,n.jsx)("p",{className:"banner ".concat(s.cmd.error?"err":"ok"),style:{marginTop:16},children:s.cmd.text}),s.lastError&&(0,n.jsx)("p",{className:"banner err",style:{marginTop:12},children:s.lastError}),"runs"===s.section?(0,n.jsx)(I,{}):(0,n.jsxs)("div",{className:"main-scroll",children:["repositories"===s.section&&(0,n.jsx)(O,{}),"agents"===s.section&&(0,n.jsx)(U,{}),"schedules"===s.section&&(0,n.jsx)(W,{}),"approvals"===s.section&&(0,n.jsx)(J,{}),"servers"===s.section&&(0,n.jsx)(Q,{}),"activity"===s.section&&(0,n.jsx)(X,{}),"shared"===s.section&&(0,n.jsx)($,{}),"login"===s.section&&(0,n.jsx)(ee,{})]})]})]})}function ea(e){let{error:t,onSubmit:s}=e,[a,l]=(0,r.useState)("");return(0,n.jsx)("div",{className:"gate",children:(0,n.jsxs)("form",{className:"gate-card",onSubmit:e=>{e.preventDefault();let t=a.trim();t&&s(t)},children:[(0,n.jsxs)("div",{className:"gate-brand",children:[(0,n.jsx)("span",{className:"logo",style:{width:26,height:26,borderRadius:7}}),"Claude Monitor"]}),(0,n.jsx)("p",{className:"muted",style:{fontSize:"var(--text-sm)",margin:0},children:"Paste your API token to continue. Management actions require the admin token; read-only views work with the plain auth token."}),(0,n.jsx)("input",{className:"input",type:"password",autoComplete:"current-password",placeholder:"API token",value:a,onChange:e=>l(e.target.value),autoFocus:!0}),t&&(0,n.jsx)("p",{className:"callout callout-danger",style:{margin:0},children:t}),(0,n.jsx)("button",{className:"btn btn-primary",type:"submit",children:"Continue"})]})})}let en="handler_token";function er(){let[e,t]=(0,r.useState)(null),[s,a]=(0,r.useState)("");(0,r.useEffect)(()=>{let e=window.localStorage.getItem(en);e&&t(e)},[]);let l=(0,r.useCallback)(e=>{window.localStorage.setItem(en,e),a(""),t(e)},[]),i=(0,r.useCallback)(()=>{window.localStorage.removeItem(en),t(null)},[]),c=(0,r.useCallback)(()=>{window.localStorage.removeItem(en),t(null),a("Invalid token — please try again.")},[]);return e?(0,n.jsx)(d,{token:e,onUnauthorized:c,children:(0,n.jsx)(es,{onSignOut:i})}):(0,n.jsx)(ea,{error:s,onSubmit:l})}},257:function(e,t,s){"use strict";var a,n;e.exports=(null==(a=s.g.process)?void 0:a.env)&&"object"==typeof(null==(n=s.g.process)?void 0:n.env)?s.g.process:s(4227)},4227:function(e){!function(){var t={229:function(e){var t,s,a,n=e.exports={};function r(){throw Error("setTimeout has not been defined")}function l(){throw Error("clearTimeout has not been defined")}function i(e){if(t===setTimeout)return setTimeout(e,0);if((t===r||!t)&&setTimeout)return t=setTimeout,setTimeout(e,0);try{return t(e,0)}catch(s){try{return t.call(null,e,0)}catch(s){return t.call(this,e,0)}}}!function(){try{t="function"==typeof setTimeout?setTimeout:r}catch(e){t=r}try{s="function"==typeof clearTimeout?clearTimeout:l}catch(e){s=l}}();var c=[],o=!1,d=-1;function u(){o&&a&&(o=!1,a.length?c=a.concat(c):d=-1,c.length&&h())}function h(){if(!o){var e=i(u);o=!0;for(var t=c.length;t;){for(a=c,c=[];++d1)for(var s=1;sHandler · Claude Activity
Claude Monitor

Paste your API token to continue. Management actions require the admin token; read-only views work with the plain auth token.

\ No newline at end of file +Handler · Claude Activity
Claude Monitor

Paste your API token to continue. Management actions require the admin token; read-only views work with the plain auth token.

\ No newline at end of file diff --git a/src/handler/api/static/index.txt b/src/handler/api/static/index.txt index 847ed74..e04b077 100644 --- a/src/handler/api/static/index.txt +++ b/src/handler/api/static/index.txt @@ -1,7 +1,7 @@ 2:I[9107,[],"ClientPageRoot"] -3:I[9859,["931","static/chunks/app/page-0637b8cf149a88ec.js"],"default",1] +3:I[9859,["931","static/chunks/app/page-2ff318d75a2e3799.js"],"default",1] 4:I[4707,[],""] 5:I[6423,[],""] -0:["ga21jhjKhYsutf8F3vo_-",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/bbd6ee1e7265be74.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]] +0:["zI_TsbchBb39diXA-dnfw",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/bbd6ee1e7265be74.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]] 6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"Handler · Claude Activity"}],["$","meta","3",{"name":"description","content":"Monitor and manage Claude Code agents across projects."}],["$","link","4",{"rel":"icon","href":"/icon.svg?bab509b45a421e43","type":"image/svg+xml","sizes":"any"}]] 1:null diff --git a/src/handler/config.py b/src/handler/config.py index 1684dc4..0c15039 100644 --- a/src/handler/config.py +++ b/src/handler/config.py @@ -58,8 +58,10 @@ class Settings(BaseSettings): # start a run are left queued (for another worker) while all slots are busy. max_concurrent_runs: int = 4 # Heartbeats older than this many seconds mark a worker dead; the reaper flips its - # running runs (and their agents) to ``crashed``. - worker_stale_after: float = 60.0 + # running runs (and their agents) to ``crashed``. Generous by design: one slow + # synchronous command (a large clone, the interactive login flow) can hold a worker + # between heartbeats for a minute or more, and a false reap is worse than a slow one. + worker_stale_after: float = 300.0 # Per-run spend cap passed as ``--max-budget-usd``. 0 disables the flag. run_budget_usd: float = 0.0 # Refuse to upload a claude session archive larger than this (a runaway sidecar dir diff --git a/src/handler/control/credsync.py b/src/handler/control/credsync.py index f76363f..166ea0d 100644 --- a/src/handler/control/credsync.py +++ b/src/handler/control/credsync.py @@ -106,10 +106,16 @@ def _merge_claude_json(path: str, incoming: str) -> None: for key in _ACCOUNT_KEYS: if key in new_data: merged[key] = new_data[key] + _write_private(path, json.dumps(merged, indent=2)) + + +def _write_private(path: str, content: str) -> None: + """Atomic write with 0600 from the first byte — these files carry OAuth tokens.""" os.makedirs(os.path.dirname(path) or ".", exist_ok=True) tmp = f"{path}.credsync.tmp" - with open(tmp, "w") as fh: - json.dump(merged, fh, indent=2) + fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) + with os.fdopen(fd, "w") as fh: + fh.write(content) os.replace(tmp, path) @@ -151,11 +157,7 @@ def refresh() -> str | None: if rel == ".claude.json": _merge_claude_json(path, content) else: - os.makedirs(os.path.dirname(path) or ".", exist_ok=True) - tmp = f"{path}.credsync.tmp" - with open(tmp, "w") as fh: - fh.write(content) - os.replace(tmp, path) + _write_private(path, content) _state.seen_updated_at = row["updated_at"] _state.last_fingerprint = fingerprint() return "materialized" diff --git a/src/handler/control/headless.py b/src/handler/control/headless.py index 3582810..a50ba64 100644 --- a/src/handler/control/headless.py +++ b/src/handler/control/headless.py @@ -198,6 +198,7 @@ class RunSupervisor: self.archive_interval = archive_interval self.on_exit = on_exit # worker's slot-release callback self._seq = 0 + self._seq_lock = threading.Lock() # reader thread + supervisor both emit events self._result_payload: dict | None = None self._canceled = False self.thread: threading.Thread | None = None @@ -211,13 +212,15 @@ class RunSupervisor: # ---------------------------------------------------------------- internals def _insert_event(self, etype: str, payload: dict) -> None: - self._seq += 1 + with self._seq_lock: + self._seq += 1 + seq = self._seq with connection() as conn: repo.insert_agent_event( conn, self.agent["id"], self.run["id"], - seq=self._seq, + seq=seq, type=etype, payload=payload, session_id=self.run["session_id"], @@ -299,13 +302,22 @@ class RunSupervisor: last_archive = now proc.wait() reader.join(timeout=30.0) + if proc.stdout is not None: + proc.stdout.close() stderr_file.seek(0) stderr_tail = stderr_file.read()[-4000:].decode("utf-8", "replace") stderr_file.close() self._settle(exit_code=proc.returncode, stderr_tail=stderr_tail) def _settle(self, exit_code: int | None, stderr_tail: str) -> None: - """Reconcile run + agent status once the process is gone, then final-archive.""" + """Final-archive, then reconcile run + agent status once the process is gone. + + Order matters: a resume can be claimed the moment the run leaves ``running``, and + it materializes from ``session_archives`` — so the archive upload must land + BEFORE the run is marked finished, or a fast resume races an incomplete archive + and needlessly degrades to context re-injection. + """ + self._upload_archive() result = self._result_payload clean = ( exit_code == 0 @@ -341,7 +353,6 @@ class RunSupervisor: self._insert_event("worker", detail) except Exception: # noqa: BLE001 - never let bookkeeping raise out of the thread pass - self._upload_archive() if self.on_exit is not None: try: self.on_exit(self) diff --git a/src/handler/control/spawn.py b/src/handler/control/spawn.py index 561c11c..7e53ee0 100644 --- a/src/handler/control/spawn.py +++ b/src/handler/control/spawn.py @@ -268,14 +268,20 @@ def resume(agent: dict, answer: str, worker_id: str | None = None) -> tuple[bool elif not transcript.exists(): return _resume_reinjected(agent, answer, settings_path, env, worker_id) - run = headless.launch( - agent, - kind="resume", - prompt=answer, - settings_path=settings_path, - env=env, - worker_id=worker_id, - ) + try: + run = headless.launch( + agent, + kind="resume", + prompt=answer, + settings_path=settings_path, + env=env, + worker_id=worker_id, + ) + except repo.RunConflictError: + # Another worker won the race for this resume (two queued resume commands, or a + # concurrent spawn) — losing loudly here beats two claude processes corrupting + # one session transcript. + return False, "another worker is already running this agent's session" return True, f"headless resume run {run['id']} started for session {agent['session_id']}" @@ -302,14 +308,17 @@ def _resume_reinjected( if entry.get("summary"): parts.append(f"Earlier log: {entry['summary']}") parts.append(f"The operator's answer/instruction: {answer}") - run = headless.launch( - agent, - kind="spawn", # a genuinely new session (new UUID) — --resume has nothing to load - prompt="\n\n".join(parts), - settings_path=settings_path, - env=env, - worker_id=worker_id, - ) + try: + run = headless.launch( + agent, + kind="spawn", # a genuinely new session (new UUID) — --resume has nothing to load + prompt="\n\n".join(parts), + settings_path=settings_path, + env=env, + worker_id=worker_id, + ) + except repo.RunConflictError: + return False, "another worker is already running this agent's session" with connection() as conn: repo.insert_agent_event( conn, diff --git a/src/handler/control/worker.py b/src/handler/control/worker.py index 7368b99..2a4591f 100644 --- a/src/handler/control/worker.py +++ b/src/handler/control/worker.py @@ -413,6 +413,12 @@ def drain(worker_id: str, limit: int | None = None) -> int: break _run_one(command) processed += 1 + # Heartbeat between commands: a long queue must not starve the proof-of-life + # (a peer's reaper would otherwise mark this worker's live runs crashed). + try: + heartbeat(worker_id) + except Exception: # noqa: BLE001 - bookkeeping must not stop the drain + pass return processed diff --git a/src/handler/db/repository.py b/src/handler/db/repository.py index 0f8e0aa..349a094 100644 --- a/src/handler/db/repository.py +++ b/src/handler/db/repository.py @@ -733,10 +733,35 @@ def delete_worker(conn: Connection, worker_id: str) -> bool: return result.rowcount > 0 +class RunConflictError(Exception): + """The agent already has a ``running`` run — a second concurrent claude process on + one session would corrupt its transcript.""" + + def create_run( conn: Connection, agent_id: int, session_id: str, worker_id: str, kind: str ) -> dict: - """Open an ``agent_runs`` row for a launching headless invocation.""" + """Open an ``agent_runs`` row for a launching headless invocation. + + Enforces **one running run per agent** atomically: two workers that both claimed a + resume for the same agent must not both launch. The agent row is locked first on + Postgres (``FOR UPDATE``) so concurrent transactions serialize; SQLite's single + writer gives the same guarantee for free. Raises :class:`RunConflictError` if a + running run already exists. + """ + lock = select(agents.c.id).where(agents.c.id == agent_id) + if conn.dialect.name == "postgresql": + lock = lock.with_for_update() + conn.execute(lock) + existing = conn.execute( + select(agent_runs.c.id) + .where(agent_runs.c.agent_id == agent_id, agent_runs.c.status == "running") + .limit(1) + ).first() + if existing is not None: + raise RunConflictError( + f"agent {agent_id} already has running run {existing[0]}" + ) result = conn.execute( agent_runs.insert().values( agent_id=agent_id, @@ -902,12 +927,13 @@ def get_runtime_secret(conn: Connection, key: str) -> dict | None: return _row_to_dict(row) -def get_latest_finished_command(conn: Connection, type: str) -> dict | None: - """The most recent ``done`` command of a type (login pinning reads login_start's - ``claimed_by`` to route login_submit to the same worker container).""" +def get_latest_claimed_command(conn: Connection, type: str) -> dict | None: + """The most recent command of a type that some worker has claimed (running or + finished). Login pinning reads login_start's ``claimed_by`` to route login_submit to + the same worker container — including while the login_start is still in flight.""" row = conn.execute( select(commands) - .where(commands.c.type == type, commands.c.status == "done") + .where(commands.c.type == type, commands.c.claimed_by.is_not(None)) .order_by(commands.c.id.desc()) .limit(1) ).first() diff --git a/tests/test_reaper.py b/tests/test_reaper.py index db594f8..e617e8f 100644 --- a/tests/test_reaper.py +++ b/tests/test_reaper.py @@ -36,7 +36,7 @@ def _hb(worker_id, age_seconds=0.0): def test_reaper_marks_stale_workers_runs_crashed(env): agent, run = _seed_run("w-dead") - _hb("w-dead", age_seconds=120) # stale: default worker_stale_after is 60s + _hb("w-dead", age_seconds=600) # stale: default worker_stale_after is 300s assert worker.reap_dead_workers() == 1 @@ -64,7 +64,7 @@ def test_reaper_preserves_paused_agent_status(env): """paused_for_input still accurately describes what the agent needs — only agents stuck in 'working' get flipped to crashed.""" agent, run = _seed_run("w-dead2", agent_name="paused", agent_status="paused_for_input") - _hb("w-dead2", age_seconds=120) + _hb("w-dead2", age_seconds=600) assert worker.reap_dead_workers() == 1 with get_engine().begin() as conn: @@ -74,7 +74,7 @@ def test_reaper_preserves_paused_agent_status(env): def test_reaper_idempotent_against_races(env): agent, run = _seed_run("w-dead3", agent_name="raced") - _hb("w-dead3", age_seconds=120) + _hb("w-dead3", age_seconds=600) assert worker.reap_dead_workers() == 1 # A second reaper (or the same one next pass) finds nothing left to settle. assert worker.reap_dead_workers() == 0 diff --git a/tests/test_repository_runs.py b/tests/test_repository_runs.py index 3616320..ff862bd 100644 --- a/tests/test_repository_runs.py +++ b/tests/test_repository_runs.py @@ -55,17 +55,29 @@ def test_cancel_request_roundtrip(conn): def test_list_running_runs_scoped_by_worker(conn): agent = _agent(conn) r1 = repo.create_run(conn, agent["id"], "s1", "worker-a", "spawn") - r2 = repo.create_run(conn, agent["id"], "s2", "worker-b", "spawn") repo.finish_run(conn, r1["id"], "completed") + r2 = repo.create_run(conn, agent["id"], "s2", "worker-b", "spawn") running = repo.list_running_runs(conn) assert [r["id"] for r in running] == [r2["id"]] assert repo.list_running_runs(conn, worker_id="worker-a") == [] assert [r["id"] for r in repo.list_running_runs(conn, worker_id="worker-b")] == [r2["id"]] +def test_create_run_refuses_concurrent_run_for_agent(conn): + """One running run per agent, atomically — two workers racing a resume must not both + launch a claude process on the same session.""" + import pytest + + agent = _agent(conn) + repo.create_run(conn, agent["id"], "s1", "worker-a", "spawn") + with pytest.raises(repo.RunConflictError): + repo.create_run(conn, agent["id"], "s1", "worker-b", "resume") + + def test_latest_run_and_agent_session(conn): agent = _agent(conn) - repo.create_run(conn, agent["id"], "s1", "w", "spawn") + first = repo.create_run(conn, agent["id"], "s1", "w", "spawn") + repo.finish_run(conn, first["id"], "completed") latest = repo.create_run(conn, agent["id"], "s1", "w", "resume") assert repo.get_latest_run(conn, agent["id"])["id"] == latest["id"] diff --git a/tests/test_worker_concurrency.py b/tests/test_worker_concurrency.py index 9e9d0e2..c3bc290 100644 --- a/tests/test_worker_concurrency.py +++ b/tests/test_worker_concurrency.py @@ -22,22 +22,26 @@ def headless_env(env, monkeypatch): config.get_settings.cache_clear() -def _seed(conn_count_running_for=None): +def _seed(extra_agents=("b",)): with get_engine().begin() as conn: repo.create_project(conn, "p", "/tmp/p") agent = repo.create_agent(conn, "p", "a", "/tmp/p/a") + for name in extra_agents: + repo.create_agent(conn, "p", name, f"/tmp/p/{name}") return agent -def _running_run(agent_id, worker_id): +def _running_run(agent_name, worker_id): with get_engine().begin() as conn: - return repo.create_run(conn, agent_id, f"sid-{worker_id}", worker_id, "spawn") + agent = repo.get_agent_by_name(conn, "p", agent_name) + return repo.create_run(conn, agent["id"], f"sid-{agent_name}", worker_id, "spawn") def test_full_worker_skips_run_commands_but_processes_others(headless_env, monkeypatch): agent = _seed() - _running_run(agent["id"], "w-full") - _running_run(agent["id"], "w-full") # 2 running == MAX_CONCURRENT_RUNS + # 2 running (one per agent — one running run per agent) == MAX_CONCURRENT_RUNS + _running_run("a", "w-full") + _running_run("b", "w-full") spawned = {} monkeypatch.setattr( @@ -66,9 +70,9 @@ def test_full_worker_skips_run_commands_but_processes_others(headless_env, monke def test_slot_frees_when_run_finishes(headless_env, monkeypatch): - agent = _seed() - run1 = _running_run(agent["id"], "w1") - _running_run(agent["id"], "w1") + _seed() + run1 = _running_run("a", "w1") + _running_run("b", "w1") assert worker._full_slot_exclusions("w1") == worker._RUN_COMMANDS with get_engine().begin() as conn: