@fnmoa/cli 0.4.5 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -49,7 +49,7 @@ free for one workspace on this machine. See [Licence](#licence).
49
49
  | 2 GitHub | your `gh` login and the repository |
50
50
  | 3 Memory & database | SQLite or Postgres, and LocusGraph memory if you have it: its graph ID (`g`) and agent secret (`e`) |
51
51
  | 4 Agent logins | your Claude or Codex login, or API key |
52
- | 5 Console access | the GitHub App that signs people in to the web console, and who is on the list |
52
+ | 5 Console access | how you sign in to the web console — a password if it is just you, or a GitHub App for a team — and who is on the list |
53
53
  | 6 Deploy & done | a review of the plan and its cost, then Enter twice to deploy |
54
54
 
55
55
  Every line on a screen is ✓, ✗ or …. Setup does not move past a ✗: the line
@@ -208,7 +208,7 @@ ${typeof t?.stderr=="string"?t.stderr:""}`)};function ep(e=At){return{async stat
208
208
  `)[0]?.slice(0,300)}`});p.get("/github/repos",async f=>{if(!await s.ok())return f.json({error:"gh has no token here \u2014 run `gh auth login`"},409);try{return f.json({repos:await o.list()})}catch(h){return f.json(d(h),502)}}),p.get("/github/repos/:owner/:name/issues",async f=>{let h=`${f.req.param("owner")}/${f.req.param("name")}`;if(!et(h))throw new ie("repo must be owner/name");if(!await s.ok())return f.json({error:"gh has no token here \u2014 run `gh auth login`"},409);try{return f.json({issues:await o.issues(h)})}catch(y){return f.json(d(y),502)}}),p.get("/github/repos/:owner/:name/pulls",async f=>{let h=`${f.req.param("owner")}/${f.req.param("name")}`;if(!et(h))throw new ie("repo must be owner/name");if(!await s.ok())return f.json({error:"gh has no token here \u2014 run `gh auth login`"},409);try{return f.json({pulls:await o.pulls(h)})}catch(y){return f.json(d(y),502)}}),p.post("/team/work",async f=>{if(!r.runsAgents)return f.json({error:wn},501);let h=f.get("tenancy").tenant,y=be(await f.req.json().catch(()=>{})),k=Gt(y.agents,"agents");if(k.length===0)throw new ie("agents must name at least one agent");let b=[],P=[];for(let $ of k){let q=await t.store.findAgent(h,$);if(!q)return f.json({error:`no agent named ${$}`},404);if(!Zt(q.cli))return f.json({error:Xn(q.cli)},409);b.push(q.name),P.push({cli:q.cli})}let j=gS(P,await e.agentTools?.()),E=y.nudge===void 0?j:Ee(y.nudge,"nudge"),v=y.routeReplies===void 0?j===void 0:!!y.routeReplies,O={agents:b,...E===void 0?{}:{nudge:E},routeReplies:v,...y.maxTurns===void 0?{}:{maxTurns:Ue(y.maxTurns,"maxTurns",200)},...y.maxCostUsd===void 0?{}:{maxCostUsd:sn(y.maxCostUsd,"maxCostUsd")}},U=wr(f,n,h);if(U)return f.json(U,202);let A=n.start({kind:"team",tenant:h,subject:b.join(", ")},($,q)=>{let Y=0,re=0;return Us(t).work({...O,signal:q,onEvent:ce=>{ce.kind==="ran"?(Y+=1,re+=ce.run.result.costUsd,$({step:ce.agent,steps:Y,costUsd:re})):ce.kind==="routed"?$({note:`${ce.from} \u2192 ${ce.to}`}):ce.kind==="bounced"&&$({note:`bounced: ${ce.from} \u2192 ${ce.to} \xB7 ${ce.reason}`})}})});return yr(f,n,h,A.id),f.json(A,202)});let l=nl(),u=rl(r.checksParallel),m=async(f,h,y={})=>{let k=(fe,je)=>({body:fe,status:je});if(!r.runsAgents)return k({error:wn},501);let b=h.repo??r.repo;if(typeof b!="string"||b.length===0)throw new ie("this workspace has no repository configured \u2014 pass repo, or set MOA_REPO");if(!et(b))throw new ie("repo must be owner/name");if(h.issue!==void 0&&h.pr!==void 0)throw new ie("pass issue or pr, not both");let P=h.pr===void 0?void 0:Ue(h.pr,"pr",1e6),j=P===void 0?"issue":"pr",E=P??Ue(h.issue,"issue",1e6),v=$S(h.steps,r.pipelinePermission??"full"),O=h.head===void 0?void 0:Ee(h.head,"head",200),U=h.intent===void 0?"merge":Ve(h.intent,Bt,"intent");if(j==="pr"&&!Da.includes(U))throw new ie(`a pull request already has its code, so it starts at review \u2014 intent must be one of ${Da.join(", ")}`);let A=await e.caps?.check(f,{repo:b,issue:E,...y.resume?{except:y.resume.id}:{}});if(A&&!A.ok)return k({error:A.says,cap:A.cap},403);let $=A?.issueLeftUsd,q=y.resume?.progress?.costUsd??0;if(y.resume&&$!==void 0&&q>0&&$<=q){let fe=(await a?.settings(f).catch(()=>{}))?.issueCapUsd??0,je=Math.round((fe-$+q)*1e4)/1e4;return k({error:`${b}#${E} has cost $${je.toFixed(2)}, and its per-issue cap is $${fe.toFixed(2)} \u2014 an owner can raise it in Settings`,cap:{kind:"issue",spentUsd:je,capUsd:fe}},403)}let Y,re,ce=!1,L=new Map;if(h.team!==void 0){let fe=await i.getTeam(f,Ee(h.team,"team",200));if(!fe)return k({error:"no such team"},404);if(re={id:fe.id,name:fe.name},TS(v,fe.timeouts),ce=fe.checks==="machine"&&mS.includes(U),Y=ws(fe.members,await i.permissions(f,b),U,j),!Y.ok){let je=Y.needs.filter(xe=>!xe.ok).map(xe=>xe.why).filter((xe,Ke,Fe)=>xe&&Fe.indexOf(xe)===Ke);return k({error:`${fe.name} cannot carry this as far as ${U}: ${je.join("; ")}`,needs:Y.needs},409)}for(let je of new Set(Object.values(Y.assignment))){let xe=await t.store.findAgent(f,je);if(!xe)return k({error:`no agent named ${je}`},404);if(!Zt(xe.cli))return k({error:Xn(xe.cli)},409);L.set(je,xe.cli)}}let K=ao();if(K.length>0)return k({error:`this workspace has no ${K.join(" or ")} \u2014 the pipeline needs both`},409);if(!await s.ok())return k({error:"gh is installed but has no token here \u2014 run `gh auth login` where this workspace runs"},409);for(let fe of Y?[]:Object.values(v))if(!Zt(fe.cli))return k({error:Xn(fe.cli)},409);if(!Y&&!Zt("claude"))return k({error:Xn("claude")},409);let ge=O??ds(E),Ce;if(P!==void 0){let fe;try{fe=await o.pull(b,P)}catch(je){let xe=(je instanceof Error?je.message:String(je)).split(`
209
209
  `)[0];return k({error:`could not read PR #${P} in ${b}: ${xe}`},409)}if(fe.state!=="OPEN")return k({error:`PR #${P} is ${fe.state.toLowerCase()}`},409);if(fe.fork&&U!=="review")return k({error:`PR #${P} comes from a fork: its branch is in another repository, so a pipeline can only review it`},409);ge=fe.fork?`moa/pr-${P}`:fe.branch,fe.fork&&(Ce=`pull/${P}/head`)}let Me,de,ue;if(P===void 0&&r.repoPath&&r.repo&&b.toLowerCase()===r.repo.toLowerCase()){if(!Dr(r.repoPath))return k({error:oo(r.repoPath)},409);Me=r.repoPath,ue=await dm(Me,y.resume)}else try{de=await o.worktree(b,`${b}-${E}-${Date.now()}`,ge,Ce),Me=de.dir}catch(fe){let je=(fe instanceof Error?fe.message:String(fe)).split(`
210
210
  `)[0];return k({error:`could not get a working copy of ${b}: ${je}`},409)}let Be={repo:b,issue:E,cwd:Me,head:ge,...P===void 0?{}:{pr:P}},Mt=e.checksShell??((fe,je,xe,Ke)=>Zi({timeoutMs:Ke})(fe,je,xe)),Ge=y.repeated?.();if(Ge)return await de?.remove(),k(Ge,202);let Tt="",on=n.start({kind:"pipeline",tenant:f,subject:`${b}#${E}`,input:h,...y.resume?{resume:y.resume}:{}},(fe,je)=>{let xe=y.resume?.progress,Ke=xe?.steps??0,Fe=xe?.costUsd??0,Ye=[...xe?.history??[]],pt,_e,$t,T=[...y.resume?.progress?.memory??[]],N=e.memoryFor?.(f),D=N?Sl(N,{tenant:f,repo:b,issue:E,intent:U,...re?{team:re.name}:{}}):void 0;fe({intent:U,from:j,...re?{team:re}:{},...Y?{assignment:Y.assignment}:{},...ue===void 0?{}:{held:ue}});let I=async V=>{let he=Y?.assignment[pt];if(!he)throw new Error(`no member of ${re?.name} takes ${pt}`);let{run:nt,result:wt}=await t.run(he,V.prompt,{cause:`pipeline ${b}#${E}`,...V.signal?{signal:V.signal}:{},workIn:{cwd:V.cwd??Me,...V.permissionMode?{permissionMode:V.permissionMode}:{},...V.model?{model:V.model}:{},...V.timeoutMs?{timeoutMs:V.timeoutMs}:{},...V.onSpawn?{onSpawn:V.onSpawn}:{},...V.onExit?{onExit:V.onExit}:{}}}),dt=Ye[Ye.length-1];return dt&&(dt.runId=nt.id),wt},G=V=>e.steps?e.steps.watching(V,{tenant:f,subject:`${b}#${E}`,job:()=>Tt,step:()=>pt??"step"}):V,C=V=>a?SS(V,{manager:a,tenant:f,job:()=>Tt,onHanded:he=>fe({note:`direction handed to ${pt}: ${he}`})}):V,M=V=>async he=>{if(!D)return V(he);let nt=await D.digest(_e,pt),wt=[...new Set(nt.items.map(dt=>String(dt.context)))].filter(dt=>!T.includes(dt));return wt.length>0&&(T.push(...wt),fe({memory:[...T]})),V({...he,prompt:El(he.prompt,nt)})},F=cm({cwd:Me,branch:ge,issue:E,...ue===void 0?{}:{held:ue}}),se=!1,ne=xe?.since,ae=V=>async he=>{let nt=ne?.step===pt?ne?.sha:void 0;ne=void 0;let wt=await F.began(nt);wt&&pt&&fe({since:{step:pt,sha:wt}});let dt=await V(he);return je.aborted||fe({since:void 0}),dt},me=ce?sl({file:V=>fS(Me,V,At,he=>fe({note:`no ${gt} at ${V.slice(0,7)}: running the default branch's, as of ${he.slice(0,7)}`})),checkout:V=>yl(o,r.workspaceRoot,b,`checks/${b}-${E}-${V.slice(0,12)}`,V),shell:Mt,runs:l,turns:u,repo:b,...r.repoPath?{ownerCheckout:r.repoPath}:{},...t.stepPorts?{ports:t.stepPorts}:{},post:V=>Sa({repo:b,cwd:de?r.workspaceRoot:Me},V),onChange:V=>fe({checks:V}),onNote:V=>fe({note:V}),...xe?.checks?{from:xe.checks}:{}}):void 0,Re=xe?.budgets,Te=h.maxSteps===void 0?void 0:Ue(h.maxSteps,"maxSteps",100),Ie=h.maxCostUsd===void 0?void 0:sn(h.maxCostUsd,"maxCostUsd"),Je={...Te===void 0&&!Re?.steps?{}:{maxSteps:(Te??Br.maxSteps)+(Re?.steps??0)},...vS(Ie===void 0||Ie===0?Ie:Ie+(Re?.costUsd??0),$),...Re?.attempts?{attempts:xS(Re.attempts)}:{}},le=(e.keepAwake??um)(),ye=Bi({signal:je,...xe?{from:{steps:xe.steps??0,costUsd:xe.costUsd??0,attempts:hS(xe.history??[])}}:{},onEvent:V=>{if(V.kind==="decided"){pt=V.action.kind,V.granted&&(fe({note:V.granted}),a?.addLog({id:`mlog_${lm()}`,tenant:f,at:c(),says:`${b}#${E}: ${V.granted}`,by:"manager",repo:b,ref:`#${E}`}).catch(()=>{}));let he=Ye[Ye.length-1];if(he&&he.step==="wait"&&V.action.kind==="wait")he.times=(he.times??1)+1,he.reason=V.action.reason;else{let nt=Y?.assignment[V.action.kind],wt=nt?L.get(nt):v[V.action.kind]?.cli;Ye.push({step:V.action.kind,at:V.ts,reason:V.action.reason,...nt?{worker:nt}:{},...wt?{cli:wt}:{}})}fe({step:V.action.kind,steps:Ke,costUsd:Fe,history:[...Ye]})}else if(V.kind==="acted")wS(V)&&(Ke+=1),V.result&&(Fe+=V.result.costUsd),bS(Ye[Ye.length-1],V),fe({steps:Ke,costUsd:Fe,...V.note===void 0?{}:{note:V.note},history:[...Ye]});else if(V.kind==="slept"){let he=Ye[Ye.length-1];he&&(he.sleptMs=(he.sleptMs??0)+V.ms,V.rerun&&(he.interrupted=!0)),Ke=V.steps,fe({steps:Ke,note:`this machine went to sleep for ${Rn(V.ms)}${V.rerun?`: ${V.action} is run again`:""}`,history:[...Ye]})}else if(V.kind==="reread"){let he=Ye[Ye.length-1];he&&(he.reread={times:V.tries,says:V.error}),fe({note:`GitHub could not be read (${V.error}): reading it again in ${Rn(V.waitMs)}, ${V.tries} of ${V.of}`,history:[...Ye]})}else if(V.kind==="observed"){let he=V.observation.pr;_e=V.observation.issue.title,he&&($t=he.number),fe({title:V.observation.issue.title,...he?{pr:he.number}:{},note:he?`PR #${he.number} \xB7 ${he.state.toLowerCase()} \xB7 ${he.checks.passing}/${he.checks.passing+he.checks.failing+he.checks.pending} checks`:"no pull request yet"})}},context:{repo:b,branch:ge,cwd:Me},observe:()=>ka(me?{...Be,ranHere:V=>me.at(V)}:Be),act:ls(Be),runAgent:ae(C(M(G(Y?I:kS(Dn,t.stepPorts))))),steps:v,...U==="solve"?{}:{keepWork:async V=>{let he=await F(V);return se=!he.kept,he}},policy:{requireApproval:!1,...U==="merge"?{}:{stopAt:U},...Y?{allow:Y.allow}:{},...Je}});return ye.then(V=>D?.finished({outcome:V.outcome,reason:V.reason,steps:Ke,costUsd:Fe,history:Ye,given:T,..._e?{title:_e}:{},...$t?{pr:$t}:{}}),()=>{}),ye.finally(async()=>{if(le(),me?.stop().catch(()=>{}),!de)return;U==="solve"||se&&(await de.unsaved?.()??!0)?fe({folder:de.dir}):await de.remove()})});return Tt=on.id,y.remember?.(on.id),k(on,202)},g=new Set,w=async(f,h,y)=>{let k=(O,U)=>({body:O,status:U}),b=await n.find(h,f);if(!b)return k({error:"no such job"},404);let P=AS(b);if(!P.ok)return k({error:P.says},P.status);let j=await n.inputOf(h,f);if(!j)return k({error:"what started it was not kept, so it cannot be picked up \u2014 start a new pipeline"},409);let E=b.subject?.toLowerCase(),v=n.list(f).find(O=>O.id!==h&&O.kind==="pipeline"&&O.subject?.toLowerCase()===E&&(O.state==="running"||O.state==="queued"));if(v)return k({error:`${b.subject} has a pipeline going (${v.id})`},409);if(g.has(h))return k({error:"it is being resumed already"},409);g.add(h);try{let U=((b.result??{}).reason??b.error??"it stopped").split(`
211
- `)[0]??"it stopped",A=ES(U,j),$={at:c(),by:y.by,...y.who?{who:y.who}:{},why:y.why,...y.lead?{lead:!0}:{},from:U,...A?{topUp:A.says}:{}},q=b.progress??{},Y=A?RS(q.budgets,A.budgets):q.budgets,re=y.by==="lead"?"the Lead":y.who??"you",ce=await m(f,j,{resume:{id:b.id,startedAt:b.startedAt,progress:{...q,...Y?{budgets:Y}:{},resumes:[...q.resumes??[],$],note:`resumed by ${re}: ${y.why}`}}});if(ce.status<300){let[L,K]=(b.subject??"").split("#");await a?.addLog({id:`mlog_${lm()}`,tenant:f,at:c(),says:`Resumed ${b.subject??b.id} (${b.id}), which had stopped: ${U}. Why: ${y.why}${A?`. Given ${A.says}.`:"."}`,by:y.by,...L?{repo:L}:{},...K?{ref:`#${K}`}:{},...y.who?{who:y.who}:{}}).catch(()=>{})}return ce}finally{g.delete(h)}};return p.post("/jobs/:id/resume",async f=>{let{tenant:h,role:y,login:k}=f.get("tenancy"),b=be(await f.req.json().catch(()=>{})??{}),P=b.why===void 0?"asked to carry on from where it stopped":Ee(b.why,"why",500),j=f.req.param("id");if(y!=="owner"&&k&&e.access){let v=await ar(e.access,n,h,k,c());if(!v.ok)return f.json({error:v.says},403)}let E=await w(h,j,{by:"you",...k?{who:k}:{},why:P});return E.status<300&&y!=="owner"&&k&&e.access&&await e.access.addStart({tenant:h,login:k,job:j,at:c()}),f.json(E.body,E.status)}),p.post("/pipelines",async f=>{let h=f.get("tenancy").tenant,y=be(await f.req.json().catch(()=>{})),k=await m(h,y,{repeated:()=>wr(f,n,h),remember:b=>yr(f,n,h,b)});return f.json(k.body,k.status)}),Object.assign(p,{startPipeline:(f,h)=>m(f,h),resumeJob:w,resumePipeline:f=>m(f.tenant,f.input,{resume:{id:f.id,startedAt:f.startedAt,progress:{...f.progress,note:"picked up again after the workspace restarted"}}})})}function xS(e){let t={...$n.attempts};for(let[n,r]of Object.entries(e)){let s=n;t[s]=(t[s]??0)+(r??0)}return t}function vS(e,t){let n=e!==void 0&&e>0?e:void 0;return t===void 0?e===void 0?{}:{maxCostUsd:e}:{maxCostUsd:n===void 0?t:Math.min(n,t)}}var mm=["implement","openPr","review","address","fixCi"];function TS(e,t){let n=Sn;for(let[r,s]of Object.entries(e)){let o=(n.includes(r)?t?.[r]:void 0)??t?.all;o&&(s.timeoutMs=o*6e4)}return e}function $S(e,t="full"){let n=e==null?{}:be(e);for(let s of Object.keys(n))Ve(s,mm,"steps");let r={};for(let s of mm)r[s]={cli:n[s]===void 0?"claude":hr(n[s]),permissionMode:s==="review"?"readOnly":t};return r}function gm(e){let t=new De,n=(e.now??(()=>new Date))().toISOString(),r=e.github??io(),s=e.teams??Pr(),o=e.manager??Zo(),i=e.access??Jo(),a=()=>(e.now??(()=>new Date))().toISOString(),c=[...e.config.tokens.values()][0]?.tenant,p=e.config.ownerLogin&&c?fd(i,c,e.config.ownerLogin,a()).catch(E=>{console.error("[workspace] could not put the first owner on the access list:",E)}):Promise.resolve(),d=e.memoryTenant??c,l=E=>e.memory&&E===d?e.memory:void 0;t.use("*",Xl()),e.log!==!1&&t.use("*",zl(e.log?{write:e.log}:{})),t.get("/health",async E=>{let v={version:e.version??"0.0.0",database:e.config.database.kind};try{return await e.mesh.store.listAgents("__health__"),E.json({ok:!0,...v,startedAt:n})}catch(O){return E.json({ok:!1,...v,startedAt:n,error:O instanceof Error?O.message:String(O)},503)}});let u=e.agentTokens??$p(),m=e.agentTools??Ep();t.route("/v1",vp({mesh:e.mesh,tokens:u,runsAgents:e.config.runsAgents,dailyAgentBudgetUsd:e.config.dailyAgentBudgetUsd,chains:e.callChains??yp(),jobs:e.jobs,assertWritable:()=>e.config.licence?.assertWritable(),memoryFor:l,memorySearch:async(E,v)=>(await s.options(E))[v]?.memorySearch===!0}));let g={access:i,jobs:e.jobs,now:a,ready:p,...e.config.githubClientId?{githubClientId:e.config.githubClientId}:{},...e.config.clientAddress?{clientAddress:e.config.clientAddress}:{},...c?{tenant:c}:{},...e.githubFetch?{fetch:e.githubFetch}:{}};t.route("/v1",Up(g)),t.use("/v1/*",gd(e.config.tokens,{access:i,now:a,ready:p})),t.use("/v1/*",async(E,v)=>{if(!["GET","HEAD","OPTIONS"].includes(E.req.method)&&e.config.licence?.status().state==="read-only")return E.json({error:"licence_read_only",message:"Renew your subscription to start new work. Your data is kept."},402);await v()}),t.use("*",Ip()),t.get("/v1/whoami",E=>{let{tenant:v,scope:O,role:U,login:A}=E.get("tenancy");return E.json({tenant:v,scope:O,role:U,...A?{login:A}:{}})}),t.get("/v1/seats",E=>{let v=E.get("tenancy").tenant,O=new Map(e.mesh.seats.usage(v).map(U=>[U.seatId,U]));return E.json({seats:e.mesh.seats.list(v).map(U=>{let A=O.get(U.id);return{...U,installed:e.config.runsAgents&&Zt(U.cli),...(()=>{let $=e.config.runsAgents?Sd(U.cli):void 0;return $===void 0?{}:{connected:$}})(),used:A?.used??0,...A?.limit===void 0?{}:{limit:A.limit},...A?.resetsAt===void 0?{}:{resetsAt:A.resetsAt.toISOString()}}})})}),t.get("/v1/workspace",async E=>E.json({tenant:E.get("tenancy").tenant,scope:E.get("tenancy").scope,role:E.get("tenancy").role,...E.get("tenancy").login?{login:E.get("tenancy").login}:{},githubLogin:!!e.config.githubClientId,database:e.config.database.kind,version:e.version??"0.0.0",startedAt:n,restart:{at:n,cutShort:e.recovered?.runs??0,freed:e.recovered?.agents??0},workspaceRoot:e.config.workspaceRoot,repo:e.config.repo??null,runsAgents:e.config.runsAgents,hasCheckout:Dr(e.config.repoPath),runTimeoutMinutes:e.config.runTimeoutMinutes,keepActivityDays:e.config.keepActivityDays,runSlots:{ceiling:e.config.maxConcurrentRuns,running:e.mesh.scheduler.inFlight(),waiting:e.mesh.scheduler.waitingForSlot()},missingTools:ao(),githubAuthenticated:await r.ok(),...e.config.dailyAgentBudgetUsd>0?{agentBudget:await dr(e.mesh.store,E.get("tenancy").tenant,e.config.dailyAgentBudgetUsd)}:{},...e.config.runsAgents?{agentTools:await m.fact(`${e.config.selfUrl}/v1/mcp`,u)}:{},...Dr(e.config.repoPath)?{}:{checkoutProblem:e.config.repoPath?oo(e.config.repoPath):"no checkout configured \u2014 set MOA_REPO_PATH to one"}})),t.post("/v1/agents/:name/mcp-token",async E=>{let v=E.get("tenancy").tenant,O=await e.mesh.store.findAgent(v,E.req.param("name"));if(!O)return E.json({error:`no agent named ${E.req.param("name")}`},404);let{id:U,token:A,expiresAt:$}=u.mint({tenant:v,agent:O.name});return E.json({id:U,token:A,expiresAt:$,url:new URL("/v1/mcp",E.req.url).toString()},201)}),t.get("/v1/spend",async E=>{let v=E.req.query("since");if(v!==void 0&&Number.isNaN(Date.parse(v)))throw new ie("since must be an ISO 8601 timestamp");let O=E.get("tenancy").tenant,U=await so(i,e.jobs,O,v??"");return E.json({...await cr(e.mesh.store,O,v,o),byPerson:[...U.entries()].map(([A,$])=>({login:A,starts:$.starts,costUsd:Math.round($.costUsd*1e4)/1e4})).sort((A,$)=>$.costUsd-A.costUsd)})}),t.get("/v1/agents/:name/mcp-token",async E=>{let v=E.get("tenancy").tenant,O=await e.mesh.store.findAgent(v,E.req.param("name"));return O?E.json({tokens:u.issued({tenant:v,agent:O.name})}):E.json({error:`no agent named ${E.req.param("name")}`},404)}),t.delete("/v1/agents/:name/mcp-token/:id",async E=>{let v=E.get("tenancy").tenant,O=await e.mesh.store.findAgent(v,E.req.param("name"));if(!O)return E.json({error:`no agent named ${E.req.param("name")}`},404);let U=u.revokeById({tenant:v,agent:O.name},E.req.param("id"));return E.json({revoked:U?1:0})}),t.delete("/v1/agents/:name/mcp-token",async E=>{let v=E.get("tenancy").tenant,O=await e.mesh.store.findAgent(v,E.req.param("name"));if(!O)return E.json({error:`no agent named ${E.req.param("name")}`},404);let U=u.revokeFor({tenant:v,agent:O.name});return E.json({revoked:U})}),t.get("/v1/peers",async E=>{let v=await e.mesh.store.peersIn(E.get("tenancy").tenant);return E.json({peers:v})}),t.route("/v1",jp(g)),t.route("/v1/agents",Fp(e.mesh)),t.route("/v1",em(e.mesh)),t.route("/v1",am(e.mesh,s)),t.route("/v1",Zp({memoryFor:l,live:e.memoryLive===!0})),t.route("/v1",im({mesh:e.mesh,teams:s,workspaceRoot:e.config.workspaceRoot,memoryFor:l}));let w=e.machine??xd(),f=Yl({now:a,machine:w}),h=wd({manager:o,jobs:e.jobs,store:e.mesh.store,now:a}),y=fm({caps:h,access:i,now:a,mesh:e.mesh,jobs:e.jobs,config:e.config,github:r,steps:f,...e.repos?{repos:e.repos}:{},...e.keepAwake?{keepAwake:e.keepAwake}:{},teams:s,manager:o,memoryFor:l,agentTools:async()=>e.config.runsAgents?await m.fact(`${e.config.selfUrl}/v1/mcp`,u):void 0});t.route("/v1",y);let k=e.repos??To({root:e.config.workspaceRoot}),b=e.issues??ep(),P=Kl({jobs:e.jobs,manager:o,caps:h,repos:k,issues:b,splits:{carryOn:(E,v)=>j.carryOnSplit(E,v)},file:(E,v)=>j.carryOut(E,v),resume:async(E,v,O)=>{let U=await y.resumeJob(E,v,O),A=U.body;return U.status<300?{ok:!0,says:`Resumed ${A.subject??v}`}:{ok:!1,says:A.error??`could not resume it (${U.status})`}},memoryFor:l,now:a,...e.build?{build:e.build}:{},...e.leadGithub?{github:e.leadGithub}:{},...e.restart?{restart:e.restart}:{},steps:f,runs:{active:()=>wi()+e.mesh.scheduler.inFlight()+e.mesh.scheduler.waitingForSlot()+tu()+mu(),hold:yi},machine:w,ownerPorts:Wa(e.config),...e.reviewer?{review:{reviewer:e.reviewer,worktree:(E,v,O,U)=>k.worktree(E,v,O,U),shell:e.shell??ru,...e.runApp?{app:e.runApp}:{},...e.mesh.stepPorts?{ports:e.mesh.stepPorts}:{},...e.config.repoPath?{ownerCheckout:e.config.repoPath}:{}}}:{}}),j=wp({mesh:e.mesh,jobs:e.jobs,manager:o,teams:s,repos:k,issues:b,files:e.files??tp(),startPipeline:y.startPipeline,think:e.think??Vl(e.config.workspaceRoot),memoryFor:l,access:i,lead:P});return t.route("/v1",Vp({mesh:e.mesh,jobs:e.jobs,manager:o,teams:s,repos:k,service:j,github:r,runsAgents:e.config.runsAgents,access:i,lead:P,caps:h})),t.route("/v1",nm({mesh:e.mesh,teams:s,manager:o,service:j})),t.route("/v1",Wp({manager:o,lead:P})),t.route("/v1",zp({mesh:e.mesh,jobs:e.jobs,runsAgents:e.config.runsAgents,dailyAgentBudgetUsd:e.config.dailyAgentBudgetUsd,maxQueuedRuns:e.config.maxQueuedRuns,...e.pollMs===void 0?{}:{pollMs:e.pollMs},...e.heartbeatMs===void 0?{}:{heartbeatMs:e.heartbeatMs},...e.stopping===void 0?{}:{stopping:e.stopping}})),t.notFound(E=>E.json({error:"not found"},404)),t.onError((E,v)=>{if(E instanceof ie)return v.json({error:E.message},400);if(E instanceof Lt)return v.json({error:E.message},400);if(E instanceof zt)return v.json({error:E.message},409);if(E instanceof yt||E instanceof Ft)return v.json({error:E.message},409);if(E instanceof dn)return console.error(`[workspace] ${Yn(v)} database unavailable:`,E.message),v.json({error:E.message,requestId:Yn(v)},503);let O=Yn(v);return console.error(`[workspace] ${O}`,E),v.json({error:"internal error",requestId:O},500)}),Object.assign(t,{managerService:j,leadService:P,resumePipeline:y.resumePipeline})}var hm=8787,_S=3100,PS="/home/moa/.codex/auth.json",Ho="codex/auth.json",OS="/home/moa/logs",za=6,IS=["MOA_LICENCE","MOA_INSTALL_ID","ANTHROPIC_API_KEY","OPENAI_API_KEY","CURSOR_API_KEY","MOA_TOKENS","MOA_D1_ACCOUNT_ID","MOA_D1_DATABASE_ID","MOA_D1_API_TOKEN","MOA_REPO","MOA_OWNER_LOGIN","MOA_GITHUB_CLIENT_ID","MOA_DAILY_AGENT_BUDGET_USD","MOA_MAX_CONCURRENT_RUNS","MOA_RUN_TIMEOUT_MINUTES","GH_TOKEN","CLAUDE_CODE_OAUTH_TOKEN","LOCUSGRAPH_AGENT_SECRET","LOCUSGRAPH_GRAPH_ID","LOCUSGRAPH_SERVER_URL","AUTH_GITHUB_ID","AUTH_GITHUB_SECRET","AUTH_SECRET","AUTH_URL","MOA_PUBLIC_URL"];function CS(e,t){let n={};for(let r of IS){let s=e[r];typeof s=="string"&&s!==""&&(n[r]=s)}return n.MOA_SELF_URL=`http://127.0.0.1:${hm}`,n.MOA_WORKSPACE_ROOT="/data/worktrees",n.MOA_CLIENT_ADDRESS="cf-connecting-ip",t&&(n.MOA_CODEX_AUTH=btoa(t)),n}var Va=e=>{let t=Number(e.MOA_IDLE_HOURS??za),n=Number.isFinite(t)&&t>0?Math.min(t,za):za;return Math.round(n*36e5)},MS=e=>(e.headers.get("host")??new URL(e.url).host).split(":")[0]?.toLowerCase()??"",Ut=(e,t=200)=>new Response(JSON.stringify(e),{status:t,headers:{"content-type":"application/json"}}),Qa=e=>{try{let t=JSON.parse(e);return typeof t=="object"&&t!==null&&Object.keys(t).length>0}catch{return!1}},Za=class e{constructor(t,n){this.ctx=t;this.env=n}static readyMs=12e4;static stepMs=1e3;starting;loggingIn;get container(){let t=this.ctx.container;if(!t)throw new Error("this Durable Object has no container: wrangler's [[containers]] names it");return t}async fetch(t){let n=new URL(t.url);if(n.pathname.startsWith("/_moa/"))return this.control(t,n);await this.ensure();let r=this.env.MOA_CONSOLE_HOST?.toLowerCase(),s=r&&MS(t)===r?_S:hm;return this.forward(s,t)}async ensure(){let t=this.container;t.running||(this.starting??=this.start().finally(()=>{this.starting=void 0}),await this.starting),await t.setInactivityTimeout(Va(this.env))}async start(){let t=this.container,n=await this.env.STATE?.get(Ho),r=n?await n.text():void 0;if(t.running)return;let s=Object.values(t.images??{})[0];t.start({...s?{image:s}:{},enableInternet:!0,env:CS(this.env,r&&Qa(r)?r:void 0)})}async forward(t,n){let r=n.method==="GET"||n.method==="HEAD"?void 0:await n.arrayBuffer(),s=this.container.getTcpPort(t),o=Date.now()+e.readyMs;for(;;)try{return await s.fetch(r===void 0?new Request(n):new Request(n,{body:r}))}catch(i){if(Date.now()>=o)return Ut({error:`nothing answers on :${t} in the container yet`,detail:i instanceof Error?i.message:String(i)},503);await new Promise(a=>setTimeout(a,e.stepMs))}}async control(t,n){let r=n.pathname,s=t.headers.get("authorization")?.replace(/^Bearer\s+/i,"")??"",o=!1;try{o=Do(this.env.MOA_TOKENS).get(s)?.scope==="owner"}catch{o=!1}if(!s||!o)return Ut({error:"an owner token from MOA_TOKENS only"},401);if(r==="/_moa/box"&&t.method==="GET"){let i=await this.env.STATE?.get(Ho);return Ut({running:this.container.running,codexSaved:!!i,idleHours:Va(this.env)/36e5})}if(r==="/_moa/awake"&&t.method==="POST")return this.container.running&&await this.container.setInactivityTimeout(Va(this.env)),Ut({running:this.container.running});if(r==="/_moa/restart"&&t.method==="POST"){let i=this.container.running;return i&&await this.container.destroy(),Ut({restarted:i})}if(r==="/_moa/logs"&&t.method==="GET"){if(!this.container.running)return Ut({running:!1});let i=Number(n.searchParams.get("lines")),a=Number.isInteger(i)&&i>0?Math.min(i,2e3):100,c=async p=>new TextDecoder().decode((await this.exec(["tail","-n",String(a),`${OS}/${p}.log`])).stdout);return Ut({running:!0,workspace:await c("workspace"),console:await c("console")})}if(r==="/_moa/codex/auth"&&t.method==="PUT"){let i=await t.text();return Qa(i)?this.env.STATE?(await this.env.STATE.put(Ho,i),Ut({saved:!0})):Ut({error:"no STATE bucket bound"},501):Ut({error:"not a codex auth.json"},400)}if(r==="/_moa/codex"&&t.method==="GET"){await this.ensure();let i=await this.exec(["codex","login","status"]),a=new TextDecoder().decode(i.stdout).trim()||new TextDecoder().decode(i.stderr).trim();return Ut({loggedIn:i.exitCode===0,says:a.split(`
211
+ `)[0]??"it stopped",A=ES(U,j),$={at:c(),by:y.by,...y.who?{who:y.who}:{},why:y.why,...y.lead?{lead:!0}:{},from:U,...A?{topUp:A.says}:{}},q=b.progress??{},Y=A?RS(q.budgets,A.budgets):q.budgets,re=y.by==="lead"?"the Lead":y.who??"you",ce=await m(f,j,{resume:{id:b.id,startedAt:b.startedAt,progress:{...q,...Y?{budgets:Y}:{},resumes:[...q.resumes??[],$],note:`resumed by ${re}: ${y.why}`}}});if(ce.status<300){let[L,K]=(b.subject??"").split("#");await a?.addLog({id:`mlog_${lm()}`,tenant:f,at:c(),says:`Resumed ${b.subject??b.id} (${b.id}), which had stopped: ${U}. Why: ${y.why}${A?`. Given ${A.says}.`:"."}`,by:y.by,...L?{repo:L}:{},...K?{ref:`#${K}`}:{},...y.who?{who:y.who}:{}}).catch(()=>{})}return ce}finally{g.delete(h)}};return p.post("/jobs/:id/resume",async f=>{let{tenant:h,role:y,login:k}=f.get("tenancy"),b=be(await f.req.json().catch(()=>{})??{}),P=b.why===void 0?"asked to carry on from where it stopped":Ee(b.why,"why",500),j=f.req.param("id");if(y!=="owner"&&k&&e.access){let v=await ar(e.access,n,h,k,c());if(!v.ok)return f.json({error:v.says},403)}let E=await w(h,j,{by:"you",...k?{who:k}:{},why:P});return E.status<300&&y!=="owner"&&k&&e.access&&await e.access.addStart({tenant:h,login:k,job:j,at:c()}),f.json(E.body,E.status)}),p.post("/pipelines",async f=>{let h=f.get("tenancy").tenant,y=be(await f.req.json().catch(()=>{})),k=await m(h,y,{repeated:()=>wr(f,n,h),remember:b=>yr(f,n,h,b)});return f.json(k.body,k.status)}),Object.assign(p,{startPipeline:(f,h)=>m(f,h),resumeJob:w,resumePipeline:f=>m(f.tenant,f.input,{resume:{id:f.id,startedAt:f.startedAt,progress:{...f.progress,note:"picked up again after the workspace restarted"}}})})}function xS(e){let t={...$n.attempts};for(let[n,r]of Object.entries(e)){let s=n;t[s]=(t[s]??0)+(r??0)}return t}function vS(e,t){let n=e!==void 0&&e>0?e:void 0;return t===void 0?e===void 0?{}:{maxCostUsd:e}:{maxCostUsd:n===void 0?t:Math.min(n,t)}}var mm=["implement","openPr","review","address","fixCi"];function TS(e,t){let n=Sn;for(let[r,s]of Object.entries(e)){let o=(n.includes(r)?t?.[r]:void 0)??t?.all;o&&(s.timeoutMs=o*6e4)}return e}function $S(e,t="full"){let n=e==null?{}:be(e);for(let s of Object.keys(n))Ve(s,mm,"steps");let r={};for(let s of mm)r[s]={cli:n[s]===void 0?"claude":hr(n[s]),permissionMode:s==="review"?"readOnly":t};return r}function gm(e){let t=new De,n=(e.now??(()=>new Date))().toISOString(),r=e.github??io(),s=e.teams??Pr(),o=e.manager??Zo(),i=e.access??Jo(),a=()=>(e.now??(()=>new Date))().toISOString(),c=[...e.config.tokens.values()][0]?.tenant,p=e.config.ownerLogin&&c?fd(i,c,e.config.ownerLogin,a()).catch(E=>{console.error("[workspace] could not put the first owner on the access list:",E)}):Promise.resolve(),d=e.memoryTenant??c,l=E=>e.memory&&E===d?e.memory:void 0;t.use("*",Xl()),e.log!==!1&&t.use("*",zl(e.log?{write:e.log}:{})),t.get("/health",async E=>{let v={version:e.version??"0.0.0",database:e.config.database.kind};try{return await e.mesh.store.listAgents("__health__"),E.json({ok:!0,...v,startedAt:n})}catch(O){return E.json({ok:!1,...v,startedAt:n,error:O instanceof Error?O.message:String(O)},503)}});let u=e.agentTokens??$p(),m=e.agentTools??Ep();t.route("/v1",vp({mesh:e.mesh,tokens:u,runsAgents:e.config.runsAgents,dailyAgentBudgetUsd:e.config.dailyAgentBudgetUsd,chains:e.callChains??yp(),jobs:e.jobs,assertWritable:()=>e.config.licence?.assertWritable(),memoryFor:l,memorySearch:async(E,v)=>(await s.options(E))[v]?.memorySearch===!0}));let g={access:i,jobs:e.jobs,now:a,ready:p,...e.config.githubClientId?{githubClientId:e.config.githubClientId}:{},...e.config.clientAddress?{clientAddress:e.config.clientAddress}:{},...c?{tenant:c}:{},...e.githubFetch?{fetch:e.githubFetch}:{}};t.route("/v1",Up(g)),t.use("/v1/*",gd(e.config.tokens,{access:i,now:a,ready:p})),t.use("/v1/*",async(E,v)=>{if(!["GET","HEAD","OPTIONS"].includes(E.req.method)&&e.config.licence?.status().state==="read-only")return E.json({error:"licence_read_only",message:"Renew your subscription to start new work. Your data is kept."},402);await v()}),t.use("*",Ip()),t.get("/v1/whoami",E=>{let{tenant:v,scope:O,role:U,login:A}=E.get("tenancy");return E.json({tenant:v,scope:O,role:U,...A?{login:A}:{}})}),t.get("/v1/seats",E=>{let v=E.get("tenancy").tenant,O=new Map(e.mesh.seats.usage(v).map(U=>[U.seatId,U]));return E.json({seats:e.mesh.seats.list(v).map(U=>{let A=O.get(U.id);return{...U,installed:e.config.runsAgents&&Zt(U.cli),...(()=>{let $=e.config.runsAgents?Sd(U.cli):void 0;return $===void 0?{}:{connected:$}})(),used:A?.used??0,...A?.limit===void 0?{}:{limit:A.limit},...A?.resetsAt===void 0?{}:{resetsAt:A.resetsAt.toISOString()}}})})}),t.get("/v1/workspace",async E=>E.json({tenant:E.get("tenancy").tenant,scope:E.get("tenancy").scope,role:E.get("tenancy").role,...E.get("tenancy").login?{login:E.get("tenancy").login}:{},githubLogin:!!e.config.githubClientId,database:e.config.database.kind,version:e.version??"0.0.0",startedAt:n,restart:{at:n,cutShort:e.recovered?.runs??0,freed:e.recovered?.agents??0},workspaceRoot:e.config.workspaceRoot,repo:e.config.repo??null,runsAgents:e.config.runsAgents,hasCheckout:Dr(e.config.repoPath),runTimeoutMinutes:e.config.runTimeoutMinutes,keepActivityDays:e.config.keepActivityDays,runSlots:{ceiling:e.config.maxConcurrentRuns,running:e.mesh.scheduler.inFlight(),waiting:e.mesh.scheduler.waitingForSlot()},missingTools:ao(),githubAuthenticated:await r.ok(),...e.config.dailyAgentBudgetUsd>0?{agentBudget:await dr(e.mesh.store,E.get("tenancy").tenant,e.config.dailyAgentBudgetUsd)}:{},...e.config.runsAgents?{agentTools:await m.fact(`${e.config.selfUrl}/v1/mcp`,u)}:{},...Dr(e.config.repoPath)?{}:{checkoutProblem:e.config.repoPath?oo(e.config.repoPath):"no checkout configured \u2014 set MOA_REPO_PATH to one"}})),t.post("/v1/agents/:name/mcp-token",async E=>{let v=E.get("tenancy").tenant,O=await e.mesh.store.findAgent(v,E.req.param("name"));if(!O)return E.json({error:`no agent named ${E.req.param("name")}`},404);let{id:U,token:A,expiresAt:$}=u.mint({tenant:v,agent:O.name});return E.json({id:U,token:A,expiresAt:$,url:new URL("/v1/mcp",E.req.url).toString()},201)}),t.get("/v1/spend",async E=>{let v=E.req.query("since");if(v!==void 0&&Number.isNaN(Date.parse(v)))throw new ie("since must be an ISO 8601 timestamp");let O=E.get("tenancy").tenant,U=await so(i,e.jobs,O,v??"");return E.json({...await cr(e.mesh.store,O,v,o),byPerson:[...U.entries()].map(([A,$])=>({login:A,starts:$.starts,costUsd:Math.round($.costUsd*1e4)/1e4})).sort((A,$)=>$.costUsd-A.costUsd)})}),t.get("/v1/agents/:name/mcp-token",async E=>{let v=E.get("tenancy").tenant,O=await e.mesh.store.findAgent(v,E.req.param("name"));return O?E.json({tokens:u.issued({tenant:v,agent:O.name})}):E.json({error:`no agent named ${E.req.param("name")}`},404)}),t.delete("/v1/agents/:name/mcp-token/:id",async E=>{let v=E.get("tenancy").tenant,O=await e.mesh.store.findAgent(v,E.req.param("name"));if(!O)return E.json({error:`no agent named ${E.req.param("name")}`},404);let U=u.revokeById({tenant:v,agent:O.name},E.req.param("id"));return E.json({revoked:U?1:0})}),t.delete("/v1/agents/:name/mcp-token",async E=>{let v=E.get("tenancy").tenant,O=await e.mesh.store.findAgent(v,E.req.param("name"));if(!O)return E.json({error:`no agent named ${E.req.param("name")}`},404);let U=u.revokeFor({tenant:v,agent:O.name});return E.json({revoked:U})}),t.get("/v1/peers",async E=>{let v=await e.mesh.store.peersIn(E.get("tenancy").tenant);return E.json({peers:v})}),t.route("/v1",jp(g)),t.route("/v1/agents",Fp(e.mesh)),t.route("/v1",em(e.mesh)),t.route("/v1",am(e.mesh,s)),t.route("/v1",Zp({memoryFor:l,live:e.memoryLive===!0})),t.route("/v1",im({mesh:e.mesh,teams:s,workspaceRoot:e.config.workspaceRoot,memoryFor:l}));let w=e.machine??xd(),f=Yl({now:a,machine:w}),h=wd({manager:o,jobs:e.jobs,store:e.mesh.store,now:a}),y=fm({caps:h,access:i,now:a,mesh:e.mesh,jobs:e.jobs,config:e.config,github:r,steps:f,...e.repos?{repos:e.repos}:{},...e.keepAwake?{keepAwake:e.keepAwake}:{},teams:s,manager:o,memoryFor:l,agentTools:async()=>e.config.runsAgents?await m.fact(`${e.config.selfUrl}/v1/mcp`,u):void 0});t.route("/v1",y);let k=e.repos??To({root:e.config.workspaceRoot}),b=e.issues??ep(),P=Kl({jobs:e.jobs,manager:o,caps:h,repos:k,issues:b,splits:{carryOn:(E,v)=>j.carryOnSplit(E,v)},file:(E,v)=>j.carryOut(E,v),resume:async(E,v,O)=>{let U=await y.resumeJob(E,v,O),A=U.body;return U.status<300?{ok:!0,says:`Resumed ${A.subject??v}`}:{ok:!1,says:A.error??`could not resume it (${U.status})`}},memoryFor:l,now:a,...e.build?{build:e.build}:{},...e.leadGithub?{github:e.leadGithub}:{},...e.restart?{restart:e.restart}:{},steps:f,runs:{active:()=>wi()+e.mesh.scheduler.inFlight()+e.mesh.scheduler.waitingForSlot()+tu()+mu(),hold:yi},machine:w,ownerPorts:Wa(e.config),...e.reviewer?{review:{reviewer:e.reviewer,worktree:(E,v,O,U)=>k.worktree(E,v,O,U),shell:e.shell??ru,...e.runApp?{app:e.runApp}:{},...e.mesh.stepPorts?{ports:e.mesh.stepPorts}:{},...e.config.repoPath?{ownerCheckout:e.config.repoPath}:{}}}:{}}),j=wp({mesh:e.mesh,jobs:e.jobs,manager:o,teams:s,repos:k,issues:b,files:e.files??tp(),startPipeline:y.startPipeline,think:e.think??Vl(e.config.workspaceRoot),memoryFor:l,access:i,lead:P});return t.route("/v1",Vp({mesh:e.mesh,jobs:e.jobs,manager:o,teams:s,repos:k,service:j,github:r,runsAgents:e.config.runsAgents,access:i,lead:P,caps:h})),t.route("/v1",nm({mesh:e.mesh,teams:s,manager:o,service:j})),t.route("/v1",Wp({manager:o,lead:P})),t.route("/v1",zp({mesh:e.mesh,jobs:e.jobs,runsAgents:e.config.runsAgents,dailyAgentBudgetUsd:e.config.dailyAgentBudgetUsd,maxQueuedRuns:e.config.maxQueuedRuns,...e.pollMs===void 0?{}:{pollMs:e.pollMs},...e.heartbeatMs===void 0?{}:{heartbeatMs:e.heartbeatMs},...e.stopping===void 0?{}:{stopping:e.stopping}})),t.notFound(E=>E.json({error:"not found"},404)),t.onError((E,v)=>{if(E instanceof ie)return v.json({error:E.message},400);if(E instanceof Lt)return v.json({error:E.message},400);if(E instanceof zt)return v.json({error:E.message},409);if(E instanceof yt||E instanceof Ft)return v.json({error:E.message},409);if(E instanceof dn)return console.error(`[workspace] ${Yn(v)} database unavailable:`,E.message),v.json({error:E.message,requestId:Yn(v)},503);let O=Yn(v);return console.error(`[workspace] ${O}`,E),v.json({error:"internal error",requestId:O},500)}),Object.assign(t,{managerService:j,leadService:P,resumePipeline:y.resumePipeline})}var hm=8787,_S=3100,PS="/home/moa/.codex/auth.json",Ho="codex/auth.json",OS="/home/moa/logs",za=6,IS=["MOA_LICENCE","MOA_INSTALL_ID","ANTHROPIC_API_KEY","OPENAI_API_KEY","CURSOR_API_KEY","MOA_TOKENS","MOA_D1_ACCOUNT_ID","MOA_D1_DATABASE_ID","MOA_D1_API_TOKEN","MOA_REPO","MOA_OWNER_LOGIN","MOA_GITHUB_CLIENT_ID","MOA_DAILY_AGENT_BUDGET_USD","MOA_MAX_CONCURRENT_RUNS","MOA_RUN_TIMEOUT_MINUTES","GH_TOKEN","CLAUDE_CODE_OAUTH_TOKEN","LOCUSGRAPH_AGENT_SECRET","LOCUSGRAPH_GRAPH_ID","LOCUSGRAPH_SERVER_URL","AUTH_GITHUB_ID","AUTH_GITHUB_SECRET","AUTH_SECRET","MOA_CONSOLE_PASSWORD_HASH","MOA_CONSOLE_LOGIN","AUTH_URL","MOA_PUBLIC_URL"];function CS(e,t){let n={};for(let r of IS){let s=e[r];typeof s=="string"&&s!==""&&(n[r]=s)}return n.MOA_SELF_URL=`http://127.0.0.1:${hm}`,n.MOA_WORKSPACE_ROOT="/data/worktrees",n.MOA_CLIENT_ADDRESS="cf-connecting-ip",t&&(n.MOA_CODEX_AUTH=btoa(t)),n}var Va=e=>{let t=Number(e.MOA_IDLE_HOURS??za),n=Number.isFinite(t)&&t>0?Math.min(t,za):za;return Math.round(n*36e5)},MS=e=>(e.headers.get("host")??new URL(e.url).host).split(":")[0]?.toLowerCase()??"",Ut=(e,t=200)=>new Response(JSON.stringify(e),{status:t,headers:{"content-type":"application/json"}}),Qa=e=>{try{let t=JSON.parse(e);return typeof t=="object"&&t!==null&&Object.keys(t).length>0}catch{return!1}},Za=class e{constructor(t,n){this.ctx=t;this.env=n}static readyMs=12e4;static stepMs=1e3;starting;loggingIn;get container(){let t=this.ctx.container;if(!t)throw new Error("this Durable Object has no container: wrangler's [[containers]] names it");return t}async fetch(t){let n=new URL(t.url);if(n.pathname.startsWith("/_moa/"))return this.control(t,n);await this.ensure();let r=this.env.MOA_CONSOLE_HOST?.toLowerCase(),s=r&&MS(t)===r?_S:hm;return this.forward(s,t)}async ensure(){let t=this.container;t.running||(this.starting??=this.start().finally(()=>{this.starting=void 0}),await this.starting),await t.setInactivityTimeout(Va(this.env))}async start(){let t=this.container,n=await this.env.STATE?.get(Ho),r=n?await n.text():void 0;if(t.running)return;let s=Object.values(t.images??{})[0];t.start({...s?{image:s}:{},enableInternet:!0,env:CS(this.env,r&&Qa(r)?r:void 0)})}async forward(t,n){let r=n.method==="GET"||n.method==="HEAD"?void 0:await n.arrayBuffer(),s=this.container.getTcpPort(t),o=Date.now()+e.readyMs;for(;;)try{return await s.fetch(r===void 0?new Request(n):new Request(n,{body:r}))}catch(i){if(Date.now()>=o)return Ut({error:`nothing answers on :${t} in the container yet`,detail:i instanceof Error?i.message:String(i)},503);await new Promise(a=>setTimeout(a,e.stepMs))}}async control(t,n){let r=n.pathname,s=t.headers.get("authorization")?.replace(/^Bearer\s+/i,"")??"",o=!1;try{o=Do(this.env.MOA_TOKENS).get(s)?.scope==="owner"}catch{o=!1}if(!s||!o)return Ut({error:"an owner token from MOA_TOKENS only"},401);if(r==="/_moa/box"&&t.method==="GET"){let i=await this.env.STATE?.get(Ho);return Ut({running:this.container.running,codexSaved:!!i,idleHours:Va(this.env)/36e5})}if(r==="/_moa/awake"&&t.method==="POST")return this.container.running&&await this.container.setInactivityTimeout(Va(this.env)),Ut({running:this.container.running});if(r==="/_moa/restart"&&t.method==="POST"){let i=this.container.running;return i&&await this.container.destroy(),Ut({restarted:i})}if(r==="/_moa/logs"&&t.method==="GET"){if(!this.container.running)return Ut({running:!1});let i=Number(n.searchParams.get("lines")),a=Number.isInteger(i)&&i>0?Math.min(i,2e3):100,c=async p=>new TextDecoder().decode((await this.exec(["tail","-n",String(a),`${OS}/${p}.log`])).stdout);return Ut({running:!0,workspace:await c("workspace"),console:await c("console")})}if(r==="/_moa/codex/auth"&&t.method==="PUT"){let i=await t.text();return Qa(i)?this.env.STATE?(await this.env.STATE.put(Ho,i),Ut({saved:!0})):Ut({error:"no STATE bucket bound"},501):Ut({error:"not a codex auth.json"},400)}if(r==="/_moa/codex"&&t.method==="GET"){await this.ensure();let i=await this.exec(["codex","login","status"]),a=new TextDecoder().decode(i.stdout).trim()||new TextDecoder().decode(i.stderr).trim();return Ut({loggedIn:i.exitCode===0,says:a.split(`
212
212
  `)[0]??""})}return r==="/_moa/codex/login"&&t.method==="POST"?(await this.ensure(),this.deviceLogin()):Ut({error:"no such box route"},404)}exec(t){return this.container.exec(t,{cwd:"/home/moa",env:{HOME:"/home/moa"}}).then(n=>n.output())}deviceLogin(){let t=this.loggingIn??=this.startLogin(),{readable:n,writable:r}=new TransformStream,s=r.getWriter();for(let o of t.said)s.write(o).catch(()=>{});return t.watching.add(s),new Response(n,{headers:{"content-type":"text/plain; charset=utf-8"}})}startLogin(){let t={said:[],watching:new Set},n=o=>{t.said.push(o);for(let i of t.watching)i.write(o).catch(()=>{})},r=o=>new TextEncoder().encode(o),s=(async()=>{try{let o=await this.container.exec(["codex","login","--device-auth"],{cwd:"/home/moa",env:{HOME:"/home/moa"},stdout:"pipe",stderr:"combined"});if(o.stdout){let c=o.stdout.getReader();for(;;){let{done:p,value:d}=await c.read();if(p)break;n(d)}}let i=await o.exitCode,a=!1;i===0&&(a=await this.saveCodex().catch(()=>!1)),n(r(`
213
213
  [moa] codex login exited ${i}${a?", saved":""}
214
214
  `))}catch(o){let i=o instanceof Error?o.message:String(o);n(r(`