@codweft/runner
Advanced tools
+81
-8
@@ -5,2 +5,46 @@ #!/usr/bin/env node | ||
| declare const eventSeveritySchema: z.ZodUnion<readonly [z.ZodLiteral<"debug">, z.ZodLiteral<"info">, z.ZodLiteral<"warn">, z.ZodLiteral<"error">]>; | ||
| declare const jobSchema: z.ZodObject<{ | ||
| job: z.ZodObject<{ | ||
| jobId: z.ZodString; | ||
| commandId: z.ZodString; | ||
| owner: z.ZodString; | ||
| repo: z.ZodString; | ||
| fullName: z.ZodString; | ||
| defaultBranch: z.ZodString; | ||
| command: z.ZodString; | ||
| instruction: z.ZodString; | ||
| outputSchema: z.ZodOptional<z.ZodUnknown>; | ||
| status: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"queued">, z.ZodLiteral<"in_progress">, z.ZodLiteral<"success">, z.ZodLiteral<"failure">, z.ZodLiteral<"cancelled">, z.ZodLiteral<"timed_out">]>>; | ||
| result: z.ZodOptional<z.ZodUnknown>; | ||
| workItemNumber: z.ZodOptional<z.ZodNumber>; | ||
| changeRequestNumber: z.ZodOptional<z.ZodNumber>; | ||
| }, z.core.$strip>; | ||
| checkout: z.ZodObject<{ | ||
| checkoutMode: z.ZodUnion<readonly [z.ZodLiteral<"default_branch">, z.ZodLiteral<"change_request_head">]>; | ||
| checkoutRef: z.ZodString; | ||
| checkoutSha: z.ZodOptional<z.ZodString>; | ||
| checkoutFetchRef: z.ZodOptional<z.ZodString>; | ||
| baseRef: z.ZodOptional<z.ZodString>; | ||
| publishBaseRef: z.ZodString; | ||
| }, z.core.$strip>; | ||
| modelRoutingPlan: z.ZodObject<{ | ||
| retryGroup: z.ZodString; | ||
| routes: z.ZodArray<z.ZodObject<{ | ||
| agent: z.ZodDefault<z.ZodEnum<{ | ||
| pi: "pi"; | ||
| codex: "codex"; | ||
| opencode: "opencode"; | ||
| }>>; | ||
| provider: z.ZodString; | ||
| model: z.ZodString; | ||
| credentialId: z.ZodString; | ||
| credentialName: z.ZodOptional<z.ZodString>; | ||
| secretName: z.ZodString; | ||
| }, z.core.$strip>>; | ||
| }, z.core.$strip>; | ||
| mcp: z.ZodObject<{ | ||
| endpoint: z.ZodString; | ||
| }, z.core.$strip>; | ||
| }, z.core.$strip>; | ||
| type JobPayload = z.infer<typeof jobSchema>; | ||
| type EventSource = "runner" | "agent" | "mcp" | "compute" | "model_router"; | ||
@@ -27,2 +71,3 @@ type EventChannel = "timeline" | "stdout" | "stderr" | "agent_message" | "tool_call" | "result"; | ||
| }; | ||
| type ModelRoute = JobPayload["modelRoutingPlan"]["routes"][number]; | ||
| type RunnerContext = { | ||
@@ -57,12 +102,9 @@ jobId: string; | ||
| } | ||
| declare function buildGitHubCloneAuth({ fullName, username, token, }: { | ||
| declare function buildGitHubCloneAuth({ fullName, }: { | ||
| fullName: string; | ||
| username: string; | ||
| token: string; | ||
| }): { | ||
| repoUrl: string; | ||
| extraHeaderConfig: string; | ||
| }; | ||
| declare function buildChangeRequestFetchArgs(checkoutFetchRef: string, gitExtraHeaderConfig?: string): string[]; | ||
| declare function buildGitPushArgs(branchName: string, gitExtraHeaderConfig: string): string[]; | ||
| declare function buildChangeRequestFetchArgs(checkoutFetchRef: string): string[]; | ||
| declare function buildGitPushArgs(branchName: string): string[]; | ||
| declare function buildGitAddPublishArgs(): string[]; | ||
@@ -88,6 +130,7 @@ declare function buildGitCommitArgs(message: string): string[]; | ||
| declare function buildSafeProcessEnv(sourceEnv?: Record<string, string | undefined>): Record<string, string>; | ||
| declare function buildAgentEnv({ mcpToken, secretName, secretValue, sourceEnv, }: { | ||
| declare function buildAgentEnv({ mcpToken, secretName, secretValue, secretAliases, sourceEnv, }: { | ||
| mcpToken: string; | ||
| secretName: string; | ||
| secretValue: string | undefined; | ||
| secretAliases?: string[]; | ||
| sourceEnv?: Record<string, string | undefined>; | ||
@@ -104,4 +147,34 @@ }): Record<string, string>; | ||
| normalizeEvent?: (line: string, channel: "stdout" | "stderr") => RunnerEvent | null | false; | ||
| idleTimeoutMs?: number; | ||
| maxTimeoutMs?: number; | ||
| context: RunnerContext; | ||
| }): Promise<ProcessCapture>; | ||
| declare function openCodeProviderEnvName(route: Pick<ModelRoute, "provider" | "secretName">): string; | ||
| declare function openCodeSecretAliases(route: Pick<ModelRoute, "provider" | "secretName">): string[]; | ||
| declare function buildOpenCodeInstallCommand(): string; | ||
| declare function buildOpenCodeConfig({ endpoint, mcpToken, route, }: { | ||
| endpoint: string; | ||
| mcpToken: string; | ||
| route: Pick<ModelRoute, "provider" | "secretName">; | ||
| }): { | ||
| $schema: string; | ||
| enabled_providers: string[]; | ||
| provider: { | ||
| [route.provider]: { | ||
| options: { | ||
| apiKey: string; | ||
| }; | ||
| }; | ||
| }; | ||
| mcp: { | ||
| codweft: { | ||
| type: string; | ||
| url: string; | ||
| enabled: boolean; | ||
| headers: { | ||
| Authorization: string; | ||
| }; | ||
| }; | ||
| }; | ||
| }; | ||
| declare function normalizePiEvent(line: string, channel: "stdout" | "stderr"): RunnerEvent | null | false; | ||
@@ -111,2 +184,2 @@ declare function assertNoCommittedControlFiles(context: RunnerContext, baseSha: string): Promise<void>; | ||
| export { assertNoCommittedControlFiles, buildAgentEnv, buildChangeRequestFetchArgs, buildCommittedControlFilesDiffArgs, buildCreateChangeRequestArgs, buildGitAddPublishArgs, buildGitCommitArgs, buildGitHubCloneAuth, buildGitPushArgs, buildPublishBranchName, buildSafeProcessEnv, canPublishForCommand, isCliEntrypoint, normalizePiEvent, parseRunnerResultJson, redactText, runProcess, taskExplicitlyRequestsChangeRequest }; | ||
| export { assertNoCommittedControlFiles, buildAgentEnv, buildChangeRequestFetchArgs, buildCommittedControlFilesDiffArgs, buildCreateChangeRequestArgs, buildGitAddPublishArgs, buildGitCommitArgs, buildGitHubCloneAuth, buildGitPushArgs, buildOpenCodeConfig, buildOpenCodeInstallCommand, buildPublishBranchName, buildSafeProcessEnv, canPublishForCommand, isCliEntrypoint, normalizePiEvent, openCodeProviderEnvName, openCodeSecretAliases, parseRunnerResultJson, redactText, runProcess, taskExplicitlyRequestsChangeRequest }; |
+22
-4
| #!/usr/bin/env node | ||
| import{spawn as M}from"child_process";import{realpathSync as N}from"fs";import{appendFile as J,mkdir as R,mkdtemp as z,readFile as T,rm as B,writeFile as U}from"fs/promises";import{tmpdir as L}from"os";import{dirname as W,join as l}from"path";import{fileURLToPath as G}from"url";import{z as n}from"zod";var A=n.union([n.literal("debug"),n.literal("info"),n.literal("warn"),n.literal("error")]),I=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(),credentialId:n.string(),credentialName:n.string().optional(),secretName:n.string()}))}),mcp:n.object({endpoint:n.string().url()})}),O=16384,V=20,Z=1e3,$=[":(exclude).codweft/result.json",":(exclude).mcp.json"],K=[".codweft/result.json",".mcp.json"],X=["PATH","HOME","TMPDIR","USER","LANG","LC_ALL","CI"],Y=new Set(["failure","cancelled","timed_out"]),Q=/(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 v(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||!Q.test(i)||(r=r.split(s).join(`[redacted:${i}]`));return r}function ee(e){return Buffer.byteLength(e)<=O?e:`${e.slice(0,O-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}jobId;logPath;sequence=0;buffer=[];flushTimer=null;async event(t,r=!1){let i={...t,sequence:t.sequence??++this.sequence,message:ee(v(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>=V||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(()=>{})},Z))}};async function te(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 re({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 ne(e,t){let r=["fetch","origin",e,"--depth=1"];return t?["-c",t,...r]:r}function se(e,t){return["-c",t,"-c","core.hooksPath=/dev/null","push","origin",`HEAD:refs/heads/${e}`,"--force-with-lease"]}function ie(){return["add","-A","--",".",...$]}function oe(e){return["-c","core.hooksPath=/dev/null","commit","-m",e]}function ae(e){return["diff","--name-only",`${e}..HEAD`,"--",...K]}function D(e){return`codweft/${e.replace(/[^A-Za-z0-9._-]/g,"-")}`}function ce({jobId:e,baseRef:t,title:r,body:i,draft:s}){return{title:r,body:i,draft:s,head:D(e),base:t}}function ue(e){return/\b(create|open|submit|raise)\b[\s\S]{0,80}\b(pr|pull request|change request)\b/i.test(e)}function le(e,t){return["fix","implement","resolve_conflicts"].includes(e)?!0:e==="task"&&ue(t)}function x(e=process.env){let t={};for(let r of X){let i=e[r];i&&(t[r]=i)}return t.HOME&&(t.PATH=`${t.HOME}/.pi/bin${t.PATH?`:${t.PATH}`:""}`),t}function de({mcpToken:e,secretName:t,secretValue:r,sourceEnv:i=process.env}){let s=x(i);if(!r)throw new Error(`${t} is not configured`);return s[t]=r,s.CODWEFT_MCP_TOKEN=e,s}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 pe(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 fe(e,t){let r=await w(`/api/runner/jobs/${e}/provider-secret`,{method:"POST",body:JSON.stringify({credentialId:t})});return n.object({configured:n.boolean(),credentialId:n.string().optional(),credentialName:n.string().optional(),secretName:n.string().optional(),value:n.string().optional()}).parse(await r.json())}async function C(e,t){await R(W(e),{recursive:!0}),await J(e,t)}async function g(e,t,r){let i=r.source??"runner",s=v(`${e} ${t.join(" ")}`,r.env);await r.context.reporter.timeline(`running ${s}`,"info",r.step);let o=l(r.context.logsDir,`${r.logPrefix??r.step}-stdout.log`),a=l(r.context.logsDir,`${r.logPrefix??r.step}-stderr.log`),c="",u="",d=[];return new Promise((m,b)=>{async function p(){await Promise.allSettled(d)}let F=r.inheritEnv===!1||e==="git"&&r.inheritEnv!==!0?{...x(),...r.env}:{...process.env,...r.env},k=M(e,t,{cwd:r.cwd,env:F,stdio:["ignore","pipe","pipe"]});k.stdout?.on("data",y=>{let h=v(y.toString("utf8"),r.env);c+=h,process.stdout.write(h),d.push(Promise.all([C(o,h),q(r.context,{text:h,source:i,channel:"stdout",step:r.step,normalizeEvent:r.normalizeEvent})]))}),k.stderr?.on("data",y=>{let h=v(y.toString("utf8"),r.env);u+=h,process.stderr.write(h),d.push(Promise.all([C(a,h),q(r.context,{text:h,source:i,channel:"stderr",step:r.step,normalizeEvent:r.normalizeEvent})]))}),k.on("exit",y=>{let h=y??1;p().then(()=>{h===0?m({stdout:c,stderr:u,exitCode:h}):b(new Error(`${e} exited with ${h}`))}).catch(b)}),k.on("error",y=>{p().then(()=>b(y)).catch(b)})})}async function q(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 u=o?.(c,i);u!==!1&&await e.reporter.event(u??{type:"process_output",source:r,channel:i,message:c,severity:i==="stderr"?"warn":"debug",step:s,visibility:"debug",redacted:!0})}}function me(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 be(e,t){let r={mcpServers:{codweft:{url:t,auth:"bearer",bearerTokenEnv:"CODWEFT_MCP_TOKEN",lifecycle:"keep-alive",directTools:!0}}};await U(l(e,".mcp.json"),JSON.stringify(r,null,2))}function we(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(),toolCallId:n.string().optional(),toolName:n.string().optional(),level:A.optional(),severity:A.optional(),role:n.string().optional()}).passthrough().safeParse(i);if(!s.success)return null;if(s.data.type==="tool_execution_start")return{type:"tool_execution_start",source:"agent",channel:"tool_call",message:s.data.toolName?`starting tool ${s.data.toolName}`:"starting tool call",severity:"info",step:"agent_run",visibility:"debug",redacted:!0,metadata:{toolCallId:s.data.toolCallId,toolName:s.data.toolName}};if(s.data.type==="tool_execution_end")return{type:"tool_execution_end",source:"agent",channel:"tool_call",message:s.data.toolName?`completed tool ${s.data.toolName}`:"completed tool call",severity:"info",step:"agent_run",visibility:"debug",redacted:!0,metadata:{toolCallId:s.data.toolCallId,toolName:s.data.toolName}};if(s.data.type==="session"||s.data.type==="agent_start"||s.data.type==="turn_start"||s.data.type==="turn_end"||s.data.type==="message_start"||s.data.type==="message_update"||s.data.type==="message_end")return!1;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 g("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 g("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 be(e.workspace,t)},async run(e,t,r){let i=me(t),s=null;for(let o of t.modelRoutingPlan.routes)try{let a=await fe(t.job.jobId,o.credentialId);if(!a.configured||!a.value){await e.reporter.event({type:"model_route_skipped",source:"model_router",channel:"timeline",message:`skipping ${o.provider}/${o.model}: credential ${o.credentialName??o.credentialId} is not configured`,severity:"warn",step:"model_route",visibility:"debug",redacted:!0});continue}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 g("pi",["--mode","json","--provider",o.provider,"--model",o.model,i],{cwd:e.workspace,env:de({mcpToken:r,secretName:a.secretName??o.secretName,secretValue:a.value}),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:we,async collectArtifacts(e){await f(e.jobId,"log","agent-stdout.log",l(e.logsDir,"agent-stdout.log")),await f(e.jobId,"log","agent-stderr.log",l(e.logsDir,"agent-stderr.log"))}};async function H(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 H(e,t,r,s).catch(()=>{})}var _=class extends Error{constructor(r){super(`Job was already marked ${r} by MCP`);this.status=r}status};function ye(e){return!!(e&&Y.has(e))}async function ve(e){let t=await w(`/api/runner/jobs/${e}`),r=I.parse(await t.json());if(ye(r.job.status))throw new _(r.job.status)}async function ke(e){let t=l(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=pe(r);return await B(t,{force:!0}),i}async function P(e,t,r,i=r){return(await g("git",t,{cwd:e.workspace,step:r,logPrefix:i,context:e})).stdout.trim()}async function Ee(e){return(await P(e,["status","--porcelain","--",".",...$],"publish","git-publish")).length>0}async function _e(e,t){return(await P(e,["diff","--name-only",`${t}..HEAD`,"--",".",...$],"publish","git-publish")).length>0}async function Pe(e,t){let r=await P(e,ae(t),"publish","git-publish");if(r)throw new Error(`Agent committed runner control files, refusing to publish: ${r.split(/\r?\n/).join(", ")}`)}async function Re(e,t){await Ee(t)&&(await g("git",["config","user.name","codweft[bot]"],{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}),await g("git",["config","user.email","codweft[bot]@users.noreply.github.com"],{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}),await g("git",ie(),{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}),await g("git",oe(`codweft: apply ${e.job.command} for ${e.job.jobId}`),{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}))}async function Se(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 je({payload:e,context:t,baseSha:r,gitExtraHeaderConfig:i,mcpToken:s}){let o=await ke(t.workspace),a=le(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 u=D(e.job.jobId);await g("git",["checkout","-B",u],{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}),await Re(e,t),await Pe(t,r);let d=await _e(t,r);if(!c){if(d)throw new Error("Agent made code changes but did not write .codweft/result.json with change request metadata");return null}if(!d)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 ${u}`,severity:"info",step:"publish",visibility:"user",redacted:!0},!0),await g("git",se(u,i),{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t});let m=o?.changeRequest;if(!m)return null;let b=await Se(e.mcp.endpoint,s,"create_change_request",ce({jobId:e.job.jobId,baseRef:e.checkout.publishBaseRef,title:m.title,body:m.body,draft:m.draft})),p=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:p.html_url?`created change request ${p.html_url}`:`created change request${p.number?` #${p.number}`:""}`,severity:"info",step:"publish",visibility:"user",redacted:!0,metadata:{number:p.number,url:p.html_url??p.url}},!0),{changeRequest:{number:p.number,url:p.html_url??p.url,branch:u}}}async function Ce(){let e=S("CODWEFT_JOB_ID"),t=await w(`/api/runner/jobs/${e}`),r=I.parse(await t.json()),i=await z(l(L(),"codweft-")),s=l(i,"repo"),o=l(i,"logs");await R(s,{recursive:!0}),await R(o,{recursive:!0});let a=new j(e,l(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 u=await te(e),d=re({fullName:r.job.fullName,username:u.username,token:u.token});await g("git",["-c",d.extraHeaderConfig,"clone","--recurse-submodules",d.repoUrl,s],{step:"clone",logPrefix:"git-clone",context:c}),await g("git",["remote","set-url","origin",d.repoUrl],{cwd:s,step:"clone",logPrefix:"git-clone",context:c}),await Te(r,c,d.extraHeaderConfig);let m=await P(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 ve(e);let p=await je({payload:r,context:c,baseSha:m,gitExtraHeaderConfig:d.extraHeaderConfig,mcpToken:b});await w(`/api/runner/jobs/${e}/result`,{method:"POST",body:JSON.stringify({status:"success",result:p??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(u){let d=u instanceof Error?u.stack??u.message:String(u),m=u instanceof _?u.status:"failure";await a.event({type:"job_failed",source:"runner",channel:"result",message:d,severity:"error",step:"result",visibility:"user",redacted:!0},!0),await H(e,"log","runner-error.txt",v(d)).catch(()=>{}),await w(`/api/runner/jobs/${e}/result`,{method:"POST",body:JSON.stringify({status:m,failureReason:v(d).slice(0,16e3)})}).catch(()=>{}),process.exitCode=1}finally{await a.flush(),await f(e,"log","git-clone-stdout.log",l(o,"git-clone-stdout.log")),await f(e,"log","git-clone-stderr.log",l(o,"git-clone-stderr.log")),await f(e,"log","git-checkout-stdout.log",l(o,"git-checkout-stdout.log")),await f(e,"log","git-checkout-stderr.log",l(o,"git-checkout-stderr.log")),await f(e,"log","git-publish-stdout.log",l(o,"git-publish-stdout.log")),await f(e,"log","git-publish-stderr.log",l(o,"git-publish-stderr.log")),await f(e,"log","pi-install-stdout.log",l(o,"pi-install-stdout.log")),await f(e,"log","pi-install-stderr.log",l(o,"pi-install-stderr.log")),await f(e,"log","runner.log",l(o,"runner.log")),await E.collectArtifacts(c),await T(l(s,".mcp.json"),"utf8").catch(()=>"")}}async function Te(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 g("git",ne(e.checkout.checkoutFetchRef,r),{cwd:t.workspace,step:"checkout",logPrefix:"git-checkout",context:t}),await g("git",["checkout","--detach","FETCH_HEAD"],{cwd:t.workspace,step:"checkout",logPrefix:"git-checkout",context:t});let s=(await g("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 g("git",["checkout",e.checkout.checkoutRef],{cwd:t.workspace,step:"checkout",logPrefix:"git-checkout",context:t})}function $e(e=process.argv[1],t=import.meta.url){let r=G(t);if(!e)return!1;try{return N(e)===N(r)}catch{return e===r}}$e()&&Ce().catch(e=>{console.error(v(e instanceof Error?e.stack??e.message:String(e))),process.exit(1)});export{Pe as assertNoCommittedControlFiles,de as buildAgentEnv,ne as buildChangeRequestFetchArgs,ae as buildCommittedControlFilesDiffArgs,ce as buildCreateChangeRequestArgs,ie as buildGitAddPublishArgs,oe as buildGitCommitArgs,re as buildGitHubCloneAuth,se as buildGitPushArgs,D as buildPublishBranchName,x as buildSafeProcessEnv,le as canPublishForCommand,$e as isCliEntrypoint,we as normalizePiEvent,pe as parseRunnerResultJson,v as redactText,g as runProcess,ue as taskExplicitlyRequestsChangeRequest}; | ||
| import{spawn as ge}from"child_process";import{randomUUID as V}from"crypto";import{createServer as pe}from"http";import{realpathSync as Z}from"fs";import{appendFile as fe,chmod as U,mkdir as j,mkdtemp as me,readFile as J,rm as te,writeFile as I}from"fs/promises";import{tmpdir as he}from"os";import{dirname as we,join as u}from"path";import{fileURLToPath as be}from"url";import{z as o}from"zod";var ye=["pi","codex","opencode"],$=o.union([o.literal("debug"),o.literal("info"),o.literal("warn"),o.literal("error")]),ne=o.object({job:o.object({jobId:o.string(),commandId:o.string(),owner:o.string(),repo:o.string(),fullName:o.string(),defaultBranch:o.string(),command:o.string(),instruction:o.string(),outputSchema:o.unknown().optional(),status:o.union([o.literal("queued"),o.literal("in_progress"),o.literal("success"),o.literal("failure"),o.literal("cancelled"),o.literal("timed_out")]).optional(),result:o.unknown().optional(),workItemNumber:o.number().optional(),changeRequestNumber:o.number().optional()}),checkout:o.object({checkoutMode:o.union([o.literal("default_branch"),o.literal("change_request_head")]),checkoutRef:o.string().min(1),checkoutSha:o.string().min(1).optional(),checkoutFetchRef:o.string().min(1).optional(),baseRef:o.string().min(1).optional(),publishBaseRef:o.string().min(1)}),modelRoutingPlan:o.object({retryGroup:o.string(),routes:o.array(o.object({agent:o.enum(ye).default("pi"),provider:o.string(),model:o.string(),credentialId:o.string(),credentialName:o.string().optional(),secretName:o.string()}))}),mcp:o.object({endpoint:o.string().url()})}),X=16384,ve=20,ke=1e3,Ee=300*1e3,_e=7200*1e3,Pe=1e3,Y=300*1e3,Se="opencode-ai@1.17.10",F=[":(exclude).codweft/result.json",":(exclude).mcp.json"],Te=[".codweft/result.json",".mcp.json"],Ae=["PATH","HOME","TMPDIR","USER","LANG","LC_ALL","CI"],Re=new Set(["failure","cancelled","timed_out"]),Ce=/(token|secret|password|api[_-]?key|private[_-]?key|bearer)/i,A=new Set;function M(e){let t=process.env[e];if(!t)throw new Error(`${e} is required`);return t}function T(e,t={}){let n=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[s,r]of Object.entries({...process.env,...t}))!r||r.length<8||!Ce.test(s)||(n=n.split(r).join(`[redacted:${s}]`));return n}function je(e){return Buffer.byteLength(e)<=X?e:`${e.slice(0,X-128)} | ||
| [truncated by codweft-runner]`}async function S(e,t){let n=M("CODWEFT_API_URL").replace(/\/$/,""),s=M("CODWEFT_BOOTSTRAP_TOKEN"),r=await fetch(`${n}${e}`,{...t,headers:{authorization:`Bearer ${s}`,"content-type":"application/json",...t?.headers}});if(!r.ok)throw new Error(`${e} failed: ${r.status} ${await r.text()}`);return r}var D=class{constructor(t,n){this.jobId=t;this.logPath=n}jobId;logPath;sequence=0;buffer=[];flushTimer=null;async event(t,n=!1){let s={...t,sequence:t.sequence??++this.sequence,message:je(T(t.message)),redacted:!0};if(this.logPath&&await q(this.logPath,`${JSON.stringify({...s,createdAt:new Date().toISOString()})} | ||
| `).catch(()=>{}),this.buffer.push(s),n||this.buffer.length>=ve||t.severity==="error"){await this.flush();return}this.scheduleFlush()}async timeline(t,n="info",s){await this.event({type:"runner",source:"runner",channel:"timeline",message:t,severity:n,step:s,visibility:n==="error"?"user":"debug",redacted:!0},n==="error")}async flush(){this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null);let t=this.buffer.splice(0,this.buffer.length);t.length!==0&&await S(`/api/runner/jobs/${this.jobId}/events`,{method:"POST",body:JSON.stringify({events:t})}).catch(()=>{})}scheduleFlush(){this.flushTimer||(this.flushTimer=setTimeout(()=>{this.flush().catch(()=>{})},ke))}};async function Ne(e){let t=await S(`/api/runner/jobs/${e}/forge-token`,{method:"POST"});return o.object({token:o.string(),username:o.string()}).parse(await t.json())}function xe({fullName:e}){return{repoUrl:`https://github.com/${e}.git`}}function $e(e){return["fetch","origin",e,"--depth=1"]}function Ie(e){return["-c","core.hooksPath=/dev/null","push","origin",`HEAD:refs/heads/${e}`,"--force-with-lease"]}async function Oe(e){await fetch("https://api.github.com/installation/token",{method:"DELETE",headers:{authorization:`Bearer ${e}`,accept:"application/vnd.github+json","user-agent":"codweft-runner"}}).catch(()=>{})}async function Me(e){let t=new Map,n=new Map,s=pe(async(d,l)=>{let f=new URL(d.url??"/","http://127.0.0.1"),[p,g]=f.pathname.split("/").filter(Boolean);if(p==="username"){let _=t.get(g);if(!_||Date.now()>_.expiresAt){l.writeHead(404).end("not found");return}l.writeHead(200,{"content-type":"text/plain"}).end(_.username);return}if(p==="mint"){let _=t.get(g);if(!_||Date.now()>_.expiresAt){l.writeHead(404).end("not found");return}let R=V();n.set(R,{registrationKey:g,expiresAt:Date.now()+Y,used:!1}),l.writeHead(200,{"content-type":"text/plain"}).end(R);return}if(p!=="token"){l.writeHead(404).end("not found");return}let v=g,y=n.get(v);if(!y||Date.now()>y.expiresAt){l.writeHead(404).end("not found");return}let E=t.get(y.registrationKey);if(!E||Date.now()>E.expiresAt){n.delete(v),l.writeHead(404).end("not found");return}if(y.used){await Oe(E.token),t.delete(y.registrationKey),n.delete(v),l.writeHead(409).end("token code reused");return}y.used=!0,l.writeHead(200,{"content-type":"text/plain"}).end(E.token)});await new Promise((d,l)=>{s.once("error",l),s.listen(0,"127.0.0.1",()=>{s.off("error",l),d()})});let r=s.address();if(!r||typeof r=="string")throw new Error("Unable to start local Git auth server");let i=r.port;async function a({token:d,username:l}){let f=V();t.set(f,{token:d,username:l,expiresAt:Date.now()+Y});let p=u(e,`git-askpass-${f}.mjs`),g=`#!/usr/bin/env node | ||
| const prompt = process.argv.slice(2).join(" "); | ||
| const baseUrl = ${JSON.stringify(`http://127.0.0.1:${i}`)}; | ||
| const registrationKey = ${JSON.stringify(f)}; | ||
| async function requestText(path) { | ||
| const response = await fetch(baseUrl + path); | ||
| if (!response.ok) process.exit(1); | ||
| return response.text(); | ||
| } | ||
| if (/username/i.test(prompt)) { | ||
| process.stdout.write(await requestText("/username/" + registrationKey)); | ||
| process.exit(0); | ||
| } | ||
| const code = await requestText("/mint/" + registrationKey); | ||
| process.stdout.write(await requestText("/token/" + code)); | ||
| `;return await I(p,g,{mode:448}),await U(p,448),{GIT_ASKPASS:p,GIT_TERMINAL_PROMPT:"0"}}async function c(){await new Promise(d=>{s.close(()=>d())})}return{register:a,close:c}}function De(){return["add","-A","--",".",...F]}function qe(e){return["-c","core.hooksPath=/dev/null","commit","-m",e]}function Ue(e){return["diff","--name-only",`${e}..HEAD`,"--",...Te]}function re(e){return`codweft/${e.replace(/[^A-Za-z0-9._-]/g,"-")}`}function Je({jobId:e,baseRef:t,title:n,body:s,draft:r}){return{title:n,body:s,draft:r,head:re(e),base:t}}function Fe(e){return/\b(create|open|submit|raise)\b[\s\S]{0,80}\b(pr|pull request|change request)\b/i.test(e)}function He(e,t){return["fix","implement","resolve_conflicts"].includes(e)?!0:e==="task"&&Fe(t)}function H(e=process.env){let t={};for(let n of Ae){let s=e[n];s&&(t[n]=s)}return t.HOME&&(t.PATH=`${t.HOME}/.pi/bin${t.PATH?`:${t.PATH}`:""}`),t}function z({mcpToken:e,secretName:t,secretValue:n,secretAliases:s=[],sourceEnv:r=process.env}){let i=H(r);if(!n)throw new Error(`${t} is not configured`);i[t]=n;for(let a of s)i[a]=n;return i.CODWEFT_MCP_TOKEN=e,i}var N=class extends Error{constructor(n,s){super(n);this.kind=s}kind};function Q(e,t){try{if(process.platform==="win32"){process.kill(e,t);return}process.kill(-e,t)}catch{try{process.kill(e,t)}catch{}}}async function oe(e){Q(e,"SIGTERM"),await new Promise(t=>setTimeout(t,Pe)),Q(e,"SIGKILL")}async function se(){let e=[...A];await Promise.all(e.map(t=>oe(t.pid))),A.clear()}for(let e of["SIGINT","SIGTERM"])process.once(e,()=>{se().finally(()=>{process.kill(process.pid,e)})});var ze=o.object({changeRequest:o.object({title:o.string().min(1).max(256),body:o.string().max(65536).optional(),draft:o.boolean().optional()}).optional()});function Ge(e){return ze.parse(JSON.parse(e))}async function Le(e){let t=await S(`/api/runner/jobs/${e}/mcp-token`,{method:"POST"});return o.object({mcpToken:o.string()}).parse(await t.json()).mcpToken}async function Be(e,t){let n=await S(`/api/runner/jobs/${e}/provider-secret`,{method:"POST",body:JSON.stringify({credentialId:t})});return o.object({configured:o.boolean(),credentialId:o.string().optional(),credentialName:o.string().optional(),provider:o.string().optional(),secretName:o.string().optional(),value:o.string().optional()}).parse(await n.json())}async function q(e,t){await j(we(e),{recursive:!0}),await fe(e,t)}async function h(e,t,n){let s=n.source??"runner",r=T(`${e} ${t.join(" ")}`,n.env);await n.context.reporter.timeline(`running ${r}`,"info",n.step);let i=u(n.context.logsDir,`${n.logPrefix??n.step}-stdout.log`),a=u(n.context.logsDir,`${n.logPrefix??n.step}-stderr.log`),c="",d="",l=[];return new Promise((f,p)=>{let g=!1,v=Date.now(),y=null,E=null;async function _(){await Promise.allSettled(l)}function R(){y&&clearInterval(y),E&&clearTimeout(E),y=null,E=null}function le(b){g||(g=!0,R(),_().then(()=>p(b)).catch(p))}function K(b){g||(g=!0,R(),k.pid&&oe(k.pid),_().then(()=>p(b)).catch(p))}let de=n.inheritEnv===!1||e==="git"&&n.inheritEnv!==!0?{...H(),...n.env}:{...process.env,...n.env},k=ge(e,t,{cwd:n.cwd,env:de,stdio:["ignore","pipe","pipe"],detached:process.platform!=="win32"});if(k.pid){A.add({pid:k.pid,command:e});let b=n.idleTimeoutMs??Ee,w=n.maxTimeoutMs??_e;y=setInterval(()=>{Date.now()-v<b||K(new N(`${e} had no output for ${Math.round(b/1e3)}s`,"idle"))},Math.min(5e3,b)),E=setTimeout(()=>{K(new N(`${e} exceeded ${Math.round(w/1e3)}s`,"max"))},w)}k.stdout?.on("data",b=>{v=Date.now();let w=T(b.toString("utf8"),n.env);c+=w,process.stdout.write(w),l.push(Promise.all([q(i,w),ee(n.context,{text:w,source:s,channel:"stdout",step:n.step,normalizeEvent:n.normalizeEvent})]))}),k.stderr?.on("data",b=>{v=Date.now();let w=T(b.toString("utf8"),n.env);d+=w,process.stderr.write(w),l.push(Promise.all([q(a,w),ee(n.context,{text:w,source:s,channel:"stderr",step:n.step,normalizeEvent:n.normalizeEvent})]))}),k.on("exit",b=>{if(k.pid)for(let W of A)W.pid===k.pid&&A.delete(W);if(g)return;g=!0,R();let w=b??1;_().then(()=>{w===0?f({stdout:c,stderr:d,exitCode:w}):p(new Error(`${e} exited with ${w}`))}).catch(p)}),k.on("error",b=>{if(k.pid)for(let w of A)w.pid===k.pid&&A.delete(w);le(b)})})}async function ee(e,{text:t,source:n,channel:s,step:r,normalizeEvent:i}){let a=t.split(/\r?\n/).filter(c=>c.trim().length>0);for(let c of a.slice(-10)){let d=i?.(c,s);d!==!1&&await e.reporter.event(d??{type:"process_output",source:n,channel:s,message:c,severity:s==="stderr"?"warn":"debug",step:r,visibility:"debug",redacted:!0})}}function G(e){let t=e.job.changeRequestNumber!==void 0?`change_request #${e.job.changeRequestNumber}`:e.job.workItemNumber!==void 0?`work_item #${e.job.workItemNumber}`:"repository",n=["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.","For pull request approvals, submit a summary-only APPROVE review. Use inline review comments only for concrete requested changes.","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."];return e.job.outputSchema!==void 0&&n.push("","This job requires structured output. Before finishing, call the Codweft MCP set_output tool with JSON matching this schema:",JSON.stringify(e.job.outputSchema,null,2)),n.join(` | ||
| `)}async function Ke(e,t){let n={mcpServers:{codweft:{url:t,auth:"bearer",bearerTokenEnv:"CODWEFT_MCP_TOKEN",lifecycle:"keep-alive",directTools:!0}}};await I(u(e,".mcp.json"),JSON.stringify(n,null,2))}function We(e){return e==="review"?"read-only":"workspace-write"}function Ve(e){return e==="review"?["--tools","read,grep,find,ls"]:[]}async function Ze(e,t,n){let s=u(e.logsDir,"codex-home");await j(s,{recursive:!0});let r=['approval_policy = "never"',"","[mcp_servers.codweft]",`url = ${JSON.stringify(t)}`,"","[mcp_servers.codweft.http_headers]",`Authorization = ${JSON.stringify(`Bearer ${n}`)}`,""].join(` | ||
| `);return await I(u(s,"config.toml"),r,{mode:384}),await U(u(s,"config.toml"),384),s}var Xe={"kimi-for-coding":"KIMI_API_KEY","zai-coding-plan":"ZHIPU_API_KEY","minimax-coding-plan":"MINIMAX_API_KEY"};function ie(e){return Xe[e.provider]??e.secretName}function Ye(e){let t=ie(e);return t===e.secretName?[]:[t]}function Qe(){return`command -v opencode >/dev/null 2>&1 || npm install -g ${Se}`}function et({endpoint:e,mcpToken:t,route:n}){let s=ie(n);return{$schema:"https://opencode.ai/config.json",enabled_providers:[n.provider],provider:{[n.provider]:{options:{apiKey:`{env:${s}}`}}},mcp:{codweft:{type:"remote",url:e,enabled:!0,headers:{Authorization:`Bearer ${t}`}}}}}async function tt(e,t,n,s){let r=u(e.logsDir,"opencode");await j(r,{recursive:!0});let i=et({endpoint:t,mcpToken:n,route:s});return await I(u(r,"opencode.jsonc"),JSON.stringify(i,null,2),{mode:384}),await U(u(r,"opencode.jsonc"),384),r}function ae(e,t){let n=e.trim();if(!n.startsWith("{")||!n.endsWith("}"))return null;let s;try{s=JSON.parse(n)}catch{return null}let r=o.object({type:o.string().optional(),message:o.union([o.string(),o.object({}).passthrough()]).optional(),content:o.string().optional(),toolName:o.string().optional(),tool:o.string().optional(),level:$.optional(),severity:$.optional(),role:o.string().optional()}).passthrough().safeParse(s);if(!r.success)return null;let i=r.data.type??"agent_event";if(i==="session"||i.endsWith("_start")||i==="message_update"||i==="message_delta")return!1;let a=typeof r.data.message=="string"?r.data.message:r.data.content??r.data.toolName??r.data.tool??(i.includes("error")?JSON.stringify(s):void 0);return a?{type:i,source:"agent",channel:r.data.role==="tool"||r.data.toolName?"tool_call":"agent_message",message:a,severity:r.data.severity??r.data.level??(t==="stderr"?"warn":"info"),visibility:i.includes("error")?"user":"debug",redacted:!0,metadata:{rawType:i}}:null}function C(e){return e&&typeof e=="object"&&!Array.isArray(e)?e:null}function P(e){return typeof e=="number"&&Number.isFinite(e)?e:void 0}function L(e){let t={},n=!1;for(let s of e.split(/\r?\n/)){if(!s.trim().startsWith("{"))continue;let r;try{r=JSON.parse(s)}catch{continue}let i=C(r),a=C(i?.message),c=C(i?.usage)??C(a?.usage);if(!c)continue;n=!0;let d=C(c.cost);t.inputTokens=P(c.input)??P(c.inputTokens)??t.inputTokens,t.outputTokens=P(c.output)??P(c.outputTokens)??t.outputTokens,t.cacheReadTokens=P(c.cacheRead)??P(c.cacheReadTokens)??t.cacheReadTokens,t.cacheWriteTokens=P(c.cacheWrite)??P(c.cacheWriteTokens)??t.cacheWriteTokens,t.rawCostUsd=P(d?.total)??P(c.rawCostUsd)??t.rawCostUsd}return n?t:void 0}function nt(e,t){let n=e.trim();if(!n.startsWith("{")||!n.endsWith("}"))return null;let s;try{s=JSON.parse(n)}catch{return null}let r=o.object({type:o.string().optional(),message:o.string().optional(),content:o.string().optional(),toolCallId:o.string().optional(),toolName:o.string().optional(),level:$.optional(),severity:$.optional(),role:o.string().optional()}).passthrough().safeParse(s);if(!r.success)return null;if(r.data.type==="tool_execution_start")return{type:"tool_execution_start",source:"agent",channel:"tool_call",message:r.data.toolName?`starting tool ${r.data.toolName}`:"starting tool call",severity:"info",step:"agent_run",visibility:"debug",redacted:!0,metadata:{toolCallId:r.data.toolCallId,toolName:r.data.toolName}};if(r.data.type==="tool_execution_end")return{type:"tool_execution_end",source:"agent",channel:"tool_call",message:r.data.toolName?`completed tool ${r.data.toolName}`:"completed tool call",severity:"info",step:"agent_run",visibility:"debug",redacted:!0,metadata:{toolCallId:r.data.toolCallId,toolName:r.data.toolName}};if(r.data.type==="session"||r.data.type==="agent_start"||r.data.type==="turn_start"||r.data.type==="turn_end"||r.data.type==="message_start"||r.data.type==="message_update"||r.data.type==="message_end")return!1;let i=r.data.message??r.data.content;return i?{type:r.data.type??"agent_message",source:"agent",channel:r.data.role==="tool"?"tool_call":"agent_message",message:i,severity:r.data.severity??r.data.level??(t==="stderr"?"warn":"info"),visibility:"user",redacted:!0,metadata:{rawType:r.data.type}}:null}var rt={name:"pi",async install(e){let t=H();await h("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 h("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 Ke(e.workspace,t)},async run(e,t,n,s,r){let i=G(t),a=await h("pi",["--mode","json","--print","--provider",n.provider,"--model",n.model,...Ve(t.job.command),i],{cwd:e.workspace,env:z({mcpToken:s,secretName:r.secretName??n.secretName,secretValue:r.value}),inheritEnv:!1,step:"agent_run",source:"agent",logPrefix:"agent",normalizeEvent:this.normalizeEvent,context:e});return{usage:L(a.stdout)}},normalizeEvent:nt,async collectArtifacts(e){await m(e.jobId,"log","agent-stdout.log",u(e.logsDir,"agent-stdout.log")),await m(e.jobId,"log","agent-stderr.log",u(e.logsDir,"agent-stderr.log"))}},ot={name:"codex",async install(e){await h("sh",["-lc","command -v codex >/dev/null 2>&1"],{cwd:e.workspace,inheritEnv:!1,step:"agent_install",logPrefix:"codex-install",context:e})},async configureMcp(){},async run(e,t,n,s,r){let i=await Ze(e,t.mcp.endpoint,s),a=await h("codex",["exec","--json","--skip-git-repo-check","--cd",e.workspace,"--sandbox",We(t.job.command),"--ask-for-approval","never","--model",n.model,G(t)],{cwd:e.workspace,env:{...z({mcpToken:s,secretName:r.secretName??n.secretName,secretValue:r.value}),CODEX_HOME:i},inheritEnv:!1,step:"agent_run",source:"agent",logPrefix:"agent",normalizeEvent:this.normalizeEvent,context:e});return{usage:L(a.stdout)}},normalizeEvent:ae,async collectArtifacts(e){await m(e.jobId,"log","agent-stdout.log",u(e.logsDir,"agent-stdout.log")),await m(e.jobId,"log","agent-stderr.log",u(e.logsDir,"agent-stderr.log"))}},st={name:"opencode",async install(e){await h("sh",["-lc",Qe()],{cwd:e.workspace,inheritEnv:!1,step:"agent_install",logPrefix:"opencode-install",context:e})},async configureMcp(){},async run(e,t,n,s,r){let i=await tt(e,t.mcp.endpoint,s,n),a=["run","--format","json","--model",`${n.provider}/${n.model}`,"--dir",e.workspace,"--title",`codweft-${t.job.jobId}`];t.job.command!=="review"&&a.push("--dangerously-skip-permissions"),a.push(G(t));let c=await h("opencode",a,{cwd:e.workspace,env:{...z({mcpToken:s,secretName:r.secretName??n.secretName,secretValue:r.value,secretAliases:Ye(n)}),OPENCODE_CONFIG_DIR:i},inheritEnv:!1,step:"agent_run",source:"agent",logPrefix:"agent",normalizeEvent:this.normalizeEvent,context:e});return{usage:L(c.stdout)}},normalizeEvent:ae,async collectArtifacts(e){await m(e.jobId,"log","agent-stdout.log",u(e.logsDir,"agent-stdout.log")),await m(e.jobId,"log","agent-stderr.log",u(e.logsDir,"agent-stderr.log"))}},it={pi:rt,codex:ot,opencode:st};async function at(e,t,n,s){s&&await S(`/api/runner/jobs/${e}/usage`,{method:"POST",body:JSON.stringify({agent:n.agent,provider:n.provider,model:n.model,retryGroup:t,...s})}).catch(()=>{})}async function ct(e,t,n){let s=null,r=new Set,i=new Set;for(let a of t.modelRoutingPlan.routes){let c=await Be(t.job.jobId,a.credentialId);if(!c.configured||!c.value){await e.reporter.event({type:"model_route_skipped",source:"model_router",channel:"timeline",message:`skipping ${a.agent}/${a.provider}/${a.model}: credential ${a.credentialName??a.credentialId} is not configured`,severity:"warn",step:"model_route",visibility:"debug",redacted:!0});continue}let d=it[a.agent];try{r.has(d.name)||(await d.install(e),r.add(d.name)),i.has(d.name)||(await d.configureMcp(e,t.mcp.endpoint),i.add(d.name)),await e.reporter.event({type:"agent_started",source:"agent",channel:"timeline",message:`starting ${a.agent} with ${a.provider}/${a.model}`,severity:"info",step:"agent_run",visibility:"user",redacted:!0},!0);let l=await d.run(e,t,a,n,c);await at(t.job.jobId,t.modelRoutingPlan.retryGroup,a,l.usage);return}catch(l){s=l instanceof Error?l:new Error(String(l)),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")}async function ce(e,t,n,s,r="text/plain"){let i=await S(`/api/runner/jobs/${e}/artifacts`,{method:"POST",body:JSON.stringify({kind:t,fileName:n,name:n,contentType:r,size:Buffer.byteLength(s)})}),a=o.object({uploadUrl:o.string(),headers:o.record(o.string(),o.string())}).parse(await i.json());await fetch(a.uploadUrl,{method:"PUT",headers:a.headers,body:s})}async function m(e,t,n,s){let r=await J(s,"utf8").catch(()=>"");r&&await ce(e,t,n,r).catch(()=>{})}var x=class extends Error{constructor(n){super(`Job was already marked ${n} by MCP`);this.status=n}status};function ut(e){return!!(e&&Re.has(e))}async function ue(e){let t=await S(`/api/runner/jobs/${e}`),n=ne.parse(await t.json());if(ut(n.job.status))throw new x(n.job.status);return n}async function lt(e,t){if(t.job.outputSchema===void 0)return;if((await ue(e)).job.result===void 0)throw new Error("Job requires structured output, but the agent did not call set_output")}async function dt(e){let t=u(e,".codweft","result.json"),n=await J(t,"utf8").catch(r=>{if(r.code==="ENOENT")return null;throw r});if(n===null)return null;let s=Ge(n);return await te(t,{force:!0}),s}async function O(e,t,n,s=n){return(await h("git",t,{cwd:e.workspace,step:n,logPrefix:s,context:e})).stdout.trim()}async function gt(e){return(await O(e,["status","--porcelain","--",".",...F],"publish","git-publish")).length>0}async function pt(e,t){return(await O(e,["diff","--name-only",`${t}..HEAD`,"--",".",...F],"publish","git-publish")).length>0}async function ft(e,t){let n=await O(e,Ue(t),"publish","git-publish");if(n)throw new Error(`Agent committed runner control files, refusing to publish: ${n.split(/\r?\n/).join(", ")}`)}async function mt(e,t){await gt(t)&&(await h("git",["config","user.name","codweft[bot]"],{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}),await h("git",["config","user.email","codweft[bot]@users.noreply.github.com"],{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}),await h("git",De(),{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}),await h("git",qe(`codweft: apply ${e.job.command} for ${e.job.jobId}`),{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}))}async function B(e,t,n,s){let r=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:n,arguments:s}})});if(!r.ok)throw new Error(`MCP ${n} failed: ${r.status} ${await r.text()}`);let i=o.object({error:o.object({message:o.string()}).optional(),result:o.object({content:o.array(o.object({type:o.string(),text:o.string()})).optional()}).optional()}).parse(await r.json());if(i.error)throw new Error(`MCP ${n} failed: ${i.error.message}`);let a=i.result?.content?.[0]?.text;return a?JSON.parse(a):{}}async function ht(e,t,n){try{return await B(e,t,"report_progress",{text:n}),!0}catch{return!1}}async function wt(e,t,n){try{return await B(e,t,"report_failure",{text:n}),!0}catch{return!1}}async function bt({payload:e,context:t,baseSha:n,gitAskpassEnv:s,mcpToken:r}){let i=await dt(t.workspace),a=He(e.job.command,e.job.instruction),c=!!i?.changeRequest;if(c&&!a)throw new Error(`${e.job.command} jobs are not allowed to publish change requests`);let d=re(e.job.jobId);await h("git",["checkout","-B",d],{cwd:t.workspace,step:"publish",logPrefix:"git-publish",context:t}),await mt(e,t),await ft(t,n);let l=await pt(t,n);if(!c){if(l)throw new Error("Agent made code changes but did not write .codweft/result.json with change request metadata");return null}if(!l)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 ${d}`,severity:"info",step:"publish",visibility:"user",redacted:!0},!0),await h("git",Ie(d),{cwd:t.workspace,env:await s(),step:"publish",logPrefix:"git-publish",context:t});let f=i?.changeRequest;if(!f)return null;let p=await B(e.mcp.endpoint,r,"create_change_request",Je({jobId:e.job.jobId,baseRef:e.checkout.publishBaseRef,title:f.title,body:f.body,draft:f.draft})),g=o.object({number:o.number().optional(),html_url:o.string().optional(),url:o.string().optional()}).passthrough().parse(p);return await t.reporter.event({type:"change_request_created",source:"runner",channel:"result",message:g.html_url?`created change request ${g.html_url}`:`created change request${g.number?` #${g.number}`:""}`,severity:"info",step:"publish",visibility:"user",redacted:!0,metadata:{number:g.number,url:g.html_url??g.url}},!0),{changeRequest:{number:g.number,url:g.html_url??g.url,branch:d}}}async function yt(){let e=M("CODWEFT_JOB_ID"),t=await S(`/api/runner/jobs/${e}`),n=ne.parse(await t.json()),s=await me(u(he(),"codweft-")),r=u(s,"repo"),i=u(s,"logs");await j(r,{recursive:!0}),await j(i,{recursive:!0});let a=new D(e,u(i,"runner.log")),c={jobId:e,workspace:r,logsDir:i,reporter:a},d=null,l=null;try{await a.event({type:"runner_started",source:"runner",channel:"timeline",message:"runner started",severity:"info",step:"bootstrap",visibility:"user",redacted:!0},!0);let f=await Ne(e);d=await Me(s);let p=()=>d.register(f),g=xe({fullName:n.job.fullName});await h("git",["clone","--recurse-submodules",g.repoUrl,r],{env:await p(),step:"clone",logPrefix:"git-clone",context:c}),await h("git",["remote","set-url","origin",g.repoUrl],{cwd:r,step:"clone",logPrefix:"git-clone",context:c}),await vt(n,c,p);let v=await O(c,["rev-parse","HEAD"],"checkout","git-checkout");l=await Le(e),await ct(c,n,l),await ue(e),await lt(e,n);let y=await bt({payload:n,context:c,baseSha:v,gitAskpassEnv:p,mcpToken:l}),E=await ht(n.mcp.endpoint,l,"Codweft completed the job successfully.");await S(`/api/runner/jobs/${e}/result`,{method:"POST",body:JSON.stringify({status:"success",result:y??void 0,finalSummaryWritten:E})}),await a.event({type:"job_completed",source:"runner",channel:"result",message:"job completed successfully",severity:"info",step:"result",visibility:"user",redacted:!0},!0)}catch(f){let p=f instanceof Error?f.stack??f.message:String(f),g=f instanceof x?f.status:f instanceof N?"timed_out":"failure",v=!1;!(f instanceof x)&&l&&(v=await wt(n.mcp.endpoint,l,`Codweft runner failed. | ||
| ${T(p).slice(0,4e3)}`)),await a.event({type:"job_failed",source:"runner",channel:"result",message:p,severity:"error",step:"result",visibility:"user",redacted:!0},!0),await ce(e,"log","runner-error.txt",T(p)).catch(()=>{}),await S(`/api/runner/jobs/${e}/result`,{method:"POST",body:JSON.stringify({status:g,failureReason:T(p).slice(0,16e3),finalSummaryWritten:v})}).catch(()=>{}),process.exitCode=1}finally{await se(),await a.flush(),await m(e,"log","git-clone-stdout.log",u(i,"git-clone-stdout.log")),await m(e,"log","git-clone-stderr.log",u(i,"git-clone-stderr.log")),await m(e,"log","git-checkout-stdout.log",u(i,"git-checkout-stdout.log")),await m(e,"log","git-checkout-stderr.log",u(i,"git-checkout-stderr.log")),await m(e,"log","git-publish-stdout.log",u(i,"git-publish-stdout.log")),await m(e,"log","git-publish-stderr.log",u(i,"git-publish-stderr.log")),await m(e,"log","pi-install-stdout.log",u(i,"pi-install-stdout.log")),await m(e,"log","pi-install-stderr.log",u(i,"pi-install-stderr.log")),await m(e,"log","codex-install-stdout.log",u(i,"codex-install-stdout.log")),await m(e,"log","codex-install-stderr.log",u(i,"codex-install-stderr.log")),await m(e,"log","opencode-install-stdout.log",u(i,"opencode-install-stdout.log")),await m(e,"log","opencode-install-stderr.log",u(i,"opencode-install-stderr.log")),await m(e,"log","agent-stdout.log",u(i,"agent-stdout.log")),await m(e,"log","agent-stderr.log",u(i,"agent-stderr.log")),await m(e,"log","runner.log",u(i,"runner.log")),await d?.close().catch(()=>{}),await J(u(r,".mcp.json"),"utf8").catch(()=>""),await te(s,{recursive:!0,force:!0}).catch(()=>{})}}async function vt(e,t,n){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 h("git",$e(e.checkout.checkoutFetchRef),{cwd:t.workspace,env:await n(),step:"checkout",logPrefix:"git-checkout",context:t}),await h("git",["checkout","--detach","FETCH_HEAD"],{cwd:t.workspace,step:"checkout",logPrefix:"git-checkout",context:t});let r=(await h("git",["rev-parse","HEAD"],{cwd:t.workspace,step:"checkout",logPrefix:"git-checkout",context:t})).stdout.trim();if(r!==e.checkout.checkoutSha)throw new Error(`Checkout SHA mismatch: expected ${e.checkout.checkoutSha}, got ${r}`);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 h("git",["checkout",e.checkout.checkoutRef],{cwd:t.workspace,step:"checkout",logPrefix:"git-checkout",context:t})}function kt(e=process.argv[1],t=import.meta.url){let n=be(t);if(!e)return!1;try{return Z(e)===Z(n)}catch{return e===n}}kt()&&yt().catch(e=>{console.error(T(e instanceof Error?e.stack??e.message:String(e))),process.exit(1)});export{ft as assertNoCommittedControlFiles,z as buildAgentEnv,$e as buildChangeRequestFetchArgs,Ue as buildCommittedControlFilesDiffArgs,Je as buildCreateChangeRequestArgs,De as buildGitAddPublishArgs,qe as buildGitCommitArgs,xe as buildGitHubCloneAuth,Ie as buildGitPushArgs,et as buildOpenCodeConfig,Qe as buildOpenCodeInstallCommand,re as buildPublishBranchName,H as buildSafeProcessEnv,He as canPublishForCommand,kt as isCliEntrypoint,nt as normalizePiEvent,ie as openCodeProviderEnvName,Ye as openCodeSecretAliases,Ge as parseRunnerResultJson,T as redactText,h as runProcess,Fe as taskExplicitlyRequestsChangeRequest}; |
+1
-1
| { | ||
| "name": "@codweft/runner", | ||
| "version": "0.4.6", | ||
| "version": "0.4.7", | ||
| "type": "module", | ||
@@ -5,0 +5,0 @@ "bin": { |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
39035
52.02%314
67.91%8
14.29%5
66.67%