+ live output{a.output_at ? ` · ${timeAgo(a.output_at)}` : ""}
+
+
+ {a.last_output}
+
+
+
+ )}
+
))}
diff --git a/frontend/lib/api.ts b/frontend/lib/api.ts
index 2c004f2..d54d333 100644
--- a/frontend/lib/api.ts
+++ b/frontend/lib/api.ts
@@ -30,6 +30,10 @@ export interface Agent {
working_dir: string;
status: string;
role?: string | null;
+ /* Latest tmux pane-tail snapshot from the worker, so the UI can show what a running
+ * agent is actually doing (and expose one wedged on an interactive prompt). */
+ last_output?: string | null;
+ output_at?: string | null;
created_at: string;
}
diff --git a/src/handler/api/schemas.py b/src/handler/api/schemas.py
index 51ed620..cbf825b 100644
--- a/src/handler/api/schemas.py
+++ b/src/handler/api/schemas.py
@@ -128,6 +128,10 @@ class AgentOut(BaseModel):
working_dir: str
status: str
role: Role | None = None
+ # Latest tmux pane-tail snapshot (worker poll loop) so the UI can show what a running
+ # agent is doing — including one wedged on an interactive prompt no one can answer.
+ last_output: str | None = None
+ output_at: datetime | None = None
created_at: datetime
diff --git a/src/handler/api/static/404.html b/src/handler/api/static/404.html
index 8738f2c..cc28808 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/117-e7bb738621b70d3f.js b/src/handler/api/static/_next/static/chunks/117-d9d1ed00b6332a85.js
similarity index 100%
rename from src/handler/api/static/_next/static/chunks/117-e7bb738621b70d3f.js
rename to src/handler/api/static/_next/static/chunks/117-d9d1ed00b6332a85.js
diff --git a/src/handler/api/static/_next/static/chunks/app/_not-found/page-b54079f2332706f3.js b/src/handler/api/static/_next/static/chunks/app/_not-found/page-34168ac570dc8992.js
similarity index 100%
rename from src/handler/api/static/_next/static/chunks/app/_not-found/page-b54079f2332706f3.js
rename to src/handler/api/static/_next/static/chunks/app/_not-found/page-34168ac570dc8992.js
diff --git a/src/handler/api/static/_next/static/chunks/app/page-4aea2dcf515b45ea.js b/src/handler/api/static/_next/static/chunks/app/page-4aea2dcf515b45ea.js
deleted file mode 100644
index d5e34e2..0000000
--- a/src/handler/api/static/_next/static/chunks/app/page-4aea2dcf515b45ea.js
+++ /dev/null
@@ -1 +0,0 @@
-(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[931],{2482: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 ea}});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,x]=(0,r.useState)([]),[p,j]=(0,r.useState)([]),[v,g]=(0,r.useState)(""),[y,b]=(0,r.useState)(null),[f,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,z]=(0,r.useState)([]),[E,P]=(0,r.useState)([]),[M,O]=(0,r.useState)([]),[L,U]=(0,r.useState)({log:[],context:[]}),[D,F]=(0,r.useState)({text:"",error:!1,busy:!1}),[B,H]=(0,r.useState)(""),[q,W]=(0,r.useState)(!0),[G,J]=(0,r.useState)({status:"idle",url:"",message:""}),K=(0,r.useRef)(u);K.current=u;let Y=(0,r.useRef)(v);Y.current=v;let V=(0,r.useRef)(y);V.current=y;let Q=(0,r.useRef)(S);Q.current=S;let X=e=>{e instanceof i||H(e.message)},$=(0,r.useCallback)(async()=>{try{let e=await d.current.api("/projects");x(e),H(""),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){X(e)}},[]),Z=(0,r.useCallback)(async e=>{try{let t=await Promise.all(e.map(e=>d.current.api("/projects/".concat(encodeURIComponent(e.id),"/agents")).catch(()=>[])));j(t.flat())}catch(e){X(e)}},[]),ee=(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)):X(e)}try{let e=await d.current.api("".concat(s,"/log?limit=").concat(100,"&offset=").concat(Q.current));C(e)}catch(e){X(e)}},[]),et=(0,r.useCallback)(async e=>{if(!e){T([]);return}try{T(await d.current.api("/projects/".concat(encodeURIComponent(e),"/approvals")))}catch(e){X(e)}},[]),es=(0,r.useCallback)(async()=>{try{z(await d.current.api("/hosts"))}catch(e){X(e)}},[]),ea=(0,r.useCallback)(async()=>{try{P(await d.current.api("/commands?limit=50"))}catch(e){X(e)}},[]),en=(0,r.useCallback)(async()=>{try{O(await d.current.api("/schedules"))}catch(e){X(e)}},[]),er=(0,r.useCallback)(async()=>{try{let[e,t]=await Promise.all([d.current.api("/shared/log"),d.current.api("/shared/context")]);U({log:e,context:t})}catch(e){X(e)}},[]),el=(0,r.useCallback)(async()=>{let e=await d.current.api("/projects").catch(e=>(X(e),null));e&&(x(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 Z(e));let t=K.current,s=V.current;s&&await ee(s.projectId,s.name),"approvals"===t&&await et(Y.current),"servers"===t&&await es(),"activity"===t&&await ea(),"schedules"===t&&await en(),"shared"===t&&await er()},[Z,ee,et,es,ea,en,er]);(0,r.useEffect)(()=>{let e=!0;(async()=>{W(!0),await el(),e&&W(!1)})();let t=setInterval(()=>{document.hidden||el()},5e3);return()=>{e=!1,clearInterval(t)}},[el]);let ei=(0,r.useCallback)(e=>{h(e),F({text:"",error:!1,busy:!1}),"approvals"===e&&et(Y.current),"servers"===e&&es(),"activity"===e&&ea(),"schedules"===e&&en(),"shared"===e&&er()},[et,es,ea,en,er]),ec=(0,r.useCallback)(e=>{g(e),"approvals"===K.current&&et(e)},[et]),eo=(0,r.useCallback)((e,t)=>{b({projectId:e,name:t}),R(0),Q.current=0,k(null),N(!1),C([]),ee(e,t)},[ee]),ed=(0,r.useCallback)(e=>{let t=Math.max(0,S+100*e);if(t===S)return;R(t),Q.current=t;let s=V.current;s&&ee(s.projectId,s.name)},[S,ee]),eu=(0,r.useCallback)(()=>{el()},[el]),eh=(0,r.useCallback)(async(e,t,s)=>{F({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 F({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 F({text:"".concat(s," ").concat(r?"done":"failed").concat(l?" — "+l:""),error:!r,busy:!1}),n}catch(e){if(e instanceof i)return null;return F({text:"".concat(s," failed: ").concat(e.message),error:!0,busy:!1}),null}},[]),em=(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(Y.current),a=await eh("/projects/".concat(s,"/agents/spawn"),t,"spawn ".concat(t.name));return await Z(m),(null==a?void 0:a.status)==="done"},[eh,Z,m]),ex=(0,r.useCallback)(async(e,t)=>{let s=encodeURIComponent(e);await eh("/projects/".concat(s,"/agents/").concat(encodeURIComponent(t),"/kill"),void 0,"kill ".concat(t)),await Z(m)},[eh,Z,m]),ep=(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"}),F({text:"agent '".concat(t,"' row deleted"),error:!1,busy:!1}),(null===(a=V.current)||void 0===a?void 0:a.name)===t&&b(null),await Z(m)}catch(e){if(e instanceof i)return;F({text:e.message,error:!0,busy:!1})}},[Z,m]),ej=(0,r.useCallback)(async(e,t)=>{let s=V.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 eh("".concat(a,"/resume"),{answer:e},"resume"):F({text:"Answer saved (agent still paused).",error:!1,busy:!1}),await Z(m),await ee(s.projectId,s.name),!0}catch(e){if(e instanceof i)return!1;return F({text:e.message,error:!0,busy:!1}),!1}},[eh,Z,ee,m]),ev=(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 $(),null!=n.sync_command_id){F({text:"repository '".concat(n.id,"': cloning…"),error:!1,busy:!0});let e=await d.current.trackCommand(n.sync_command_id);e?"done"===e.status?F({text:"repository '".concat(n.id,"' registered and cloned"),error:!1,busy:!1}):F({text:"repository '".concat(n.id,"' registered but the clone failed — ").concat(null!==(t=e.error)&&void 0!==t?t:""),error:!0,busy:!1}):F({text:"repository '".concat(n.id,"' registered; clone still running (see Activity). Is the worker up?"),error:!1,busy:!1})}else F({text:"repository '".concat(n.id,"' registered"),error:!1,busy:!1});if(null!=n.mise_init_command_id){F({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?F({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}):F({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}):F({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 F({text:e.message,error:!0,busy:!1}),!1}},[$]),eg=(0,r.useCallback)(async e=>{await eh("/projects/".concat(encodeURIComponent(e),"/sync"),void 0,"pull ".concat(e))},[eh]),ey=(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}}),F({text:"repository '".concat(e,"' updated"),error:!1,busy:!1}),await $(),!0}catch(e){if(e instanceof i)return!1;return F({text:e.message,error:!0,busy:!1}),!1}},[$]),eb=(0,r.useCallback)(async e=>{try{await d.current.api("/projects/".concat(encodeURIComponent(e)),{method:"DELETE"}),F({text:"repository '".concat(e,"' removed"),error:!1,busy:!1}),g(t=>t===e?"":t),await $()}catch(e){if(e instanceof i)return;F({text:e.message,error:!0,busy:!1})}},[$]),ef=(0,r.useCallback)(async e=>{let t=encodeURIComponent(Y.current);await eh("/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 et(Y.current)},[eh,et]),ek=(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}}),F({text:"git server '".concat(e.hostname,"' added"),error:!1,busy:!1}),await es(),!0}catch(e){if(e instanceof i)return!1;return F({text:e.message,error:!0,busy:!1}),!1}},[es]),ew=(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}),F({text:"git server '".concat(e,"' updated"),error:!1,busy:!1}),await es(),!0}catch(e){if(e instanceof i)return!1;return F({text:e.message,error:!0,busy:!1}),!1}},[es]),eN=(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}}),F({text:"schedule '".concat(t.name_prefix,"' created — first run on the worker's next pass"),error:!1,busy:!1}),await en(),!0}catch(e){if(e instanceof i)return!1;return F({text:e.message,error:!0,busy:!1}),!1}},[en]),e_=(0,r.useCallback)(async(e,t)=>{try{return await d.current.api("/schedules/".concat(e),{method:"PATCH",body:t}),await en(),!0}catch(e){if(e instanceof i)return!1;return F({text:e.message,error:!0,busy:!1}),!1}},[en]),eC=(0,r.useCallback)(async e=>{try{await d.current.api("/schedules/".concat(e),{method:"DELETE"}),F({text:"schedule ".concat(e," removed"),error:!1,busy:!1}),await en()}catch(e){if(e instanceof i)return;F({text:e.message,error:!0,busy:!1})}},[en]),eS=(0,r.useCallback)(async e=>{try{await d.current.api("/hosts/".concat(encodeURIComponent(e)),{method:"DELETE"}),F({text:"git server '".concat(e,"' removed"),error:!1,busy:!1}),await es()}catch(e){if(e instanceof i)return;F({text:e.message,error:!0,busy:!1})}},[es]),eR=(0,r.useCallback)(async()=>{await eh("/poll-ci",void 0,"poll-ci (all projects)"),await ea()},[eh,ea]),eI=(0,r.useCallback)(async()=>{J({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){J({status:"error",url:"",message:"Still starting (see Activity). Is the control worker running?"});return}if("done"!==t.status){J({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){J({status:"error",url:"",message:"No login URL was returned by claude."});return}J({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;J({status:"error",url:"",message:e.message})}},[]),eT=(0,r.useCallback)(async e=>{let t=e.trim();if(!t)return!1;J(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 J(e=>({...e,status:"awaiting",message:"Submit still running (see Activity). Is the control worker running?"})),!1;if("done"===s.status)return J({status:"done",url:"",message:"Claude Code is now logged in on the host — new agents will use this account."}),!0;return J(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 J(t=>({...t,status:"awaiting",message:e.message})),!1}},[]),eA=(0,r.useCallback)(()=>{J({status:"idle",url:"",message:""})},[]),ez=(0,r.useCallback)(async(e,t)=>{try{return await d.current.api("/shared/context/".concat(encodeURIComponent(e)),{method:"PUT",body:{value:t}}),F({text:"shared context '".concat(e,"' set"),error:!1,busy:!1}),await er(),!0}catch(e){if(e instanceof i)return!1;return F({text:e.message,error:!0,busy:!1}),!1}},[er]);return(0,n.jsx)(c.Provider,{value:{section:u,setSection:ei,projects:m,agents:p,selectedProjectId:v,selectProject:ec,selectedRun:y,selectRun:eo,checkmark:f,checkmarkMissing:w,log:_,logOffset:S,pageLog:ed,approvals:I,hosts:A,commands:E,schedules:M,shared:L,cmd:D,lastError:B,loading:q,refresh:eu,spawnAgent:em,killAgent:ex,deleteAgent:ep,submitAnswer:ej,createProject:ev,updateProject:ey,deleteProject:eb,syncProject:eg,submitApproval:ef,createHost:ek,updateHost:ew,deleteHost:eS,createSchedule:eN,updateSchedule:e_,deleteSchedule:eC,pollCi:eR,setSharedKey:ez,claudeLogin:G,startClaudeLogin:eI,submitClaudeCode:eT,resetClaudeLogin:eA},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":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 x(e){return e?e.slice(0,7):"—"}function p(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)(p,{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 v(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 g(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 y(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)(y,{label:t,children:(0,n.jsx)("input",{className:"input",type:l,value:s,placeholder:r,disabled:i,onChange:e=>a(e.target.value)})})}function f(e){let{label:t,value:s,onChange:a,placeholder:r,rows:l=3}=e;return(0,n.jsx)(y,{label:t,children:(0,n.jsx)("textarea",{className:"textarea",value:s,rows:l,placeholder:r,onChange:e=>a(e.target.value)})})}function k(e){let{label:t,value:s,onChange:a,options:r}=e;return(0,n.jsx)(y,{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 w(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 N(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 _(e){let{tone:t="info",children:s}=e;return(0,n.jsx)("div",{className:"callout callout-".concat(t),children:s})}function C(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 S=[{value:"all",label:"All"},{value:"needs",label:"Needs Input"},{value:"working",label:"Working"},{value:"done",label:"Done"}];function R(){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)})].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)(N,{value:e.agents.length,label:"Runs tracked"})}),(0,n.jsx)("div",{className:"stat-cell",children:(0,n.jsx)(N,{value:i,label:"Needs input",accent:!0})}),(0,n.jsx)("div",{className:"stat-cell",children:(0,n.jsx)(N,{value:c,label:"Working"})}),(0,n.jsx)("div",{className:"stat-cell",children:(0,n.jsx)(N,{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)(w,{tabs:S,value:t,onChange:s})]}),(0,n.jsxs)("div",{className:"split-list-scroll",children:[0===a.length&&(0,n.jsx)(_,{tone:"info",children:"No runs match this filter."}),a.map(t=>(0,n.jsx)(I,{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)(A,{}):(0,n.jsx)(T,{})})]})]})}function I(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:function(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")}(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 T(){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 A(){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),v=(null==a?void 0:a.status)==="paused_for_input",y=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)(p,{tone:"info",children:a.role}),(0,n.jsx)("span",{className:"spacer"}),(0,n.jsx)(g,{size:"sm",variant:"secondary",onClick:()=>t.killAgent(s.projectId,s.name),children:"Kill"}),(0,n.jsx)(g,{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)(_,{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)(p,{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)(p,{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)})]})]}),v&&(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)(_,{tone:"danger",children:(null==l?void 0:l.open_question)||"(no question text on the checkmark)"}),(0,n.jsx)(f,{value:i,onChange:c,rows:3,placeholder:"Your answer…"}),(0,n.jsxs)("div",{className:"hstack",children:[(0,n.jsx)(g,{variant:"secondary",disabled:d||!i.trim(),onClick:()=>y(!1),children:"Answer"}),(0,n.jsx)(g,{variant:"primary",disabled:d||!i.trim(),onClick:()=>y(!0),children:"Answer & Resume"})]})]}),(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:x(e.push_sha)}),(0,n.jsx)("td",{children:(0,n.jsx)(p,{tone:u(e.ci_status),children:e.ci_status})})]},e.id))})]})}),(0,n.jsxs)("div",{className:"pager",children:[(0,n.jsx)(g,{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)(g,{size:"sm",variant:"ghost",disabled:t.log.length<100,onClick:()=>t.pageLog(1),children:"Older ›"})]})]})]})]})}let z={mode:"server",git_server:"",repo:"",id:"",root_dir:"",git_remote:"",credential_ref:"",init_mise:!1};function E(){let e=o(),[t,s]=(0,r.useState)(z),[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(z),l(!1)},u=async()=>{(a?await e.updateProject(t.id,t):await e.createProject(t))&&d()},h=e=>{var t,a;s({...z,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)},x=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)(v,{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)(w,{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)(k,{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)(g,{variant:"primary",disabled:e.cmd.busy||!x,onClick:u,children:a?"Save changes":"server"===t.mode?"Add & pull":"Register"}),a&&(0,n.jsx)(g,{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)(v,{children:[(0,n.jsxs)("div",{className:"card-head",children:[(0,n.jsx)("span",{className:"card-title",children:t.id}),(0,n.jsxs)(p,{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)(g,{size:"sm",variant:"secondary",onClick:()=>e.syncProject(t.id),children:"Pull now"}),(0,n.jsx)(g,{size:"sm",variant:"secondary",onClick:()=>h(t),children:"Edit"}),(0,n.jsx)(g,{size:"sm",variant:"danger",onClick:()=>e.deleteProject(t.id),children:"Remove"})]})]})]},t.id)})]})]})}let P=[{value:"",label:"Role — none"},{value:"junior",label:"junior"},{value:"senior",label:"senior"},{value:"deploy",label:"deploy"}],M=[{value:"worktree",label:"git worktree on branch"},{value:"subdir",label:"subdir under root"}],O={name:"",role:"",placement:"worktree",worktree:"",subdir:"",task:""};function L(){let e=o(),[t,s]=(0,r.useState)(O),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(O)};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)(k,{label:"Repository",value:e.selectedProjectId,onChange:e.selectProject,options:a})})}),(0,n.jsxs)(v,{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)(k,{label:"Role",value:t.role,onChange:e=>s({...t,role:e}),options:P}),(0,n.jsx)(k,{label:"Placement",value:t.placement,onChange:e=>s({...t,placement:e}),options:M}),"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)(f,{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)(g,{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=>(0,n.jsxs)("tr",{children:[(0,n.jsx)("td",{className:"mono",children:t.name}),(0,n.jsx)("td",{children:t.role?(0,n.jsx)(p,{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)(g,{size:"sm",variant:"ghost",onClick:()=>e.selectRun(t.project_id,t.name),children:"Open"}),(0,n.jsx)(g,{size:"sm",variant:"secondary",onClick:()=>e.killAgent(t.project_id,t.name),children:"Kill"}),(0,n.jsx)(g,{size:"sm",variant:"danger",onClick:()=>e.deleteAgent(t.project_id,t.name),children:"Delete"})]})})]},t.id))})]})})]})})]})}let U=[{value:"",label:"Role — none"},{value:"junior",label:"junior"},{value:"senior",label:"senior"},{value:"deploy",label:"deploy"}],D=[{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"}],F={name_prefix:"",task:"",interval:"3600",role:""};function B(){let e=o(),[t,s]=(0,r.useState)(F),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(F)};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)(k,{label:"Repository",value:e.selectedProjectId,onChange:e.selectProject,options:a})})}),(0,n.jsxs)(v,{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)(k,{label:"Interval",value:t.interval,onChange:e=>s({...t,interval:e}),options:D}),(0,n.jsx)(k,{label:"Role",value:t.role,onChange:e=>s({...t,role:e}),options:U})]}),(0,n.jsx)("div",{className:"mt14",children:(0,n.jsx)(f,{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)(g,{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)(C,{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)(p,{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=D.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)(g,{size:"sm",variant:"danger",onClick:()=>e.deleteSchedule(t.id),children:"Delete"})})]},t.id))})]})})]})})]})}let H=[{value:"approved",label:"approve"},{value:"rejected",label:"reject"}],q={branch:"",status:"approved",agent_name:"",sha:"",note:""};function W(){let e=o(),[t,s]=(0,r.useState)(q),a=(0,r.useMemo)(()=>e.projects.map(e=>({value:e.id,label:e.id})),[e.projects]),l=async()=>{await e.submitApproval(t),s(q)};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)(k,{label:"Repository",value:e.selectedProjectId,onChange:e.selectProject,options:a})})}),(0,n.jsxs)(v,{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)(k,{label:"Verdict",value:t.status,onChange:e=>s({...t,status:e}),options:H}),(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)(g,{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:x(e.approved_sha)}),(0,n.jsx)("td",{children:e.note||"—"})]},e.id))})]})})]})})]})}let G=[{value:"github",label:"github"},{value:"gitlab",label:"gitlab"},{value:"gitea",label:"gitea"},{value:"forgejo",label:"forgejo"},{value:"bitbucket",label:"bitbucket"}],J={hostname:"",forge_type:"github",token_env_var:"",base_url:"",token:"",generate_ssh_key:!0};function K(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)(g,{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 Y(){let e=o(),[t,s]=(0,r.useState)(J),[a,l]=(0,r.useState)(!1),i=()=>{s(J),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)(v,{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)(k,{label:"Type",value:t.forge_type,onChange:e=>s({...t,forge_type:e}),options:G}),(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)(g,{variant:"primary",disabled:e.cmd.busy||!t.hostname.trim(),onClick:c,children:a?"Save changes":"Add server"}),a&&(0,n.jsx)(g,{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)(v,{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)(p,{tone:"info",children:t.forge_type}),(0,n.jsx)(p,{tone:t.has_token?"success":"neutral",children:t.has_token?"token stored":"no token"}),(0,n.jsx)(p,{tone:t.ssh_public_key?"success":"neutral",children:t.ssh_public_key?"ssh key":"no ssh key"}),(0,n.jsx)(g,{size:"sm",variant:"secondary",onClick:()=>d(t),children:"Edit"}),(0,n.jsx)(g,{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)(K,{value:t.ssh_public_key})]},t.hostname))]})]})}function V(){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)(g,{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 Q(){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)(v,{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)(g,{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 X(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 $(){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=X("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)(_,{tone:"error"===t?"danger":"done"===t?"success":"info",children:a}),"done"===t?(0,n.jsx)("div",{children:(0,n.jsx)(g,{variant:"secondary",onClick:e.resetClaudeLogin,children:"Log in again"})}):u?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(_,{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)(g,{variant:"secondary",disabled:!s,onClick:()=>{s&&(c.current=X(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)(g,{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)(g,{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)(g,{variant:"primary",disabled:d,onClick:h,children:"starting"===t?"Starting…":"Log in to Claude"}),"error"===t&&(0,n.jsx)(g,{variant:"ghost",disabled:d,onClick:h,children:"Retry"})]})]})]})}let Z=[{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 ee(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"]}),Z.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)(R,{}):(0,n.jsxs)("div",{className:"main-scroll",children:["repositories"===s.section&&(0,n.jsx)(E,{}),"agents"===s.section&&(0,n.jsx)(L,{}),"schedules"===s.section&&(0,n.jsx)(B,{}),"approvals"===s.section&&(0,n.jsx)(W,{}),"servers"===s.section&&(0,n.jsx)(Y,{}),"activity"===s.section&&(0,n.jsx)(V,{}),"shared"===s.section&&(0,n.jsx)(Q,{}),"login"===s.section&&(0,n.jsx)($,{})]})]})]})}function et(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 es="handler_token";function ea(){let[e,t]=(0,r.useState)(null),[s,a]=(0,r.useState)("");(0,r.useEffect)(()=>{let e=window.localStorage.getItem(es);e&&t(e)},[]);let l=(0,r.useCallback)(e=>{window.localStorage.setItem(es,e),a(""),t(e)},[]),i=(0,r.useCallback)(()=>{window.localStorage.removeItem(es),t(null)},[]),c=(0,r.useCallback)(()=>{window.localStorage.removeItem(es),t(null),a("Invalid token — please try again.")},[]);return e?(0,n.jsx)(d,{token:e,onUnauthorized:c,children:(0,n.jsx)(ee,{onSignOut:i})}):(0,n.jsx)(et,{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,x]=(0,r.useState)([]),[p,j]=(0,r.useState)([]),[v,g]=(0,r.useState)(""),[y,b]=(0,r.useState)(null),[f,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,z]=(0,r.useState)([]),[E,P]=(0,r.useState)([]),[M,O]=(0,r.useState)([]),[L,U]=(0,r.useState)({log:[],context:[]}),[D,F]=(0,r.useState)({text:"",error:!1,busy:!1}),[B,H]=(0,r.useState)(""),[q,W]=(0,r.useState)(!0),[G,J]=(0,r.useState)({status:"idle",url:"",message:""}),K=(0,r.useRef)(u);K.current=u;let Y=(0,r.useRef)(v);Y.current=v;let V=(0,r.useRef)(y);V.current=y;let Q=(0,r.useRef)(S);Q.current=S;let X=e=>{e instanceof i||H(e.message)},$=(0,r.useCallback)(async()=>{try{let e=await d.current.api("/projects");x(e),H(""),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){X(e)}},[]),Z=(0,r.useCallback)(async e=>{try{let t=await Promise.all(e.map(e=>d.current.api("/projects/".concat(encodeURIComponent(e.id),"/agents")).catch(()=>[])));j(t.flat())}catch(e){X(e)}},[]),ee=(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)):X(e)}try{let e=await d.current.api("".concat(s,"/log?limit=").concat(100,"&offset=").concat(Q.current));C(e)}catch(e){X(e)}},[]),et=(0,r.useCallback)(async e=>{if(!e){T([]);return}try{T(await d.current.api("/projects/".concat(encodeURIComponent(e),"/approvals")))}catch(e){X(e)}},[]),es=(0,r.useCallback)(async()=>{try{z(await d.current.api("/hosts"))}catch(e){X(e)}},[]),ea=(0,r.useCallback)(async()=>{try{P(await d.current.api("/commands?limit=50"))}catch(e){X(e)}},[]),en=(0,r.useCallback)(async()=>{try{O(await d.current.api("/schedules"))}catch(e){X(e)}},[]),er=(0,r.useCallback)(async()=>{try{let[e,t]=await Promise.all([d.current.api("/shared/log"),d.current.api("/shared/context")]);U({log:e,context:t})}catch(e){X(e)}},[]),el=(0,r.useCallback)(async()=>{let e=await d.current.api("/projects").catch(e=>(X(e),null));e&&(x(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 Z(e));let t=K.current,s=V.current;s&&await ee(s.projectId,s.name),"approvals"===t&&await et(Y.current),"servers"===t&&await es(),"activity"===t&&await ea(),"schedules"===t&&await en(),"shared"===t&&await er()},[Z,ee,et,es,ea,en,er]);(0,r.useEffect)(()=>{let e=!0;(async()=>{W(!0),await el(),e&&W(!1)})();let t=setInterval(()=>{document.hidden||el()},5e3);return()=>{e=!1,clearInterval(t)}},[el]);let ei=(0,r.useCallback)(e=>{h(e),F({text:"",error:!1,busy:!1}),"approvals"===e&&et(Y.current),"servers"===e&&es(),"activity"===e&&ea(),"schedules"===e&&en(),"shared"===e&&er()},[et,es,ea,en,er]),ec=(0,r.useCallback)(e=>{g(e),"approvals"===K.current&&et(e)},[et]),eo=(0,r.useCallback)((e,t)=>{b({projectId:e,name:t}),R(0),Q.current=0,k(null),N(!1),C([]),ee(e,t)},[ee]),ed=(0,r.useCallback)(e=>{let t=Math.max(0,S+100*e);if(t===S)return;R(t),Q.current=t;let s=V.current;s&&ee(s.projectId,s.name)},[S,ee]),eu=(0,r.useCallback)(()=>{el()},[el]),eh=(0,r.useCallback)(async(e,t,s)=>{F({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 F({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 F({text:"".concat(s," ").concat(r?"done":"failed").concat(l?" — "+l:""),error:!r,busy:!1}),n}catch(e){if(e instanceof i)return null;return F({text:"".concat(s," failed: ").concat(e.message),error:!0,busy:!1}),null}},[]),em=(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(Y.current),a=await eh("/projects/".concat(s,"/agents/spawn"),t,"spawn ".concat(t.name));return await Z(m),(null==a?void 0:a.status)==="done"},[eh,Z,m]),ex=(0,r.useCallback)(async(e,t)=>{let s=encodeURIComponent(e);await eh("/projects/".concat(s,"/agents/").concat(encodeURIComponent(t),"/kill"),void 0,"kill ".concat(t)),await Z(m)},[eh,Z,m]),ep=(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"}),F({text:"agent '".concat(t,"' row deleted"),error:!1,busy:!1}),(null===(a=V.current)||void 0===a?void 0:a.name)===t&&b(null),await Z(m)}catch(e){if(e instanceof i)return;F({text:e.message,error:!0,busy:!1})}},[Z,m]),ej=(0,r.useCallback)(async(e,t)=>{let s=V.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 eh("".concat(a,"/resume"),{answer:e},"resume"):F({text:"Answer saved (agent still paused).",error:!1,busy:!1}),await Z(m),await ee(s.projectId,s.name),!0}catch(e){if(e instanceof i)return!1;return F({text:e.message,error:!0,busy:!1}),!1}},[eh,Z,ee,m]),ev=(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 $(),null!=n.sync_command_id){F({text:"repository '".concat(n.id,"': cloning…"),error:!1,busy:!0});let e=await d.current.trackCommand(n.sync_command_id);e?"done"===e.status?F({text:"repository '".concat(n.id,"' registered and cloned"),error:!1,busy:!1}):F({text:"repository '".concat(n.id,"' registered but the clone failed — ").concat(null!==(t=e.error)&&void 0!==t?t:""),error:!0,busy:!1}):F({text:"repository '".concat(n.id,"' registered; clone still running (see Activity). Is the worker up?"),error:!1,busy:!1})}else F({text:"repository '".concat(n.id,"' registered"),error:!1,busy:!1});if(null!=n.mise_init_command_id){F({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?F({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}):F({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}):F({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 F({text:e.message,error:!0,busy:!1}),!1}},[$]),eg=(0,r.useCallback)(async e=>{await eh("/projects/".concat(encodeURIComponent(e),"/sync"),void 0,"pull ".concat(e))},[eh]),ey=(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}}),F({text:"repository '".concat(e,"' updated"),error:!1,busy:!1}),await $(),!0}catch(e){if(e instanceof i)return!1;return F({text:e.message,error:!0,busy:!1}),!1}},[$]),eb=(0,r.useCallback)(async e=>{try{await d.current.api("/projects/".concat(encodeURIComponent(e)),{method:"DELETE"}),F({text:"repository '".concat(e,"' removed"),error:!1,busy:!1}),g(t=>t===e?"":t),await $()}catch(e){if(e instanceof i)return;F({text:e.message,error:!0,busy:!1})}},[$]),ef=(0,r.useCallback)(async e=>{let t=encodeURIComponent(Y.current);await eh("/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 et(Y.current)},[eh,et]),ek=(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}}),F({text:"git server '".concat(e.hostname,"' added"),error:!1,busy:!1}),await es(),!0}catch(e){if(e instanceof i)return!1;return F({text:e.message,error:!0,busy:!1}),!1}},[es]),ew=(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}),F({text:"git server '".concat(e,"' updated"),error:!1,busy:!1}),await es(),!0}catch(e){if(e instanceof i)return!1;return F({text:e.message,error:!0,busy:!1}),!1}},[es]),eN=(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}}),F({text:"schedule '".concat(t.name_prefix,"' created — first run on the worker's next pass"),error:!1,busy:!1}),await en(),!0}catch(e){if(e instanceof i)return!1;return F({text:e.message,error:!0,busy:!1}),!1}},[en]),e_=(0,r.useCallback)(async(e,t)=>{try{return await d.current.api("/schedules/".concat(e),{method:"PATCH",body:t}),await en(),!0}catch(e){if(e instanceof i)return!1;return F({text:e.message,error:!0,busy:!1}),!1}},[en]),eC=(0,r.useCallback)(async e=>{try{await d.current.api("/schedules/".concat(e),{method:"DELETE"}),F({text:"schedule ".concat(e," removed"),error:!1,busy:!1}),await en()}catch(e){if(e instanceof i)return;F({text:e.message,error:!0,busy:!1})}},[en]),eS=(0,r.useCallback)(async e=>{try{await d.current.api("/hosts/".concat(encodeURIComponent(e)),{method:"DELETE"}),F({text:"git server '".concat(e,"' removed"),error:!1,busy:!1}),await es()}catch(e){if(e instanceof i)return;F({text:e.message,error:!0,busy:!1})}},[es]),eR=(0,r.useCallback)(async()=>{await eh("/poll-ci",void 0,"poll-ci (all projects)"),await ea()},[eh,ea]),eI=(0,r.useCallback)(async()=>{J({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){J({status:"error",url:"",message:"Still starting (see Activity). Is the control worker running?"});return}if("done"!==t.status){J({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){J({status:"error",url:"",message:"No login URL was returned by claude."});return}J({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;J({status:"error",url:"",message:e.message})}},[]),eT=(0,r.useCallback)(async e=>{let t=e.trim();if(!t)return!1;J(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 J(e=>({...e,status:"awaiting",message:"Submit still running (see Activity). Is the control worker running?"})),!1;if("done"===s.status)return J({status:"done",url:"",message:"Claude Code is now logged in on the host — new agents will use this account."}),!0;return J(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 J(t=>({...t,status:"awaiting",message:e.message})),!1}},[]),eA=(0,r.useCallback)(()=>{J({status:"idle",url:"",message:""})},[]),ez=(0,r.useCallback)(async(e,t)=>{try{return await d.current.api("/shared/context/".concat(encodeURIComponent(e)),{method:"PUT",body:{value:t}}),F({text:"shared context '".concat(e,"' set"),error:!1,busy:!1}),await er(),!0}catch(e){if(e instanceof i)return!1;return F({text:e.message,error:!0,busy:!1}),!1}},[er]);return(0,n.jsx)(c.Provider,{value:{section:u,setSection:ei,projects:m,agents:p,selectedProjectId:v,selectProject:ec,selectedRun:y,selectRun:eo,checkmark:f,checkmarkMissing:w,log:_,logOffset:S,pageLog:ed,approvals:I,hosts:A,commands:E,schedules:M,shared:L,cmd:D,lastError:B,loading:q,refresh:eu,spawnAgent:em,killAgent:ex,deleteAgent:ep,submitAnswer:ej,createProject:ev,updateProject:ey,deleteProject:eb,syncProject:eg,submitApproval:ef,createHost:ek,updateHost:ew,deleteHost:eS,createSchedule:eN,updateSchedule:e_,deleteSchedule:eC,pollCi:eR,setSharedKey:ez,claudeLogin:G,startClaudeLogin:eI,submitClaudeCode:eT,resetClaudeLogin:eA},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":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 x(e){return e?e.slice(0,7):"—"}function p(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 j(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 v(e){let{status:t}=e;return(0,n.jsx)(j,{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 b(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 f(e){let{label:t,value:s,onChange:a,placeholder:r,type:l="text",disabled:i}=e;return(0,n.jsx)(b,{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)(b,{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)(b,{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"}];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)})].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:p(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)(v,{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),p=(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)(v,{status:null==a?void 0:a.status}),(null==a?void 0:a.role)&&(0,n.jsx)(j,{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)(v,{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)(j,{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)(j,{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)})]})]}),p&&(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"})]})]}),(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)(v,{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:x(e.push_sha)}),(0,n.jsx)("td",{children:(0,n.jsx)(j,{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 ›"})]})]})]})]})}let E={mode:"server",git_server:"",repo:"",id:"",root_dir:"",git_remote:"",credential_ref:"",init_mise:!1};function P(){let e=o(),[t,s]=(0,r.useState)(E),[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(E),l(!1)},u=async()=>{(a?await e.updateProject(t.id,t):await e.createProject(t))&&d()},h=e=>{var t,a;s({...E,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)},x=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)(f,{label:"ID / slug",value:t.id,onChange:e=>s({...t,id:e}),placeholder:"leeworks-api",disabled:a}),(0,n.jsx)(f,{label:"Root dir",value:t.root_dir,onChange:e=>s({...t,root_dir:e}),placeholder:"/var/lib/handler/projects/leeworks"}),(0,n.jsx)(f,{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)(f,{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)(f,{label:"Repository (owner/name)",value:t.repo,onChange:e=>s({...t,repo:e}),placeholder:"me/coolproj"}),(0,n.jsx)(f,{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||!x,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)(j,{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"}],O=[{value:"worktree",label:"git worktree on branch"},{value:"subdir",label:"subdir under root"}],L={name:"",role:"",placement:"worktree",worktree:"",subdir:"",task:""};function U(){let e=o(),[t,s]=(0,r.useState)(L),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(L)};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)(f,{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:O}),"worktree"===t.placement?(0,n.jsx)(f,{label:"Branch",value:t.worktree,onChange:e=>s({...t,worktree:e}),placeholder:"feat/auth"}):(0,n.jsx)(f,{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)(j,{tone:"info",children:t.role}):"—"}),(0,n.jsx)("td",{children:(0,n.jsx)(v,{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&&(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:["live output",t.output_at?" \xb7 ".concat(p(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 D=[{value:"",label:"Role — none"},{value:"junior",label:"junior"},{value:"senior",label:"senior"},{value:"deploy",label:"deploy"}],F=[{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"}],B={name_prefix:"",task:"",interval:"3600",role:""};function H(){let e=o(),[t,s]=(0,r.useState)(B),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(B)};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)(f,{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:F}),(0,n.jsx)(w,{label:"Role",value:t.role,onChange:e=>s({...t,role:e}),options:D})]}),(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)(j,{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=F.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"}],W={branch:"",status:"approved",agent_name:"",sha:"",note:""};function G(){let e=o(),[t,s]=(0,r.useState)(W),a=(0,r.useMemo)(()=>e.projects.map(e=>({value:e.id,label:e.id})),[e.projects]),l=async()=>{await e.submitApproval(t),s(W)};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)(f,{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)(f,{label:"Agent",value:t.agent_name,onChange:e=>s({...t,agent_name:e}),placeholder:"reads its HEAD (optional)"}),(0,n.jsx)(f,{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)(f,{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)(v,{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:x(e.approved_sha)}),(0,n.jsx)("td",{children:e.note||"—"})]},e.id))})]})})]})})]})}let J=[{value:"github",label:"github"},{value:"gitlab",label:"gitlab"},{value:"gitea",label:"gitea"},{value:"forgejo",label:"forgejo"},{value:"bitbucket",label:"bitbucket"}],K={hostname:"",forge_type:"github",token_env_var:"",base_url:"",token:"",generate_ssh_key:!0};function Y(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 V(){let e=o(),[t,s]=(0,r.useState)(K),[a,l]=(0,r.useState)(!1),i=()=>{s(K),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)(f,{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:J}),(0,n.jsx)(f,{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)(f,{label:"Base URL (optional)",value:t.base_url,onChange:e=>s({...t,base_url:e}),placeholder:"https://git.corp.internal:8443"}),(0,n.jsx)(f,{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)(j,{tone:"info",children:t.forge_type}),(0,n.jsx)(j,{tone:t.has_token?"success":"neutral",children:t.has_token?"token stored":"no token"}),(0,n.jsx)(j,{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)(Y,{value:t.ssh_public_key})]},t.hostname))]})]})}function Q(){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)(v,{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 X(){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)(v,{status:e.status})}),(0,n.jsx)("td",{children:e.summary||"—"}),(0,n.jsx)("td",{children:(0,n.jsx)(v,{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)(f,{label:"Key",value:t,onChange:s,placeholder:"key"}),(0,n.jsx)(f,{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 $(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 Z(){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=$("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=$(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)(f,{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 ee=[{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 et(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"]}),ee.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)(P,{}),"agents"===s.section&&(0,n.jsx)(U,{}),"schedules"===s.section&&(0,n.jsx)(H,{}),"approvals"===s.section&&(0,n.jsx)(G,{}),"servers"===s.section&&(0,n.jsx)(V,{}),"activity"===s.section&&(0,n.jsx)(Q,{}),"shared"===s.section&&(0,n.jsx)(X,{}),"login"===s.section&&(0,n.jsx)(Z,{})]})]})]})}function es(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 ea="handler_token";function en(){let[e,t]=(0,r.useState)(null),[s,a]=(0,r.useState)("");(0,r.useEffect)(()=>{let e=window.localStorage.getItem(ea);e&&t(e)},[]);let l=(0,r.useCallback)(e=>{window.localStorage.setItem(ea,e),a(""),t(e)},[]),i=(0,r.useCallback)(()=>{window.localStorage.removeItem(ea),t(null)},[]),c=(0,r.useCallback)(()=>{window.localStorage.removeItem(ea),t(null),a("Invalid token — please try again.")},[]);return e?(0,n.jsx)(d,{token:e,onUnauthorized:c,children:(0,n.jsx)(et,{onSignOut:i})}):(0,n.jsx)(es,{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
\ No newline at end of file
+Handler · Claude Activity
\ No newline at end of file
diff --git a/src/handler/api/static/index.txt b/src/handler/api/static/index.txt
index 586a822..22a12d7 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-4aea2dcf515b45ea.js"],"default",1]
+3:I[9859,["931","static/chunks/app/page-a511d608db773c67.js"],"default",1]
4:I[4707,[],""]
5:I[6423,[],""]
-0:["j4wMy4OdPdBURLtWQmeHO",[[["",{"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:["tQnVLKX-Dr8H3frHzPrCa",[[["",{"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/control/claude_config.py b/src/handler/control/claude_config.py
new file mode 100644
index 0000000..eaa13e2
--- /dev/null
+++ b/src/handler/control/claude_config.py
@@ -0,0 +1,78 @@
+"""Seed the user-level Claude Code config so a spawned agent never blocks on first-run
+onboarding.
+
+A freshly-installed ``claude`` opens an interactive setup — the theme picker, then a
+"do you trust the files in this folder?" prompt — before it reaches the REPL. A spawned
+agent runs detached in tmux with no human at the TTY, so it would sit on the theme picker
+forever (the observed wedge: ``agents.status='working'`` while nothing happens). The
+``/login`` flow drives those screens by hand; a spawned agent can't, so we mark onboarding
+complete on disk *before* launching claude.
+
+The write is a merge, never a clobber: ``~/.claude.json`` also holds the ``oauthAccount``
+the login flow wrote, and losing that would log the agent out. We only fill in the keys
+that gate first-run and leave everything else — including a theme the operator already
+chose — untouched.
+"""
+
+from __future__ import annotations
+
+import json
+import os
+
+
+def _home() -> str:
+ return os.path.expanduser("~") or "/tmp"
+
+
+def config_path(home: str | None = None) -> str:
+ return os.path.join(home or _home(), ".claude.json")
+
+
+def _load(path: str) -> dict:
+ if not os.path.exists(path):
+ return {}
+ try:
+ with open(path) as fh:
+ data = json.load(fh)
+ except (OSError, json.JSONDecodeError):
+ return {}
+ return data if isinstance(data, dict) else {}
+
+
+def _atomic_write(path: str, data: dict) -> None:
+ os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
+ tmp = f"{path}.handler.tmp"
+ with open(tmp, "w") as fh:
+ json.dump(data, fh, indent=2)
+ os.replace(tmp, path)
+
+
+def ensure_onboarded(working_dir: str | None = None, home: str | None = None) -> str:
+ """Mark Claude Code onboarding complete in ``~/.claude.json`` and (when given) trust
+ ``working_dir``, so a detached agent boots straight to the REPL. Returns the path.
+
+ Idempotent and merge-only: ``hasCompletedOnboarding`` is forced true (a stale ``false``
+ from a half-finished run would otherwise re-trigger setup), while ``theme`` is only
+ filled in when absent so an operator's chosen theme survives.
+ """
+ path = config_path(home)
+ data = _load(path)
+
+ data["hasCompletedOnboarding"] = True
+ data.setdefault("theme", "dark")
+
+ if working_dir:
+ projects = data.get("projects")
+ if not isinstance(projects, dict):
+ projects = {}
+ entry = projects.get(working_dir)
+ if not isinstance(entry, dict):
+ entry = {}
+ # The per-directory trust prompt claude shows on first entry into a new folder.
+ entry["hasTrustDialogAccepted"] = True
+ entry.setdefault("hasCompletedProjectOnboarding", True)
+ projects[working_dir] = entry
+ data["projects"] = projects
+
+ _atomic_write(path, data)
+ return path
diff --git a/src/handler/control/mise.py b/src/handler/control/mise.py
index 6172e51..937a1b8 100644
--- a/src/handler/control/mise.py
+++ b/src/handler/control/mise.py
@@ -1,9 +1,13 @@
-"""The ``.mise.toml`` gate — one source of truth for "does this project define the
-canonical ``[tasks.test]`` task".
+"""The mise-config gate — one source of truth for "does this project define the canonical
+``[tasks.test]`` task".
Shared by the spawn gate (``control.spawn.require_test_task``, which refuses to launch an
agent against a project with no test task) and the mise-init Stop hook (which refuses to
let the bootstrap agent finish until the task exists and is committed + pushed).
+
+mise reads several config filenames, not just ``.mise.toml`` — a repo may ship ``mise.toml``
+(no leading dot) or keep config under ``.config/mise/`` — so the gate accepts any of them
+and treats a ``[tasks.test]`` in *any* present config as satisfying the requirement.
"""
from __future__ import annotations
@@ -11,19 +15,44 @@ from __future__ import annotations
import os
import tomllib
+# The config filenames mise itself looks for, in the rough precedence order it uses. A
+# project only needs one; we scan all present ones for the test task.
+CONFIG_NAMES = (
+ "mise.toml",
+ ".mise.toml",
+ "mise.local.toml",
+ ".mise.local.toml",
+ os.path.join(".config", "mise.toml"),
+ os.path.join(".config", "mise", "config.toml"),
+)
-def mise_path(working_dir: str) -> str:
- return os.path.join(working_dir, ".mise.toml")
+
+def config_paths(working_dir: str) -> list[str]:
+ return [os.path.join(working_dir, name) for name in CONFIG_NAMES]
+
+
+def existing_config(working_dir: str) -> str | None:
+ """Path to the first present mise config file under ``working_dir``, else ``None``."""
+ for path in config_paths(working_dir):
+ if os.path.exists(path):
+ return path
+ return None
+
+
+def has_config(working_dir: str) -> bool:
+ return existing_config(working_dir) is not None
def has_test_task(working_dir: str) -> bool:
- """True when ``.mise.toml`` exists and defines a ``[tasks.test]`` task."""
- path = mise_path(working_dir)
- if not os.path.exists(path):
- return False
- try:
- with open(path, "rb") as fh:
- data = tomllib.load(fh)
- except (OSError, tomllib.TOMLDecodeError):
- return False
- return "test" in (data.get("tasks") or {})
+ """True when any present mise config defines a ``[tasks.test]`` task."""
+ for path in config_paths(working_dir):
+ if not os.path.exists(path):
+ continue
+ try:
+ with open(path, "rb") as fh:
+ data = tomllib.load(fh)
+ except (OSError, tomllib.TOMLDecodeError):
+ continue
+ if "test" in (data.get("tasks") or {}):
+ return True
+ return False
diff --git a/src/handler/control/spawn.py b/src/handler/control/spawn.py
index 2bfe584..61a056a 100644
--- a/src/handler/control/spawn.py
+++ b/src/handler/control/spawn.py
@@ -14,7 +14,17 @@ import os
from ..config import get_settings
from ..db import repository as repo
from ..db.engine import connection
-from . import credentials, forge, gitops, mise, reposync, settings_gen, tmux, worktree
+from . import (
+ claude_config,
+ credentials,
+ forge,
+ gitops,
+ mise,
+ reposync,
+ settings_gen,
+ tmux,
+ worktree,
+)
class SpawnError(Exception):
@@ -22,15 +32,15 @@ class SpawnError(Exception):
def require_test_task(working_dir: str) -> None:
- """Hard gate: refuse to spawn unless ``.mise.toml`` defines ``[tasks.test]``."""
- if not os.path.exists(mise.mise_path(working_dir)):
+ """Hard gate: refuse to spawn unless a mise config defines ``[tasks.test]``."""
+ if not mise.has_config(working_dir):
raise SpawnError(
- f"no .mise.toml in {working_dir}: a project must define a [tasks.test] task "
- "before an agent can run against it"
+ f"no mise config (mise.toml / .mise.toml) in {working_dir}: a project must "
+ "define a [tasks.test] task before an agent can run against it"
)
if not mise.has_test_task(working_dir):
raise SpawnError(
- f".mise.toml in {working_dir} has no [tasks.test]: the verification gate "
+ f"mise config in {working_dir} has no [tasks.test]: the verification gate "
"requires a canonical test task"
)
@@ -163,6 +173,11 @@ def spawn(
# touches forge and the base image is the real pin (README 3.6, Phase 2).
forge_note = _check_forge_version(working_dir)
+ # Mark Claude Code onboarding complete + trust the working dir before launching, so the
+ # detached agent boots straight to the REPL instead of wedging on the first-run theme
+ # picker / trust prompt with no human at the tmux TTY to answer it.
+ claude_config.ensure_onboarded(working_dir)
+
session = tmux.session_name(project_id, name)
command = _claude_command(task, settings_path)
tmux.new_session(session, cwd=working_dir, command=command, env=env)
diff --git a/src/handler/control/worker.py b/src/handler/control/worker.py
index 9551357..2adc1bd 100644
--- a/src/handler/control/worker.py
+++ b/src/handler/control/worker.py
@@ -20,7 +20,7 @@ from datetime import UTC, datetime, timedelta
from ..db import repository as repo
from ..db.engine import connection
-from . import gitops, login, poller, reposync, skills_gen, spawn
+from . import gitops, login, poller, reposync, skills_gen, spawn, tmux
class CommandError(Exception):
@@ -313,6 +313,43 @@ def fire_due_schedules(now: datetime | None = None) -> int:
return fired
+# How many trailing pane lines to snapshot — enough to show the current screen (a menu, a
+# prompt, the tail of the last command) without bloating the row.
+_PANE_TAIL_LINES = 40
+
+
+def _pane_tail(pane: str, lines: int = _PANE_TAIL_LINES) -> str:
+ """The last ``lines`` of a captured pane, trailing blank lines trimmed so an idle
+ screen doesn't store as a wall of whitespace."""
+ rows = (pane or "").splitlines()
+ while rows and not rows[-1].strip():
+ rows.pop()
+ return "\n".join(rows[-lines:])
+
+
+def capture_agent_output() -> int:
+ """Snapshot each working agent's live tmux pane tail into the DB.
+
+ The tmux socket lives only in the control container, so this is the one channel the
+ API/UI have onto what a running — or wedged — agent is actually doing: an agent stuck
+ on claude's first-run theme picker surfaces as that screen instead of a misleading
+ green 'working'. A missing session is skipped (its process is gone). Returns the count
+ updated.
+ """
+ with connection() as conn:
+ working = repo.list_agents_by_status(conn, "working")
+ updated = 0
+ for agent in working:
+ session = tmux.session_name(agent["project_id"], agent["name"])
+ if not tmux.has_session(session):
+ continue
+ tail = _pane_tail(tmux.capture_pane(session))
+ with connection() as conn:
+ repo.update_agent_output(conn, agent["id"], tail)
+ updated += 1
+ return updated
+
+
def drain(worker_id: str, limit: int | None = None) -> int:
"""Claim and run queued commands until the queue is empty (or ``limit`` reached).
@@ -335,15 +372,18 @@ def run(
worker_id: str | None = None,
poll_interval: float = 2.0,
ci_interval: float = 30.0,
+ capture_interval: float = 2.0,
iterations: int | None = None,
) -> None:
- """The control-container main loop: drain the command queue + sweep CI periodically.
+ """The control-container main loop: drain the command queue, snapshot live agent
+ output, and sweep CI periodically.
``iterations`` bounds the loop for tests; production runs unbounded. Sleeps
``poll_interval`` only when a pass found no commands, so bursts drain promptly.
"""
worker_id = worker_id or f"worker-{os.getpid()}"
last_ci = 0.0
+ last_capture = 0.0
count = 0
while iterations is None or count < iterations:
try:
@@ -352,6 +392,12 @@ def run(
pass
did_work = drain(worker_id) > 0
now = time.monotonic()
+ if capture_interval > 0 and now - last_capture >= capture_interval:
+ try:
+ capture_agent_output()
+ except Exception: # noqa: BLE001 - a capture hiccup must not kill the worker
+ pass
+ last_capture = now
if ci_interval > 0 and now - last_ci >= ci_interval:
try:
poller.sweep()
diff --git a/src/handler/db/repository.py b/src/handler/db/repository.py
index da3fd4b..c737be0 100644
--- a/src/handler/db/repository.py
+++ b/src/handler/db/repository.py
@@ -219,6 +219,23 @@ def set_agent_status(conn: Connection, agent_id: int, status: str) -> None:
conn.execute(agents.update().where(agents.c.id == agent_id).values(status=status))
+def list_agents_by_status(conn: Connection, status: str) -> list[dict]:
+ """Every agent in a given status, across all projects (the worker's capture input)."""
+ rows = conn.execute(
+ select(agents).where(agents.c.status == status).order_by(agents.c.id)
+ ).all()
+ return [dict(r._mapping) for r in rows]
+
+
+def update_agent_output(conn: Connection, agent_id: int, output: str) -> None:
+ """Store the latest tmux pane-tail snapshot for an agent (worker poll loop)."""
+ conn.execute(
+ agents.update()
+ .where(agents.c.id == agent_id)
+ .values(last_output=output, output_at=_now())
+ )
+
+
def insert_log_entry(conn: Connection, agent_id: int, status: str, **fields: Any) -> int:
values = {"agent_id": agent_id, "status": status, "created_at": _now(), **fields}
result = conn.execute(log_entries.insert().values(**values))
diff --git a/src/handler/db/tables.py b/src/handler/db/tables.py
index 193ffeb..3e62f04 100644
--- a/src/handler/db/tables.py
+++ b/src/handler/db/tables.py
@@ -84,6 +84,11 @@ agents = Table(
# Optional workflow role (junior | senior | deploy) — informational, drives which
# forge skill an agent follows; the approval gate keys on identity, not role.
Column("role", String),
+ # A periodic snapshot of the agent's live tmux pane tail (last ~40 lines), refreshed by
+ # the control worker's poll loop. The tmux socket lives only in the control container,
+ # so this DB column is how the API/UI see what a running — or wedged — agent is doing.
+ Column("last_output", String),
+ Column("output_at", PortableTimestamp),
Column("created_at", PortableTimestamp, nullable=False, server_default=func.now()),
UniqueConstraint("project_id", "name", name="uq_agents_project_name"),
CheckConstraint(_in("status", AGENT_STATUSES), name="ck_agents_status"),
diff --git a/src/handler/hooks/checkpoint.py b/src/handler/hooks/checkpoint.py
index ee9986e..d36b7d4 100644
--- a/src/handler/hooks/checkpoint.py
+++ b/src/handler/hooks/checkpoint.py
@@ -26,7 +26,7 @@ def _mise_init_blocker(working_dir: str) -> str | None:
file is committed (clean tree) and pushed (no commits ahead of, and an, upstream).
"""
if not mise.has_test_task(working_dir):
- return "`.mise.toml` does not yet define a [tasks.test] task"
+ return "no mise config (mise.toml / .mise.toml) defines a [tasks.test] task yet"
if not gitops.is_clean(working_dir):
return "there are uncommitted changes — commit the .mise.toml"
ahead = gitops.ahead_count(working_dir)
diff --git a/src/handler/migrations/versions/0007_agent_pane_output.py b/src/handler/migrations/versions/0007_agent_pane_output.py
new file mode 100644
index 0000000..9fbc5bb
--- /dev/null
+++ b/src/handler/migrations/versions/0007_agent_pane_output.py
@@ -0,0 +1,36 @@
+"""agent live-output snapshot
+
+Revision ID: 0007_agent_pane_output
+Revises: 0006_mise_init_command
+Create Date: 2026-07-16
+
+Adds ``last_output`` (text) and ``output_at`` (timestamp) to ``agents``. The control
+worker snapshots each working agent's tmux pane tail into these columns on its poll loop,
+so the API/UI can show what a live — or wedged — agent is actually doing (the tmux socket
+lives only in the control container, so the DB is the one channel the API can read). Both
+are nullable and back-fill lazily on the next poll, so the migration needs no data step.
+"""
+
+from __future__ import annotations
+
+from collections.abc import Sequence
+
+import sqlalchemy as sa
+from alembic import op
+
+from handler.db.types import PortableTimestamp
+
+revision: str = "0007_agent_pane_output"
+down_revision: str | None = "0006_mise_init_command"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+
+def upgrade() -> None:
+ op.add_column("agents", sa.Column("last_output", sa.String()))
+ op.add_column("agents", sa.Column("output_at", PortableTimestamp))
+
+
+def downgrade() -> None:
+ op.drop_column("agents", "output_at")
+ op.drop_column("agents", "last_output")
diff --git a/tests/conftest.py b/tests/conftest.py
index bf3fdb8..5adb9d7 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -33,6 +33,9 @@ def env(tmp_path, monkeypatch):
monkeypatch.setenv("SHARED_CONTEXT_WRITE_TOKEN", "shared-token")
monkeypatch.setenv("PROJECTS_ROOT", str(tmp_path / "projects"))
monkeypatch.delenv("WEBHOOK_URL", raising=False)
+ # Isolate any home-dir writes (e.g. control.claude_config seeding ~/.claude.json at
+ # spawn) to the per-test tmp dir, so tests never touch the real user's config.
+ monkeypatch.setenv("HOME", str(tmp_path))
_reset_caches()
cfg = Config(str(REPO_ROOT / "alembic.ini"))
diff --git a/tests/test_api_projects_agents.py b/tests/test_api_projects_agents.py
index 8abf6ef..e73965d 100644
--- a/tests/test_api_projects_agents.py
+++ b/tests/test_api_projects_agents.py
@@ -32,6 +32,24 @@ def test_create_and_list_agent(client, auth):
assert [a["name"] for a in agents] == ["api"]
+def test_agent_serializes_live_output_snapshot(client, auth, engine):
+ from handler.db import repository as repo
+
+ _mk_project(client, auth)
+ client.post(
+ "/projects/proj/agents",
+ json={"name": "api", "working_dir": "/tmp/proj/api", "status": "working"},
+ headers=auth,
+ )
+ with engine.begin() as conn:
+ agent = repo.get_agent_by_name(conn, "proj", "api")
+ repo.update_agent_output(conn, agent["id"], "boot\nTheme picker")
+
+ listed = client.get("/projects/proj/agents", headers=auth).json()[0]
+ assert listed["last_output"] == "boot\nTheme picker"
+ assert listed["output_at"] is not None
+
+
def test_agent_under_missing_project_is_404(client, auth):
r = client.get("/projects/ghost/agents", headers=auth)
assert r.status_code == 404
diff --git a/tests/test_claude_config.py b/tests/test_claude_config.py
new file mode 100644
index 0000000..84efa8d
--- /dev/null
+++ b/tests/test_claude_config.py
@@ -0,0 +1,47 @@
+"""Seeding Claude Code onboarding so a detached agent boots straight to the REPL."""
+
+from __future__ import annotations
+
+import json
+from pathlib import Path
+
+from handler.control import claude_config
+
+
+def _read(home: Path) -> dict:
+ return json.loads((home / ".claude.json").read_text())
+
+
+def test_ensure_onboarded_seeds_defaults_on_fresh_home(tmp_path):
+ claude_config.ensure_onboarded(home=str(tmp_path))
+ data = _read(tmp_path)
+ assert data["hasCompletedOnboarding"] is True
+ assert data["theme"] == "dark"
+
+
+def test_ensure_onboarded_merges_without_clobbering(tmp_path):
+ # The login flow's oauthAccount (and a chosen theme) must survive the seeding.
+ (tmp_path / ".claude.json").write_text(
+ json.dumps({"oauthAccount": {"emailAddress": "a@b.c"}, "theme": "light"})
+ )
+ claude_config.ensure_onboarded(home=str(tmp_path))
+ data = _read(tmp_path)
+ assert data["oauthAccount"] == {"emailAddress": "a@b.c"} # preserved
+ assert data["theme"] == "light" # operator's chosen theme untouched
+ assert data["hasCompletedOnboarding"] is True
+
+
+def test_ensure_onboarded_forces_flag_and_trusts_working_dir(tmp_path):
+ # A stale false must not leave onboarding armed; the working dir gets trusted.
+ (tmp_path / ".claude.json").write_text(json.dumps({"hasCompletedOnboarding": False}))
+ wd = "/var/lib/handler/projects/x"
+ claude_config.ensure_onboarded(working_dir=wd, home=str(tmp_path))
+ data = _read(tmp_path)
+ assert data["hasCompletedOnboarding"] is True
+ assert data["projects"][wd]["hasTrustDialogAccepted"] is True
+
+
+def test_ensure_onboarded_survives_corrupt_file(tmp_path):
+ (tmp_path / ".claude.json").write_text("{ not valid json")
+ claude_config.ensure_onboarded(home=str(tmp_path))
+ assert _read(tmp_path)["hasCompletedOnboarding"] is True
diff --git a/tests/test_control_spawn.py b/tests/test_control_spawn.py
index faef3c1..fb34d92 100644
--- a/tests/test_control_spawn.py
+++ b/tests/test_control_spawn.py
@@ -37,10 +37,22 @@ def test_spawn_refuses_without_mise_file(env, fake_tmux):
root = env["tmp"] / "proj"
root.mkdir(parents=True, exist_ok=True)
_register_project(root)
- with pytest.raises(spawn.SpawnError, match="no .mise.toml"):
+ with pytest.raises(spawn.SpawnError, match="no mise config"):
spawn.spawn("proj", "api")
+def test_spawn_accepts_dotless_mise_toml(env, fake_tmux):
+ # mise also reads `mise.toml` (no leading dot); the gate must honor it too.
+ root = env["tmp"] / "proj"
+ root.mkdir(parents=True, exist_ok=True)
+ (root / "mise.toml").write_text("[tasks.test]\nrun = 'pytest'\n")
+ _register_project(root)
+
+ agent = spawn.spawn("proj", "api")
+ with get_engine().begin() as conn:
+ assert repo.get_agent_by_name(conn, "proj", "api")["id"] == agent["id"]
+
+
def test_spawn_creates_agent_settings_and_session(env, fake_tmux):
root = env["tmp"] / "proj"
_write_mise(root, with_test=True)
@@ -89,7 +101,7 @@ def test_spawn_still_gates_without_mise_init_flag(env, fake_tmux):
root.mkdir(parents=True, exist_ok=True)
_register_project(root)
# require_tests defaults on, so a normal spawn against a mise-less repo still refuses.
- with pytest.raises(spawn.SpawnError, match="no .mise.toml"):
+ with pytest.raises(spawn.SpawnError, match="no mise config"):
spawn.spawn("proj", "api")
assert fake_tmux["calls"]["new_session"] == []
diff --git a/tests/test_mise.py b/tests/test_mise.py
new file mode 100644
index 0000000..385f72e
--- /dev/null
+++ b/tests/test_mise.py
@@ -0,0 +1,40 @@
+"""The mise-config gate: which filenames count, and the [tasks.test] check."""
+
+from __future__ import annotations
+
+from handler.control import mise
+
+
+def test_no_config_is_no_config(tmp_path):
+ assert mise.has_config(str(tmp_path)) is False
+ assert mise.has_test_task(str(tmp_path)) is False
+
+
+def test_dotless_mise_toml_is_accepted(tmp_path):
+ (tmp_path / "mise.toml").write_text("[tasks.test]\nrun = 'pytest'\n")
+ assert mise.has_config(str(tmp_path)) is True
+ assert mise.has_test_task(str(tmp_path)) is True
+
+
+def test_dotted_mise_toml_is_accepted(tmp_path):
+ (tmp_path / ".mise.toml").write_text("[tasks.test]\nrun = 'go test ./...'\n")
+ assert mise.has_test_task(str(tmp_path)) is True
+
+
+def test_config_under_dot_config_dir(tmp_path):
+ cfg = tmp_path / ".config" / "mise"
+ cfg.mkdir(parents=True)
+ (cfg / "config.toml").write_text("[tasks.test]\nrun = 'pytest'\n")
+ assert mise.has_test_task(str(tmp_path)) is True
+
+
+def test_config_without_test_task_fails_the_gate(tmp_path):
+ (tmp_path / "mise.toml").write_text("[tasks.lint]\nrun = 'ruff check .'\n")
+ assert mise.has_config(str(tmp_path)) is True
+ assert mise.has_test_task(str(tmp_path)) is False
+
+
+def test_corrupt_config_is_not_a_test_task(tmp_path):
+ (tmp_path / "mise.toml").write_text("this = = not valid toml")
+ assert mise.has_config(str(tmp_path)) is True
+ assert mise.has_test_task(str(tmp_path)) is False
diff --git a/tests/test_worker.py b/tests/test_worker.py
index c26a436..bdbd7e6 100644
--- a/tests/test_worker.py
+++ b/tests/test_worker.py
@@ -211,6 +211,45 @@ def test_bad_command_is_recorded_failed_not_raised(env):
assert "agent name" in failed["error"]
+def test_capture_agent_output_snapshots_working_agents(env, monkeypatch):
+ with get_engine().begin() as conn:
+ repo.create_project(conn, "p", "/tmp/p")
+ agent = repo.create_agent(conn, "p", "api", "/tmp/p/api", status="working")
+
+ monkeypatch.setattr(worker.tmux, "has_session", lambda name: True)
+ monkeypatch.setattr(
+ worker.tmux, "capture_pane", lambda name, escapes=False: "boot\nTheme picker\n\n\n"
+ )
+
+ assert worker.capture_agent_output() == 1
+ with get_engine().begin() as conn:
+ row = repo.get_agent_by_id(conn, agent["id"])
+ # The tail is stored with trailing blank lines trimmed.
+ assert row["last_output"] == "boot\nTheme picker"
+ assert row["output_at"] is not None
+
+
+def test_capture_agent_output_skips_dead_sessions_and_nonworking(env, monkeypatch):
+ with get_engine().begin() as conn:
+ repo.create_project(conn, "p", "/tmp/p")
+ repo.create_agent(conn, "p", "gone", "/tmp/p/gone", status="working")
+ done = repo.create_agent(conn, "p", "done", "/tmp/p/done", status="done")
+
+ captured = []
+ monkeypatch.setattr(worker.tmux, "has_session", lambda name: False)
+ monkeypatch.setattr(
+ worker.tmux,
+ "capture_pane",
+ lambda name, escapes=False: captured.append(name) or "x",
+ )
+
+ # The working agent's session is dead (skipped); the done agent isn't queried at all.
+ assert worker.capture_agent_output() == 0
+ assert captured == []
+ with get_engine().begin() as conn:
+ assert repo.get_agent_by_id(conn, done["id"])["last_output"] is None
+
+
def test_drain_processes_multiple_then_stops(env, monkeypatch):
_seed_project()
monkeypatch.setattr(poller, "sweep", lambda project_id=None: {"checked": 0})