@codweft/runner
Advanced tools
+2
-1
@@ -39,3 +39,4 @@ #!/usr/bin/env node | ||
| }; | ||
| declare function redactText(value: string): string; | ||
| type SecretSource = Record<string, string | undefined>; | ||
| declare function redactText(value: string, extraSecrets?: SecretSource): string; | ||
| declare class EventReporter { | ||
@@ -42,0 +43,0 @@ private readonly jobId; |
+4
-4
| #!/usr/bin/env node | ||
| import{spawn as F}from"child_process";import{appendFile as M,mkdir as R,mkdtemp as z,readFile as T,rm as B,writeFile as J}from"fs/promises";import{tmpdir as U}from"os";import{dirname as L,join as u}from"path";import{fileURLToPath as W}from"url";import{z as n}from"zod";var A=n.union([n.literal("debug"),n.literal("info"),n.literal("warn"),n.literal("error")]),q=n.object({job:n.object({jobId:n.string(),commandId:n.string(),owner:n.string(),repo:n.string(),fullName:n.string(),defaultBranch:n.string(),command:n.string(),instruction:n.string(),status:n.union([n.literal("queued"),n.literal("in_progress"),n.literal("success"),n.literal("failure"),n.literal("cancelled"),n.literal("timed_out")]).optional(),workItemNumber:n.number().optional(),changeRequestNumber:n.number().optional()}),checkout:n.object({checkoutMode:n.union([n.literal("default_branch"),n.literal("change_request_head")]),checkoutRef:n.string().min(1),checkoutSha:n.string().min(1).optional(),checkoutFetchRef:n.string().min(1).optional(),baseRef:n.string().min(1).optional(),publishBaseRef:n.string().min(1)}),modelRoutingPlan:n.object({retryGroup:n.string(),routes:n.array(n.object({provider:n.string(),model:n.string(),secretName:n.string()}))}),mcp:n.object({endpoint:n.string().url()})}),N=16384,G=20,V=1e3,$=[":(exclude).codweft/result.json",":(exclude).mcp.json"],Z=[".codweft/result.json",".mcp.json"],K=["PATH","HOME","TMPDIR","USER","LANG","LC_ALL","CI"],X=new Set(["failure","cancelled","timed_out"]),Y=/(token|secret|password|api[_-]?key|private[_-]?key|bearer)/i;function j(e){let t=process.env[e];if(!t)throw new Error(`${e} is required`);return t}function y(e){let t=e.replace(/https:\/\/([^:\s/@]+):([^@\s]+)@github\.com/gi,"https://$1:[redacted]@github.com").replace(/authorization:\s*basic\s+[A-Za-z0-9+/=._~-]+/gi,"AUTHORIZATION: basic [redacted]").replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi,"Bearer [redacted]").replace(/(CODWEFT_(?:BOOTSTRAP|MCP)_TOKEN=)[^\s]+/g,"$1[redacted]");for(let[r,i]of Object.entries(process.env))!i||i.length<8||!Y.test(r)||(t=t.split(i).join(`[redacted:${r}]`));return t}function Q(e){return Buffer.byteLength(e)<=N?e:`${e.slice(0,N-128)} | ||
| [truncated by codweft-runner]`}async function w(e,t){let r=j("CODWEFT_API_URL").replace(/\/$/,""),i=j("CODWEFT_BOOTSTRAP_TOKEN"),s=await fetch(`${r}${e}`,{...t,headers:{authorization:`Bearer ${i}`,"content-type":"application/json",...t?.headers}});if(!s.ok)throw new Error(`${e} failed: ${s.status} ${await s.text()}`);return s}var S=class{constructor(t,r){this.jobId=t;this.logPath=r}sequence=0;buffer=[];flushTimer=null;async event(t,r=!1){let i={...t,sequence:t.sequence??++this.sequence,message:Q(y(t.message)),redacted:!0};if(this.logPath&&await C(this.logPath,`${JSON.stringify({...i,createdAt:new Date().toISOString()})} | ||
| `).catch(()=>{}),this.buffer.push(i),r||this.buffer.length>=G||t.severity==="error"){await this.flush();return}this.scheduleFlush()}async timeline(t,r="info",i){await this.event({type:"runner",source:"runner",channel:"timeline",message:t,severity:r,step:i,visibility:r==="error"?"user":"debug",redacted:!0},r==="error")}async flush(){this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null);let t=this.buffer.splice(0,this.buffer.length);t.length!==0&&await w(`/api/runner/jobs/${this.jobId}/events`,{method:"POST",body:JSON.stringify({events:t})}).catch(()=>{})}scheduleFlush(){this.flushTimer||(this.flushTimer=setTimeout(()=>{this.flush().catch(()=>{})},V))}};async function ee(e){let t=await w(`/api/runner/jobs/${e}/forge-token`,{method:"POST"});return n.object({token:n.string(),username:n.string()}).parse(await t.json())}function te({fullName:e,username:t,token:r}){let i=`https://github.com/${e}.git`,s=Buffer.from(`${t}:${r}`).toString("base64");return{repoUrl:i,extraHeaderConfig:`http.https://github.com/.extraheader=AUTHORIZATION: basic ${s}`}}function re(e,t){let r=["fetch","origin",e,"--depth=1"];return t?["-c",t,...r]:r}function ne(e,t){return["-c",t,"-c","core.hooksPath=/dev/null","push","origin",`HEAD:refs/heads/${e}`,"--force-with-lease"]}function se(){return["add","-A","--",".",...$]}function ie(e){return["-c","core.hooksPath=/dev/null","commit","-m",e]}function oe(e){return["diff","--name-only",`${e}..HEAD`,"--",...Z]}function I(e){return`codweft/${e.replace(/[^A-Za-z0-9._-]/g,"-")}`}function ae({jobId:e,baseRef:t,title:r,body:i,draft:s}){return{title:r,body:i,draft:s,head:I(e),base:t}}function ce(e){return/\b(create|open|submit|raise)\b[\s\S]{0,80}\b(pr|pull request|change request)\b/i.test(e)}function ue(e,t){return["fix","implement","resolve_conflicts"].includes(e)?!0:e==="task"&&ce(t)}function x(e=process.env){let t={};for(let r of K){let i=e[r];i&&(t[r]=i)}return t.HOME&&(t.PATH=`${t.HOME}/.pi/bin${t.PATH?`:${t.PATH}`:""}`),t}function le({mcpToken:e,secretName:t,sourceEnv:r=process.env}){let i=x(r),s=r[t];if(!s)throw new Error(`${t} is not configured`);return i[t]=s,i.CODWEFT_MCP_TOKEN=e,i}var ge=n.object({changeRequest:n.object({title:n.string().min(1).max(256),body:n.string().max(65536).optional(),draft:n.boolean().optional()}).optional()});function de(e){return ge.parse(JSON.parse(e))}async function he(e){let t=await w(`/api/runner/jobs/${e}/mcp-token`,{method:"POST"});return n.object({mcpToken:n.string()}).parse(await t.json()).mcpToken}async function C(e,t){await R(L(e),{recursive:!0}),await M(e,t)}async function d(e,t,r){let i=r.source??"runner",s=y(`${e} ${t.join(" ")}`);await r.context.reporter.timeline(`running ${s}`,"info",r.step);let o=u(r.context.logsDir,`${r.logPrefix??r.step}-stdout.log`),a=u(r.context.logsDir,`${r.logPrefix??r.step}-stderr.log`),c="",l="",g=[];return new Promise((m,b)=>{async function h(){await Promise.allSettled(g)}let H=r.inheritEnv===!1||e==="git"&&r.inheritEnv!==!0?{...x(),...r.env}:{...process.env,...r.env},k=F(e,t,{cwd:r.cwd,env:H,stdio:["ignore","pipe","pipe"]});k.stdout?.on("data",v=>{let p=y(v.toString("utf8"));c+=p,process.stdout.write(p),g.push(Promise.all([C(o,p),O(r.context,{text:p,source:i,channel:"stdout",step:r.step,normalizeEvent:r.normalizeEvent})]))}),k.stderr?.on("data",v=>{let p=y(v.toString("utf8"));l+=p,process.stderr.write(p),g.push(Promise.all([C(a,p),O(r.context,{text:p,source:i,channel:"stderr",step:r.step,normalizeEvent:r.normalizeEvent})]))}),k.on("exit",v=>{let p=v??1;h().then(()=>{p===0?m({stdout:c,stderr:l,exitCode:p}):b(new Error(`${e} exited with ${p}`))}).catch(b)}),k.on("error",v=>{h().then(()=>b(v)).catch(b)})})}async function O(e,{text:t,source:r,channel:i,step:s,normalizeEvent:o}){let a=t.split(/\r?\n/).filter(c=>c.trim().length>0);for(let c of a.slice(-10)){let l=o?.(c,i);await e.reporter.event(l??{type:"process_output",source:r,channel:i,message:c,severity:i==="stderr"?"warn":"debug",step:s,visibility:"debug",redacted:!0})}}function pe(e){let t=e.job.changeRequestNumber!==void 0?`change_request #${e.job.changeRequestNumber}`:e.job.workItemNumber!==void 0?`work_item #${e.job.workItemNumber}`:"repository";return["You are Codweft's coding agent running inside a portable runner.","Use local filesystem, shell, and git for repository work.","Use the Codweft MCP server for all forge operations: comments, reviews, labels, checks, and change requests.","Do not call forge APIs directly when an MCP tool exists.","Do not push branches or commits. The runner owns publishing.",'When code changes should be published, leave the changes locally and write .codweft/result.json with {"changeRequest":{"title":"...","body":"...","draft":false}}.',"Do not call create_change_request for local code changes; the runner will push the branch first and then create it.",`Command: ${e.job.command}`,`Target: ${t}`,`Instruction: ${e.job.instruction}`,"","For free-form task commands, default to diagnosis or a plan unless the instruction explicitly asks for code changes or a change request.","Always report progress and final success or failure through Codweft MCP."].join(` | ||
| `)}async function fe(e,t){let r={mcpServers:{codweft:{url:t,auth:"bearer",bearerTokenEnv:"CODWEFT_MCP_TOKEN",lifecycle:"keep-alive",directTools:!0}}};await J(u(e,".mcp.json"),JSON.stringify(r,null,2))}function me(e,t){let r=e.trim();if(!r.startsWith("{")||!r.endsWith("}"))return null;let i;try{i=JSON.parse(r)}catch{return null}let s=n.object({type:n.string().optional(),message:n.string().optional(),content:n.string().optional(),level:A.optional(),severity:A.optional(),role:n.string().optional()}).passthrough().safeParse(i);if(!s.success)return null;let o=s.data.message??s.data.content;return o?{type:s.data.type??"agent_message",source:"agent",channel:s.data.role==="tool"?"tool_call":"agent_message",message:o,severity:s.data.severity??s.data.level??(t==="stderr"?"warn":"info"),visibility:"user",redacted:!0,metadata:{rawType:s.data.type}}:null}var E={async install(e){let t=x();await d("sh",["-lc","command -v pi >/dev/null 2>&1 || curl -fsSL https://pi.dev/install.sh | sh"],{cwd:e.workspace,env:t,inheritEnv:!1,step:"agent_install",logPrefix:"pi-install",context:e}),await d("pi",["install","npm:pi-mcp-adapter@2.5.4"],{cwd:e.workspace,env:t,inheritEnv:!1,step:"agent_install",logPrefix:"pi-install",context:e})},async configureMcp(e,t){await fe(e.workspace,t)},async run(e,t,r){let i=pe(t),s=null;for(let o of t.modelRoutingPlan.routes){if(!process.env[o.secretName]){await e.reporter.event({type:"model_route_skipped",source:"model_router",channel:"timeline",message:`skipping ${o.provider}/${o.model}: ${o.secretName} is not configured`,severity:"warn",step:"model_route",visibility:"debug",redacted:!0});continue}try{await e.reporter.event({type:"agent_started",source:"agent",channel:"timeline",message:`starting Pi with ${o.provider}/${o.model}`,severity:"info",step:"agent_run",visibility:"user",redacted:!0},!0),await d("pi",["--mode","json","--provider",o.provider,"--model",o.model,i],{cwd:e.workspace,env:le({mcpToken:r,secretName:o.secretName}),inheritEnv:!1,step:"agent_run",source:"agent",logPrefix:"agent",normalizeEvent:this.normalizeEvent,context:e});return}catch(a){s=a instanceof Error?a:new Error(String(a)),await e.reporter.event({type:"model_route_failed",source:"model_router",channel:"timeline",message:`model route failed: ${s.message}`,severity:"warn",step:"model_route",visibility:"user",redacted:!0},!0)}}throw s??new Error("No configured model routes were available")},normalizeEvent:me,async collectArtifacts(e){await f(e.jobId,"log","agent-stdout.log",u(e.logsDir,"agent-stdout.log")),await f(e.jobId,"log","agent-stderr.log",u(e.logsDir,"agent-stderr.log"))}};async function D(e,t,r,i,s="text/plain"){let o=await w(`/api/runner/jobs/${e}/artifacts`,{method:"POST",body:JSON.stringify({kind:t,fileName:r,name:r,contentType:s,size:Buffer.byteLength(i)})}),a=n.object({uploadUrl:n.string(),headers:n.record(n.string(),n.string())}).parse(await o.json());await fetch(a.uploadUrl,{method:"PUT",headers:a.headers,body:i})}async function f(e,t,r,i){let s=await T(i,"utf8").catch(()=>"");s&&await D(e,t,r,s).catch(()=>{})}var P=class extends Error{constructor(r){super(`Job was already marked ${r} by MCP`);this.status=r}};function be(e){return!!(e&&X.has(e))}async function we(e){let t=await w(`/api/runner/jobs/${e}`),r=q.parse(await t.json());if(be(r.job.status))throw new P(r.job.status)}async function ve(e){let t=u(e,".codweft","result.json"),r=await T(t,"utf8").catch(s=>{if(s.code==="ENOENT")return null;throw s});if(r===null)return null;let i=de(r);return await B(t,{force:!0}),i}async function _(e,t,r,i=r){return(await d("git",t,{cwd:e.workspace,step:r,logPrefix:i,context:e})).stdout.trim()}async function ye(e){return(await _(e,["status","--porcelain","--",".",...$],"publish","git-publish")).length>0}async function ke(e,t){return(await _(e,["diff","--name-only",`${t}..HEAD`,"--",".",...$],"publish","git-publish")).length>0}async function Ee(e,t){let r=await _(e,oe(t),"publish","git-publish");if(r)throw new Error(`Agent committed runner control files, refusing to publish: ${r.split(/\r?\n/).join(", ")}`)}async function Pe(e,t){await ye(t)&&(await d("git",["config","user.name","codweft[bot]"],{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}),await d("git",["config","user.email","codweft[bot]@users.noreply.github.com"],{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}),await d("git",se(),{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}),await d("git",ie(`codweft: apply ${e.job.command} for ${e.job.jobId}`),{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}))}async function _e(e,t,r,i){let s=await fetch(e,{method:"POST",headers:{authorization:`Bearer ${t}`,"content-type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:`runner-${Date.now()}`,method:"tools/call",params:{name:r,arguments:i}})});if(!s.ok)throw new Error(`MCP ${r} failed: ${s.status} ${await s.text()}`);let o=n.object({error:n.object({message:n.string()}).optional(),result:n.object({content:n.array(n.object({type:n.string(),text:n.string()})).optional()}).optional()}).parse(await s.json());if(o.error)throw new Error(`MCP ${r} failed: ${o.error.message}`);let a=o.result?.content?.[0]?.text;return a?JSON.parse(a):{}}async function Re({payload:e,context:t,baseSha:r,gitExtraHeaderConfig:i,mcpToken:s}){let o=await ve(t.workspace),a=ue(e.job.command,e.job.instruction),c=!!o?.changeRequest;if(c&&!a)throw new Error(`${e.job.command} jobs are not allowed to publish change requests`);let l=I(e.job.jobId);await d("git",["checkout","-B",l],{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}),await Pe(e,t),await Ee(t,r);let g=await ke(t,r);if(!c){if(g)throw new Error("Agent made code changes but did not write .codweft/result.json with change request metadata");return null}if(!g)throw new Error("Agent requested a change request but produced no publishable code changes");await t.reporter.event({type:"publish_started",source:"runner",channel:"timeline",message:`publishing ${l}`,severity:"info",step:"publish",visibility:"user",redacted:!0},!0),await d("git",ne(l,i),{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t});let m=o?.changeRequest;if(!m)return null;let b=await _e(e.mcp.endpoint,s,"create_change_request",ae({jobId:e.job.jobId,baseRef:e.checkout.publishBaseRef,title:m.title,body:m.body,draft:m.draft})),h=n.object({number:n.number().optional(),html_url:n.string().optional(),url:n.string().optional()}).passthrough().parse(b);return await t.reporter.event({type:"change_request_created",source:"runner",channel:"result",message:h.html_url?`created change request ${h.html_url}`:`created change request${h.number?` #${h.number}`:""}`,severity:"info",step:"publish",visibility:"user",redacted:!0,metadata:{number:h.number,url:h.html_url??h.url}},!0),{changeRequest:{number:h.number,url:h.html_url??h.url,branch:l}}}async function je(){let e=j("CODWEFT_JOB_ID"),t=await w(`/api/runner/jobs/${e}`),r=q.parse(await t.json()),i=await z(u(U(),"codweft-")),s=u(i,"repo"),o=u(i,"logs");await R(s,{recursive:!0}),await R(o,{recursive:!0});let a=new S(e,u(o,"runner.log")),c={jobId:e,workspace:s,logsDir:o,reporter:a};try{await a.event({type:"runner_started",source:"runner",channel:"timeline",message:"runner started",severity:"info",step:"bootstrap",visibility:"user",redacted:!0},!0);let l=await ee(e),g=te({fullName:r.job.fullName,username:l.username,token:l.token});await d("git",["-c",g.extraHeaderConfig,"clone","--recurse-submodules",g.repoUrl,s],{step:"clone",logPrefix:"git-clone",context:c}),await d("git",["remote","set-url","origin",g.repoUrl],{cwd:s,step:"clone",logPrefix:"git-clone",context:c}),await Se(r,c,g.extraHeaderConfig);let m=await _(c,["rev-parse","HEAD"],"checkout","git-checkout"),b=await he(e);await E.install(c),await E.configureMcp(c,r.mcp.endpoint),await E.run(c,r,b),await we(e);let h=await Re({payload:r,context:c,baseSha:m,gitExtraHeaderConfig:g.extraHeaderConfig,mcpToken:b});await w(`/api/runner/jobs/${e}/result`,{method:"POST",body:JSON.stringify({status:"success",result:h??void 0})}),await a.event({type:"job_completed",source:"runner",channel:"result",message:"job completed successfully",severity:"info",step:"result",visibility:"user",redacted:!0},!0)}catch(l){let g=l instanceof Error?l.stack??l.message:String(l),m=l instanceof P?l.status:"failure";await a.event({type:"job_failed",source:"runner",channel:"result",message:g,severity:"error",step:"result",visibility:"user",redacted:!0},!0),await D(e,"log","runner-error.txt",y(g)).catch(()=>{}),await w(`/api/runner/jobs/${e}/result`,{method:"POST",body:JSON.stringify({status:m,failureReason:y(g).slice(0,16e3)})}).catch(()=>{}),process.exitCode=1}finally{await a.flush(),await f(e,"log","git-clone-stdout.log",u(o,"git-clone-stdout.log")),await f(e,"log","git-clone-stderr.log",u(o,"git-clone-stderr.log")),await f(e,"log","git-checkout-stdout.log",u(o,"git-checkout-stdout.log")),await f(e,"log","git-checkout-stderr.log",u(o,"git-checkout-stderr.log")),await f(e,"log","git-publish-stdout.log",u(o,"git-publish-stdout.log")),await f(e,"log","git-publish-stderr.log",u(o,"git-publish-stderr.log")),await f(e,"log","pi-install-stdout.log",u(o,"pi-install-stdout.log")),await f(e,"log","pi-install-stderr.log",u(o,"pi-install-stderr.log")),await f(e,"log","runner.log",u(o,"runner.log")),await E.collectArtifacts(c),await T(u(s,".mcp.json"),"utf8").catch(()=>"")}}async function Se(e,t,r){if(e.checkout.checkoutMode==="change_request_head"){if(!e.checkout.checkoutSha)throw new Error("Change request checkout is missing checkoutSha");if(!e.checkout.checkoutFetchRef)throw new Error("Change request checkout is missing checkoutFetchRef");await t.reporter.event({type:"checkout_prepared",source:"runner",channel:"timeline",message:`checking out change_request head ${e.checkout.checkoutSha}`,severity:"info",step:"checkout",visibility:"user",redacted:!0},!0),await d("git",re(e.checkout.checkoutFetchRef,r),{cwd:t.workspace,step:"checkout",logPrefix:"git-checkout",context:t}),await d("git",["checkout","--detach","FETCH_HEAD"],{cwd:t.workspace,step:"checkout",logPrefix:"git-checkout",context:t});let s=(await d("git",["rev-parse","HEAD"],{cwd:t.workspace,step:"checkout",logPrefix:"git-checkout",context:t})).stdout.trim();if(s!==e.checkout.checkoutSha)throw new Error(`Checkout SHA mismatch: expected ${e.checkout.checkoutSha}, got ${s}`);return}await t.reporter.event({type:"checkout_prepared",source:"runner",channel:"timeline",message:`checking out default branch ${e.checkout.checkoutRef}`,severity:"info",step:"checkout",visibility:"user",redacted:!0},!0),await d("git",["checkout",e.checkout.checkoutRef],{cwd:t.workspace,step:"checkout",logPrefix:"git-checkout",context:t})}process.argv[1]===W(import.meta.url)&&je().catch(e=>{console.error(y(e instanceof Error?e.stack??e.message:String(e))),process.exit(1)});export{Ee as assertNoCommittedControlFiles,le as buildAgentEnv,re as buildChangeRequestFetchArgs,oe as buildCommittedControlFilesDiffArgs,ae as buildCreateChangeRequestArgs,se as buildGitAddPublishArgs,ie as buildGitCommitArgs,te as buildGitHubCloneAuth,ne as buildGitPushArgs,I as buildPublishBranchName,x as buildSafeProcessEnv,ue as canPublishForCommand,me as normalizePiEvent,de as parseRunnerResultJson,y as redactText,d as runProcess,ce as taskExplicitlyRequestsChangeRequest}; | ||
| import{spawn as F}from"child_process";import{appendFile as M,mkdir as R,mkdtemp as z,readFile as T,rm as B,writeFile as J}from"fs/promises";import{tmpdir as U}from"os";import{dirname as L,join as u}from"path";import{fileURLToPath as W}from"url";import{z as n}from"zod";var A=n.union([n.literal("debug"),n.literal("info"),n.literal("warn"),n.literal("error")]),q=n.object({job:n.object({jobId:n.string(),commandId:n.string(),owner:n.string(),repo:n.string(),fullName:n.string(),defaultBranch:n.string(),command:n.string(),instruction:n.string(),status:n.union([n.literal("queued"),n.literal("in_progress"),n.literal("success"),n.literal("failure"),n.literal("cancelled"),n.literal("timed_out")]).optional(),workItemNumber:n.number().optional(),changeRequestNumber:n.number().optional()}),checkout:n.object({checkoutMode:n.union([n.literal("default_branch"),n.literal("change_request_head")]),checkoutRef:n.string().min(1),checkoutSha:n.string().min(1).optional(),checkoutFetchRef:n.string().min(1).optional(),baseRef:n.string().min(1).optional(),publishBaseRef:n.string().min(1)}),modelRoutingPlan:n.object({retryGroup:n.string(),routes:n.array(n.object({provider:n.string(),model:n.string(),secretName:n.string()}))}),mcp:n.object({endpoint:n.string().url()})}),N=16384,G=20,V=1e3,$=[":(exclude).codweft/result.json",":(exclude).mcp.json"],Z=[".codweft/result.json",".mcp.json"],K=["PATH","HOME","TMPDIR","USER","LANG","LC_ALL","CI"],X=new Set(["failure","cancelled","timed_out"]),Y=/(token|secret|password|api[_-]?key|private[_-]?key|bearer)/i;function S(e){let t=process.env[e];if(!t)throw new Error(`${e} is required`);return t}function y(e,t={}){let r=e.replace(/https:\/\/([^:\s/@]+):([^@\s]+)@github\.com/gi,"https://$1:[redacted]@github.com").replace(/authorization:\s*basic\s+[A-Za-z0-9+/=._~-]+/gi,"AUTHORIZATION: basic [redacted]").replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi,"Bearer [redacted]").replace(/(CODWEFT_(?:BOOTSTRAP|MCP)_TOKEN=)[^\s]+/g,"$1[redacted]");for(let[i,s]of Object.entries({...process.env,...t}))!s||s.length<8||!Y.test(i)||(r=r.split(s).join(`[redacted:${i}]`));return r}function Q(e){return Buffer.byteLength(e)<=N?e:`${e.slice(0,N-128)} | ||
| [truncated by codweft-runner]`}async function w(e,t){let r=S("CODWEFT_API_URL").replace(/\/$/,""),i=S("CODWEFT_BOOTSTRAP_TOKEN"),s=await fetch(`${r}${e}`,{...t,headers:{authorization:`Bearer ${i}`,"content-type":"application/json",...t?.headers}});if(!s.ok)throw new Error(`${e} failed: ${s.status} ${await s.text()}`);return s}var j=class{constructor(t,r){this.jobId=t;this.logPath=r}sequence=0;buffer=[];flushTimer=null;async event(t,r=!1){let i={...t,sequence:t.sequence??++this.sequence,message:Q(y(t.message)),redacted:!0};if(this.logPath&&await C(this.logPath,`${JSON.stringify({...i,createdAt:new Date().toISOString()})} | ||
| `).catch(()=>{}),this.buffer.push(i),r||this.buffer.length>=G||t.severity==="error"){await this.flush();return}this.scheduleFlush()}async timeline(t,r="info",i){await this.event({type:"runner",source:"runner",channel:"timeline",message:t,severity:r,step:i,visibility:r==="error"?"user":"debug",redacted:!0},r==="error")}async flush(){this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null);let t=this.buffer.splice(0,this.buffer.length);t.length!==0&&await w(`/api/runner/jobs/${this.jobId}/events`,{method:"POST",body:JSON.stringify({events:t})}).catch(()=>{})}scheduleFlush(){this.flushTimer||(this.flushTimer=setTimeout(()=>{this.flush().catch(()=>{})},V))}};async function ee(e){let t=await w(`/api/runner/jobs/${e}/forge-token`,{method:"POST"});return n.object({token:n.string(),username:n.string()}).parse(await t.json())}function te({fullName:e,username:t,token:r}){let i=`https://github.com/${e}.git`,s=Buffer.from(`${t}:${r}`).toString("base64");return{repoUrl:i,extraHeaderConfig:`http.https://github.com/.extraheader=AUTHORIZATION: basic ${s}`}}function re(e,t){let r=["fetch","origin",e,"--depth=1"];return t?["-c",t,...r]:r}function ne(e,t){return["-c",t,"-c","core.hooksPath=/dev/null","push","origin",`HEAD:refs/heads/${e}`,"--force-with-lease"]}function se(){return["add","-A","--",".",...$]}function ie(e){return["-c","core.hooksPath=/dev/null","commit","-m",e]}function oe(e){return["diff","--name-only",`${e}..HEAD`,"--",...Z]}function I(e){return`codweft/${e.replace(/[^A-Za-z0-9._-]/g,"-")}`}function ae({jobId:e,baseRef:t,title:r,body:i,draft:s}){return{title:r,body:i,draft:s,head:I(e),base:t}}function ce(e){return/\b(create|open|submit|raise)\b[\s\S]{0,80}\b(pr|pull request|change request)\b/i.test(e)}function ue(e,t){return["fix","implement","resolve_conflicts"].includes(e)?!0:e==="task"&&ce(t)}function x(e=process.env){let t={};for(let r of K){let i=e[r];i&&(t[r]=i)}return t.HOME&&(t.PATH=`${t.HOME}/.pi/bin${t.PATH?`:${t.PATH}`:""}`),t}function le({mcpToken:e,secretName:t,sourceEnv:r=process.env}){let i=x(r),s=r[t];if(!s)throw new Error(`${t} is not configured`);return i[t]=s,i.CODWEFT_MCP_TOKEN=e,i}var ge=n.object({changeRequest:n.object({title:n.string().min(1).max(256),body:n.string().max(65536).optional(),draft:n.boolean().optional()}).optional()});function de(e){return ge.parse(JSON.parse(e))}async function he(e){let t=await w(`/api/runner/jobs/${e}/mcp-token`,{method:"POST"});return n.object({mcpToken:n.string()}).parse(await t.json()).mcpToken}async function C(e,t){await R(L(e),{recursive:!0}),await M(e,t)}async function d(e,t,r){let i=r.source??"runner",s=y(`${e} ${t.join(" ")}`,r.env);await r.context.reporter.timeline(`running ${s}`,"info",r.step);let o=u(r.context.logsDir,`${r.logPrefix??r.step}-stdout.log`),a=u(r.context.logsDir,`${r.logPrefix??r.step}-stderr.log`),c="",l="",g=[];return new Promise((m,b)=>{async function h(){await Promise.allSettled(g)}let H=r.inheritEnv===!1||e==="git"&&r.inheritEnv!==!0?{...x(),...r.env}:{...process.env,...r.env},k=F(e,t,{cwd:r.cwd,env:H,stdio:["ignore","pipe","pipe"]});k.stdout?.on("data",v=>{let p=y(v.toString("utf8"),r.env);c+=p,process.stdout.write(p),g.push(Promise.all([C(o,p),O(r.context,{text:p,source:i,channel:"stdout",step:r.step,normalizeEvent:r.normalizeEvent})]))}),k.stderr?.on("data",v=>{let p=y(v.toString("utf8"),r.env);l+=p,process.stderr.write(p),g.push(Promise.all([C(a,p),O(r.context,{text:p,source:i,channel:"stderr",step:r.step,normalizeEvent:r.normalizeEvent})]))}),k.on("exit",v=>{let p=v??1;h().then(()=>{p===0?m({stdout:c,stderr:l,exitCode:p}):b(new Error(`${e} exited with ${p}`))}).catch(b)}),k.on("error",v=>{h().then(()=>b(v)).catch(b)})})}async function O(e,{text:t,source:r,channel:i,step:s,normalizeEvent:o}){let a=t.split(/\r?\n/).filter(c=>c.trim().length>0);for(let c of a.slice(-10)){let l=o?.(c,i);await e.reporter.event(l??{type:"process_output",source:r,channel:i,message:c,severity:i==="stderr"?"warn":"debug",step:s,visibility:"debug",redacted:!0})}}function pe(e){let t=e.job.changeRequestNumber!==void 0?`change_request #${e.job.changeRequestNumber}`:e.job.workItemNumber!==void 0?`work_item #${e.job.workItemNumber}`:"repository";return["You are Codweft's coding agent running inside a portable runner.","Use local filesystem, shell, and git for repository work.","Use the Codweft MCP server for all forge operations: comments, reviews, labels, checks, and change requests.","Do not call forge APIs directly when an MCP tool exists.","Do not push branches or commits. The runner owns publishing.",'When code changes should be published, leave the changes locally and write .codweft/result.json with {"changeRequest":{"title":"...","body":"...","draft":false}}.',"Do not call create_change_request for local code changes; the runner will push the branch first and then create it.",`Command: ${e.job.command}`,`Target: ${t}`,`Instruction: ${e.job.instruction}`,"","For free-form task commands, default to diagnosis or a plan unless the instruction explicitly asks for code changes or a change request.","Always report progress and final success or failure through Codweft MCP."].join(` | ||
| `)}async function fe(e,t){let r={mcpServers:{codweft:{url:t,auth:"bearer",bearerTokenEnv:"CODWEFT_MCP_TOKEN",lifecycle:"keep-alive",directTools:!0}}};await J(u(e,".mcp.json"),JSON.stringify(r,null,2))}function me(e,t){let r=e.trim();if(!r.startsWith("{")||!r.endsWith("}"))return null;let i;try{i=JSON.parse(r)}catch{return null}let s=n.object({type:n.string().optional(),message:n.string().optional(),content:n.string().optional(),level:A.optional(),severity:A.optional(),role:n.string().optional()}).passthrough().safeParse(i);if(!s.success)return null;let o=s.data.message??s.data.content;return o?{type:s.data.type??"agent_message",source:"agent",channel:s.data.role==="tool"?"tool_call":"agent_message",message:o,severity:s.data.severity??s.data.level??(t==="stderr"?"warn":"info"),visibility:"user",redacted:!0,metadata:{rawType:s.data.type}}:null}var E={async install(e){let t=x();await d("sh",["-lc","command -v pi >/dev/null 2>&1 || curl -fsSL https://pi.dev/install.sh | sh"],{cwd:e.workspace,env:t,inheritEnv:!1,step:"agent_install",logPrefix:"pi-install",context:e}),await d("pi",["install","npm:pi-mcp-adapter@2.5.4"],{cwd:e.workspace,env:t,inheritEnv:!1,step:"agent_install",logPrefix:"pi-install",context:e})},async configureMcp(e,t){await fe(e.workspace,t)},async run(e,t,r){let i=pe(t),s=null;for(let o of t.modelRoutingPlan.routes){if(!process.env[o.secretName]){await e.reporter.event({type:"model_route_skipped",source:"model_router",channel:"timeline",message:`skipping ${o.provider}/${o.model}: ${o.secretName} is not configured`,severity:"warn",step:"model_route",visibility:"debug",redacted:!0});continue}try{await e.reporter.event({type:"agent_started",source:"agent",channel:"timeline",message:`starting Pi with ${o.provider}/${o.model}`,severity:"info",step:"agent_run",visibility:"user",redacted:!0},!0),await d("pi",["--mode","json","--provider",o.provider,"--model",o.model,i],{cwd:e.workspace,env:le({mcpToken:r,secretName:o.secretName}),inheritEnv:!1,step:"agent_run",source:"agent",logPrefix:"agent",normalizeEvent:this.normalizeEvent,context:e});return}catch(a){s=a instanceof Error?a:new Error(String(a)),await e.reporter.event({type:"model_route_failed",source:"model_router",channel:"timeline",message:`model route failed: ${s.message}`,severity:"warn",step:"model_route",visibility:"user",redacted:!0},!0)}}throw s??new Error("No configured model routes were available")},normalizeEvent:me,async collectArtifacts(e){await f(e.jobId,"log","agent-stdout.log",u(e.logsDir,"agent-stdout.log")),await f(e.jobId,"log","agent-stderr.log",u(e.logsDir,"agent-stderr.log"))}};async function D(e,t,r,i,s="text/plain"){let o=await w(`/api/runner/jobs/${e}/artifacts`,{method:"POST",body:JSON.stringify({kind:t,fileName:r,name:r,contentType:s,size:Buffer.byteLength(i)})}),a=n.object({uploadUrl:n.string(),headers:n.record(n.string(),n.string())}).parse(await o.json());await fetch(a.uploadUrl,{method:"PUT",headers:a.headers,body:i})}async function f(e,t,r,i){let s=await T(i,"utf8").catch(()=>"");s&&await D(e,t,r,s).catch(()=>{})}var P=class extends Error{constructor(r){super(`Job was already marked ${r} by MCP`);this.status=r}};function be(e){return!!(e&&X.has(e))}async function we(e){let t=await w(`/api/runner/jobs/${e}`),r=q.parse(await t.json());if(be(r.job.status))throw new P(r.job.status)}async function ve(e){let t=u(e,".codweft","result.json"),r=await T(t,"utf8").catch(s=>{if(s.code==="ENOENT")return null;throw s});if(r===null)return null;let i=de(r);return await B(t,{force:!0}),i}async function _(e,t,r,i=r){return(await d("git",t,{cwd:e.workspace,step:r,logPrefix:i,context:e})).stdout.trim()}async function ye(e){return(await _(e,["status","--porcelain","--",".",...$],"publish","git-publish")).length>0}async function ke(e,t){return(await _(e,["diff","--name-only",`${t}..HEAD`,"--",".",...$],"publish","git-publish")).length>0}async function Ee(e,t){let r=await _(e,oe(t),"publish","git-publish");if(r)throw new Error(`Agent committed runner control files, refusing to publish: ${r.split(/\r?\n/).join(", ")}`)}async function Pe(e,t){await ye(t)&&(await d("git",["config","user.name","codweft[bot]"],{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}),await d("git",["config","user.email","codweft[bot]@users.noreply.github.com"],{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}),await d("git",se(),{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}),await d("git",ie(`codweft: apply ${e.job.command} for ${e.job.jobId}`),{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}))}async function _e(e,t,r,i){let s=await fetch(e,{method:"POST",headers:{authorization:`Bearer ${t}`,"content-type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:`runner-${Date.now()}`,method:"tools/call",params:{name:r,arguments:i}})});if(!s.ok)throw new Error(`MCP ${r} failed: ${s.status} ${await s.text()}`);let o=n.object({error:n.object({message:n.string()}).optional(),result:n.object({content:n.array(n.object({type:n.string(),text:n.string()})).optional()}).optional()}).parse(await s.json());if(o.error)throw new Error(`MCP ${r} failed: ${o.error.message}`);let a=o.result?.content?.[0]?.text;return a?JSON.parse(a):{}}async function Re({payload:e,context:t,baseSha:r,gitExtraHeaderConfig:i,mcpToken:s}){let o=await ve(t.workspace),a=ue(e.job.command,e.job.instruction),c=!!o?.changeRequest;if(c&&!a)throw new Error(`${e.job.command} jobs are not allowed to publish change requests`);let l=I(e.job.jobId);await d("git",["checkout","-B",l],{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}),await Pe(e,t),await Ee(t,r);let g=await ke(t,r);if(!c){if(g)throw new Error("Agent made code changes but did not write .codweft/result.json with change request metadata");return null}if(!g)throw new Error("Agent requested a change request but produced no publishable code changes");await t.reporter.event({type:"publish_started",source:"runner",channel:"timeline",message:`publishing ${l}`,severity:"info",step:"publish",visibility:"user",redacted:!0},!0),await d("git",ne(l,i),{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t});let m=o?.changeRequest;if(!m)return null;let b=await _e(e.mcp.endpoint,s,"create_change_request",ae({jobId:e.job.jobId,baseRef:e.checkout.publishBaseRef,title:m.title,body:m.body,draft:m.draft})),h=n.object({number:n.number().optional(),html_url:n.string().optional(),url:n.string().optional()}).passthrough().parse(b);return await t.reporter.event({type:"change_request_created",source:"runner",channel:"result",message:h.html_url?`created change request ${h.html_url}`:`created change request${h.number?` #${h.number}`:""}`,severity:"info",step:"publish",visibility:"user",redacted:!0,metadata:{number:h.number,url:h.html_url??h.url}},!0),{changeRequest:{number:h.number,url:h.html_url??h.url,branch:l}}}async function Se(){let e=S("CODWEFT_JOB_ID"),t=await w(`/api/runner/jobs/${e}`),r=q.parse(await t.json()),i=await z(u(U(),"codweft-")),s=u(i,"repo"),o=u(i,"logs");await R(s,{recursive:!0}),await R(o,{recursive:!0});let a=new j(e,u(o,"runner.log")),c={jobId:e,workspace:s,logsDir:o,reporter:a};try{await a.event({type:"runner_started",source:"runner",channel:"timeline",message:"runner started",severity:"info",step:"bootstrap",visibility:"user",redacted:!0},!0);let l=await ee(e),g=te({fullName:r.job.fullName,username:l.username,token:l.token});await d("git",["-c",g.extraHeaderConfig,"clone","--recurse-submodules",g.repoUrl,s],{step:"clone",logPrefix:"git-clone",context:c}),await d("git",["remote","set-url","origin",g.repoUrl],{cwd:s,step:"clone",logPrefix:"git-clone",context:c}),await je(r,c,g.extraHeaderConfig);let m=await _(c,["rev-parse","HEAD"],"checkout","git-checkout"),b=await he(e);await E.install(c),await E.configureMcp(c,r.mcp.endpoint),await E.run(c,r,b),await we(e);let h=await Re({payload:r,context:c,baseSha:m,gitExtraHeaderConfig:g.extraHeaderConfig,mcpToken:b});await w(`/api/runner/jobs/${e}/result`,{method:"POST",body:JSON.stringify({status:"success",result:h??void 0})}),await a.event({type:"job_completed",source:"runner",channel:"result",message:"job completed successfully",severity:"info",step:"result",visibility:"user",redacted:!0},!0)}catch(l){let g=l instanceof Error?l.stack??l.message:String(l),m=l instanceof P?l.status:"failure";await a.event({type:"job_failed",source:"runner",channel:"result",message:g,severity:"error",step:"result",visibility:"user",redacted:!0},!0),await D(e,"log","runner-error.txt",y(g)).catch(()=>{}),await w(`/api/runner/jobs/${e}/result`,{method:"POST",body:JSON.stringify({status:m,failureReason:y(g).slice(0,16e3)})}).catch(()=>{}),process.exitCode=1}finally{await a.flush(),await f(e,"log","git-clone-stdout.log",u(o,"git-clone-stdout.log")),await f(e,"log","git-clone-stderr.log",u(o,"git-clone-stderr.log")),await f(e,"log","git-checkout-stdout.log",u(o,"git-checkout-stdout.log")),await f(e,"log","git-checkout-stderr.log",u(o,"git-checkout-stderr.log")),await f(e,"log","git-publish-stdout.log",u(o,"git-publish-stdout.log")),await f(e,"log","git-publish-stderr.log",u(o,"git-publish-stderr.log")),await f(e,"log","pi-install-stdout.log",u(o,"pi-install-stdout.log")),await f(e,"log","pi-install-stderr.log",u(o,"pi-install-stderr.log")),await f(e,"log","runner.log",u(o,"runner.log")),await E.collectArtifacts(c),await T(u(s,".mcp.json"),"utf8").catch(()=>"")}}async function je(e,t,r){if(e.checkout.checkoutMode==="change_request_head"){if(!e.checkout.checkoutSha)throw new Error("Change request checkout is missing checkoutSha");if(!e.checkout.checkoutFetchRef)throw new Error("Change request checkout is missing checkoutFetchRef");await t.reporter.event({type:"checkout_prepared",source:"runner",channel:"timeline",message:`checking out change_request head ${e.checkout.checkoutSha}`,severity:"info",step:"checkout",visibility:"user",redacted:!0},!0),await d("git",re(e.checkout.checkoutFetchRef,r),{cwd:t.workspace,step:"checkout",logPrefix:"git-checkout",context:t}),await d("git",["checkout","--detach","FETCH_HEAD"],{cwd:t.workspace,step:"checkout",logPrefix:"git-checkout",context:t});let s=(await d("git",["rev-parse","HEAD"],{cwd:t.workspace,step:"checkout",logPrefix:"git-checkout",context:t})).stdout.trim();if(s!==e.checkout.checkoutSha)throw new Error(`Checkout SHA mismatch: expected ${e.checkout.checkoutSha}, got ${s}`);return}await t.reporter.event({type:"checkout_prepared",source:"runner",channel:"timeline",message:`checking out default branch ${e.checkout.checkoutRef}`,severity:"info",step:"checkout",visibility:"user",redacted:!0},!0),await d("git",["checkout",e.checkout.checkoutRef],{cwd:t.workspace,step:"checkout",logPrefix:"git-checkout",context:t})}process.argv[1]===W(import.meta.url)&&Se().catch(e=>{console.error(y(e instanceof Error?e.stack??e.message:String(e))),process.exit(1)});export{Ee as assertNoCommittedControlFiles,le as buildAgentEnv,re as buildChangeRequestFetchArgs,oe as buildCommittedControlFilesDiffArgs,ae as buildCreateChangeRequestArgs,se as buildGitAddPublishArgs,ie as buildGitCommitArgs,te as buildGitHubCloneAuth,ne as buildGitPushArgs,I as buildPublishBranchName,x as buildSafeProcessEnv,ue as canPublishForCommand,me as normalizePiEvent,de as parseRunnerResultJson,y as redactText,d as runProcess,ce as taskExplicitlyRequestsChangeRequest}; |
+1
-1
| { | ||
| "name": "@codweft/runner", | ||
| "version": "0.4.1", | ||
| "version": "0.4.2", | ||
| "type": "module", | ||
@@ -5,0 +5,0 @@ "bin": { |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
23928
0.5%179
0.56%8
-11.11%