@agimon-ai/workflow-mcp 0.1.12 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,6 +33,12 @@ Add to Claude Code configuration:
33
33
  workflow-mcp run-workflow .github/workflows/development.yml -p "Add a health check"
34
34
  ```
35
35
 
36
+ Stop a running workflow through the registry:
37
+
38
+ ```bash
39
+ workflow-mcp stop-workflow <run-key> --workspace default
40
+ ```
41
+
36
42
  ## Documentation
37
43
 
38
44
  - [CLI reference](https://github.com/AgiFlow/public-packages-internals/blob/main/packages/mcp/workflow-mcp/docs/cli.md) — all commands, flags, and examples
package/dist/cli.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const e=require(`./stdio-Dd8bf1mi.cjs`);let t=require(`node:child_process`),n=require(`node:fs/promises`),r=require(`node:path`),i=require(`node:fs`),a=require(`node:readline`),o=require(`@agimon-ai/foundation-process-registry`),s=require(`commander`),c=require(`node:module`);var l=`0.1.11`;const u=`WORKFLOW_STATUS_FILE`,ee={readStdin:ae,readStatusFile:oe,writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await(0,n.writeFile)(e,`YES
3
- `,`utf8`)},workflowStatusFile:process.env[u]};async function d(e=ee){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=f(n);if(!r?.conversationId)return;let i=await e.readStatusFile(t);if(!(i===null||i.trim()===`YES`)){if(!r.fullyIdle){e.writeStdout?.(te(`In-progress work is still running. Continue the execution loop until background tasks finish.`));return}re(r.error)||r.terminationReason!==`model_stop`||await e.writeStatusFile(t)}}function f(e){try{let t=JSON.parse(e);return ie(t)?{conversationId:typeof t.conversationId==`string`?t.conversationId:void 0,transcriptPath:ne(t.transcriptPath),terminationReason:typeof t.terminationReason==`string`?t.terminationReason:void 0,error:typeof t.error==`string`?t.error:void 0,fullyIdle:t.fullyIdle===!0}:null}catch{return null}}function te(e){return`${JSON.stringify({decision:`continue`,reason:e})}\n`}function ne(e){return typeof e==`string`&&e.trim().length>0?e:null}function re(e){return typeof e==`string`&&e.trim().length>0}function ie(e){return typeof e==`object`&&!!e}async function ae(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function oe(e){try{return await(0,n.readFile)(e,`utf8`)}catch{return null}}const se=new s.Command(`antigravity-stop-hook`).description(`Antigravity stop hook: reads Stop hook JSON on stdin and marks ${u} when the execution is fully idle`).action(async()=>{await d()});var p=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`WorkflowCommandError`}};const m=`check-codex-quota`;function h(t={}){let n=t.createService??(()=>new e.i),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(e));return new s.Command(m).description(`Check whether Codex quota is currently blocking new work`).action(async()=>{try{let e=await n().getQuotaStatus();if(a(`${JSON.stringify({blockingLimit:e?.blockingLimit??null,planType:e?.planType??null},null,2)}\n`),e?.blockingLimit){let t=new p(`Codex quota is blocking work at ${e.blockingLimit.limitId}/${e.blockingLimit.window}.`,`CODEX_QUOTA_BLOCKED`,{command:m,limitId:e.blockingLimit.limitId,limitName:e.blockingLimit.limitName,window:e.blockingLimit.window});i(`${t.message} [${t.code}]`,t),r(2);return}r(0)}catch(e){let t=e instanceof p?e:new p(`Error checking Codex quota.`,`CHECK_CODEX_QUOTA_COMMAND_FAILED`,{command:m},{cause:e});i(`${t.message} [${t.code}]`,t),r(1)}})}const g=h(),_=`WORKFLOW_STATUS_FILE`,v=`session_meta`,ce=[`sub_agent`,`subagent`],le=new Set([`in_progress`,`inProgress`,`running`,`pending_init`,`pendingInit`]),ue=new Set([`collabAgentToolCall`,`commandExecution`,`dynamicToolCall`,`imageGeneration`,`local_shell_call`,`mcpToolCall`]),de={readStdin:ve,readStatusFile:ye,readTranscriptHead:x,readTranscriptState:S,writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await(0,n.writeFile)(e,`YES
4
- `,`utf8`)},workflowStatusFile:process.env[_]};async function fe(e=de){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=he(n);if(!r||r.hook_event_name&&r.hook_event_name!==`Stop`||r.stop_hook_active&&!r.hasTaskRegistry)return;let i=ge(await e.readStatusFile(t));if(!r.session_id||r.session_id!==i)return;let a=y(r.transcript_path);if(!a)return;let o=await me(e,a),s=o.sessionMeta;if(!s||r.session_id&&s.id&&r.session_id!==s.id||_e(s.source))return;let c=r.hasTaskRegistry&&(r.background_tasks?.length??0)>0,l=r.hasTaskRegistry&&(r.session_crons?.length??0)>0;if(c||!r.hasTaskRegistry&&o.hasPendingWork){e.writeStdout?.(pe(`In-progress work is still running. Wait for spawned agents and running tool calls to finish before ending the turn.`));return}l||await e.writeStatusFile(t)}function pe(e){return`${JSON.stringify({decision:`block`,reason:e,suppressOutput:!0})}\n`}async function me(e,t){return e.readTranscriptState?e.readTranscriptState(t):e.readTranscriptHead===x?S(t):{sessionMeta:await e.readTranscriptHead(t),hasPendingWork:!1}}function he(e){try{let t=JSON.parse(e);if(!b(t))return null;let n=Array.isArray(t.background_tasks)?t.background_tasks:void 0,r=Array.isArray(t.session_crons)?t.session_crons:void 0;return{hook_event_name:typeof t.hook_event_name==`string`?t.hook_event_name:void 0,session_id:typeof t.session_id==`string`?t.session_id:void 0,transcript_path:y(t.transcript_path),stop_hook_active:t.stop_hook_active===!0,background_tasks:n,session_crons:r,hasTaskRegistry:n!==void 0||r!==void 0}}catch{return null}}function y(e){return typeof e==`string`&&e.trim().length>0?e:null}function ge(e){if(!e)return null;let t=e.trim();if(!t||t===`YES`)return null;try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?e.sessionId:null}catch{return null}}function _e(e){return typeof e==`string`?e.startsWith(`subagent_`)||e.startsWith(`internal_`):b(e)?ce.some(t=>t in e):!1}function b(e){return typeof e==`object`&&!!e}async function ve(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function ye(e){try{return await(0,n.readFile)(e,`utf8`)}catch{return null}}async function x(e){return(await S(e)).sessionMeta}async function S(e){let t=(0,i.createReadStream)(e,{encoding:`utf8`}),n=(0,a.createInterface)({input:t,crlfDelay:1/0}),r=new Set,o=new Map,s=null;try{for await(let e of n){let t=e.trim();if(t.length===0)continue;let n=be(t);n&&(s||=xe(n),Se(n,r),Ce(n,o))}}finally{n.close(),t.destroy()}return{sessionMeta:s,hasPendingWork:r.size>0||Array.from(o.values()).some(T)}}function be(e){try{let t=JSON.parse(e);return b(t)?t:null}catch{return null}}function xe(e){return e.type===v?e.payload??null:e.item?.type===v?e.item.payload??null:null}function Se(e,t){let n=E(e,[`payload`,`item`])??E(e,[`item`,`payload`,`item`]);if(!n)return;let r=D(n.id),i=D(n.type);!r||!i||!ue.has(i)||(T(n.status)?t.add(r):t.delete(r))}function Ce(e,t){let n=E(e,[`payload`])??E(e,[`item`,`payload`]);if(!n||!D(n.type)?.startsWith(`collab_`))return;C(t,D(n.new_thread_id),n.status),C(t,D(n.receiver_thread_id),n.status);let r=E(n,[`statuses`]);if(r)for(let[e,n]of Object.entries(r))C(t,e,n)}function C(e,t,n){let r=w(n);!t||!r||e.set(t,r)}function w(e){if(typeof e==`string`)return e;if(!b(e))return null;let[t]=Object.keys(e);return t??null}function T(e){let t=typeof e==`string`?e:w(e);return t!==null&&le.has(t)}function E(e,t){let n=e;for(let e of t){if(!b(n))return null;n=n[e]}return b(n)?n:null}function D(e){return typeof e==`string`&&e.length>0?e:void 0}const O=new s.Command(`claude-stop-hook`).description(`Claude Code stop hook: reads Stop hook JSON on stdin and marks ${_} when the root session is complete`).action(async()=>{await fe()}),k=`WORKFLOW_STATUS_FILE`,A=`session_meta`,we=[`sub_agent`,`subagent`],Te=new Set([`in_progress`,`inProgress`,`running`,`pending_init`,`pendingInit`]),Ee=new Set([`collabAgentToolCall`,`commandExecution`,`dynamicToolCall`,`imageGeneration`,`local_shell_call`,`mcpToolCall`]),De={readStdin:N,readStatusFile:P,readTranscriptHead:F,readTranscriptState:I,writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await(0,n.writeFile)(e,`YES
5
- `,`utf8`)},workflowStatusFile:process.env[k]},Oe={readStdin:N,readStatusFile:P,writeSessionBinding:async(e,t)=>{await(0,n.writeFile)(e,`${JSON.stringify({sessionId:t})}\n`,`utf8`)},workflowStatusFile:process.env[k]};async function ke(e=Oe){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=Ne(n);if(!r?.session_id)return;let i=await e.readStatusFile(t);i===null||i.trim().length>0||await e.writeSessionBinding(t,r.session_id)}async function Ae(e=De){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=Pe(n);if(!r||r.hook_event_name&&r.hook_event_name!==`Stop`||r.stop_hook_active&&!r.hasTaskRegistry)return;let i=Fe(await e.readStatusFile(t));if(!r.session_id||r.session_id!==i)return;let a=j(r.transcript_path);if(!a)return;let o=await Me(e,a),s=o.sessionMeta;if(!s||r.session_id&&s.id&&r.session_id!==s.id||Ie(s.source))return;let c=r.hasTaskRegistry&&(r.background_tasks?.length??0)>0,l=r.hasTaskRegistry&&(r.session_crons?.length??0)>0;if(c||!r.hasTaskRegistry&&o.hasPendingWork){e.writeStdout?.(je(`In-progress work is still running. Wait for spawned agents and running tool calls to finish before ending the turn.`));return}l||await e.writeStatusFile(t)}function je(e){return`${JSON.stringify({decision:`block`,reason:e,suppressOutput:!0})}\n`}async function Me(e,t){return e.readTranscriptState?e.readTranscriptState(t):e.readTranscriptHead===F?I(t):{sessionMeta:await e.readTranscriptHead(t),hasPendingWork:!1}}function Ne(e){try{let t=JSON.parse(e);return M(t)?{session_id:typeof t.session_id==`string`?t.session_id:void 0}:null}catch{return null}}function Pe(e){try{let t=JSON.parse(e);if(!M(t))return null;let n=Array.isArray(t.background_tasks)?t.background_tasks:void 0,r=Array.isArray(t.session_crons)?t.session_crons:void 0;return{hook_event_name:typeof t.hook_event_name==`string`?t.hook_event_name:void 0,session_id:typeof t.session_id==`string`?t.session_id:void 0,transcript_path:j(t.transcript_path),stop_hook_active:t.stop_hook_active===!0,background_tasks:n,session_crons:r,hasTaskRegistry:n!==void 0||r!==void 0}}catch{return null}}function j(e){return typeof e==`string`&&e.trim().length>0?e:null}function Fe(e){if(!e)return null;let t=e.trim();if(!t||t===`YES`)return null;try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?e.sessionId:null}catch{return null}}function Ie(e){return typeof e==`string`?e.startsWith(`subagent_`)||e.startsWith(`internal_`):M(e)?we.some(t=>t in e):!1}function M(e){return typeof e==`object`&&!!e}async function N(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function P(e){try{return await(0,n.readFile)(e,`utf8`)}catch{return null}}async function F(e){return(await I(e)).sessionMeta}async function I(e){let t=(0,i.createReadStream)(e,{encoding:`utf8`}),n=(0,a.createInterface)({input:t,crlfDelay:1/0}),r=new Set,o=new Map,s=null;try{for await(let e of n){let t=e.trim();if(t.length===0)continue;let n=Le(t);n&&(s||=Re(n),ze(n,r),Be(n,o))}}finally{n.close(),t.destroy()}return{sessionMeta:s,hasPendingWork:r.size>0||Array.from(o.values()).some(z)}}function Le(e){try{let t=JSON.parse(e);return M(t)?t:null}catch{return null}}function Re(e){return e.type===A?e.payload??null:e.item?.type===A?e.item.payload??null:null}function ze(e,t){let n=B(e,[`payload`,`item`])??B(e,[`item`,`payload`,`item`]);if(!n)return;let r=V(n.id),i=V(n.type);!r||!i||!Ee.has(i)||(z(n.status)?t.add(r):t.delete(r))}function Be(e,t){let n=B(e,[`payload`])??B(e,[`item`,`payload`]);if(!n||!V(n.type)?.startsWith(`collab_`))return;L(t,V(n.new_thread_id),n.status),L(t,V(n.receiver_thread_id),n.status);let r=B(n,[`statuses`]);if(r)for(let[e,n]of Object.entries(r))L(t,e,n)}function L(e,t,n){let r=R(n);!t||!r||e.set(t,r)}function R(e){if(typeof e==`string`)return e;if(!M(e))return null;let[t]=Object.keys(e);return t??null}function z(e){let t=typeof e==`string`?e:R(e);return t!==null&&Te.has(t)}function B(e,t){let n=e;for(let e of t){if(!M(n))return null;n=n[e]}return M(n)?n:null}function V(e){return typeof e==`string`&&e.length>0?e:void 0}const Ve=new s.Command(`codex-session-start-hook`).description(`Codex session-start hook: binds ${k} to the first workflow Codex session id`).action(async()=>{await ke()}),He=new s.Command(`codex-stop-hook`).description(`Codex stop hook: reads stop-hook JSON on stdin and marks ${k} when the root session ends`).action(async()=>{await Ae()}),H=`list-crons`;function Ue(t={}){let n=t.createService??(()=>new e.o),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(e));return new s.Command(H).description(`List cron jobs scheduled via workflow-mcp`).action(async()=>{try{let e=await n().list();a(`${JSON.stringify(e,null,2)}\n`),r(0)}catch(e){let t=new p(`Error listing cron jobs.`,`LIST_CRONS_COMMAND_FAILED`,{command:H},{cause:e});i(`${t.message} [${t.code}]`,t),r(1)}})}const We=Ue(),U=`list-workflow-statuses`;function W(e){let t=Number(e);if(!Number.isInteger(t)||t<1)throw new s.InvalidArgumentError(`Expected a positive integer.`);return t}function Ge(t={}){let n=t.createRegistry??(()=>new e.a),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(e));return new s.Command(U).description(`List tracked workflow runs and their current stages`).option(`--page <number>`,`Page number to return`,W,1).option(`--page-size <number>`,`Number of workflow runs per page`,W,20).option(`-w, --workspace <name>`,`Filter workflow runs by workspace`,`all`).action(async e=>{try{let t=n(),i=e.workspace===`all`?void 0:e.workspace,o=await t.listRunsPage({page:e.page,pageSize:e.pageSize,workspace:i});a(`${JSON.stringify(o,null,2)}\n`),r(0)}catch(t){let n=new p(`Error listing workflow statuses.`,`LIST_WORKFLOW_STATUSES_FAILED`,{command:U,workspace:e.workspace??`all`},{cause:t});i(`${n.message} [${n.code}]`,n),r(1)}})}const Ke=Ge(),qe=()=>{try{return(0,c.createRequire)(require(`url`).pathToFileURL(__filename).href)(`@agimon-ai/foundation-process-registry`)}catch{return null}};function G(e,t){for(let n of e){if(n==null||typeof n!=`object`&&typeof n!=`function`)continue;let e=n;for(let n of t){let t=e[n];if(typeof t==`function`)return t}}return null}function Je(e){let t=[e];if(e&&typeof e==`object`){let n=e.default;if(n&&(t.push(n),typeof n==`function`))try{t.push(new n)}catch{}}return t}async function Ye(e){let t=qe();if(!t)return async()=>{};let n=Je(t),r=G(n,[`registerProcess`,`register`,`registerProcessInstance`]),i=G(n,[`unregisterProcess`,`unregister`,`releaseProcess`]);if(!r)return async()=>{};try{await Promise.resolve(r({name:e,pid:process.pid,command:process.argv.join(` `)}))}catch{return async()=>{}}return i?async()=>{await Promise.resolve(i({name:e,pid:process.pid}))}:async()=>{}}async function Xe(e,t){await e.start();let n=async n=>{console.error(`\\nReceived ${n}, shutting down gracefully...`);let r=0;try{await e.stop()}catch(e){console.error(`Error during shutdown:`,e),r=1}try{await t()}catch(e){console.error(`Error during resource cleanup:`,e),r=1}process.exit(r)};process.on(`SIGINT`,()=>{n(`SIGINT`)}),process.on(`SIGTERM`,()=>{n(`SIGTERM`)})}async function Ze(e){try{await e()}catch{}}const Qe=new s.Command(`mcp-serve`).description(`Start MCP server with specified transport`).option(`-t, --type <type>`,`Transport type: stdio`,`stdio`).option(`--service-name <name>`,`Service name for registry tracking`,`workflow-mcp`).action(async t=>{let n=await Ye(t.serviceName);try{let r=t.type.toLowerCase();r===`stdio`?await Xe(new e.t(e.n()),n):(console.error(`Unknown transport type: ${r}. Use: stdio`),process.exit(1))}catch(e){await Ze(n),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),K=`run-workflow`,q=`RUN_WORKFLOW_COMMAND_FAILED`,J=`WORKFLOW_MCP_BACKGROUND_CHILD`,Y=`workflow-mcp-background-run`;function X(e){if(!e||e.length===0)return;let t={};for(let n of e){let[e,...r]=n.split(`=`);t[e]=r.join(`=`)}return Object.keys(t).length>0?t:void 0}function Z(e){if(e.runner&&e.cliAgent&&e.runner!==e.cliAgent)throw new p(`Conflicting runner selectors.`,q,{cliAgent:e.cliAgent,command:K,runner:e.runner});return e.runner??e.cliAgent}function Q(e,n){let r=process.argv[1];if(!r)throw new p(`Unable to determine the workflow-mcp CLI entry point for background execution.`,`RUN_WORKFLOW_BACKGROUND_LAUNCH_FAILED`,{command:K,workflow:e,workspace:n.workspace});let i=[r,`run-workflow`,e];n.job&&i.push(`--job`,n.job);for(let e of n.input??[])i.push(`--input`,e);for(let e of n.env??[])i.push(`--env`,e);n.secretFile&&i.push(`--secret-file`,n.secretFile),n.dryRun&&i.push(`--dry-run`),n.continueOnError&&i.push(`--continue-on-error`),n.keepWorktree&&i.push(`--keep-worktree`);let a=Z(n);a&&i.push(`--runner`,a),n.prompt&&i.push(`--prompt`,n.prompt),n.name&&i.push(`--name`,n.name),n.workspace&&i.push(`--workspace`,n.workspace);let o=(0,t.spawn)(process.execPath,i,{detached:!0,env:{...process.env,[J]:`1`},stdio:`ignore`});return o.unref(),o.pid}async function $e(e,t){if(process.env[J]!==`1`)return async()=>{};let n=new o.ProcessRegistryService(process.env.PROCESS_REGISTRY_PATH),i=(0,r.resolve)(process.cwd()),a=process.env.NODE_ENV??`development`;return(await n.registerProcess({repositoryPath:i,serviceName:Y,serviceType:`tool`,environment:a,pid:process.pid,command:process.argv.join(` `),args:process.argv.slice(2),metadata:{workflow:e,workspace:t.workspace,job:t.job,name:t.name,runner:t.runner??t.cliAgent},force:!0})).success?async()=>{let e=await n.releaseProcess({repositoryPath:i,serviceName:Y,serviceType:`tool`,environment:a,pid:process.pid,kill:!1,releasePort:!1,force:!0});if(!e.success&&!e.error?.includes(`No matching process entry`))throw Error(e.error??`Failed to release workflow background process`)}:async()=>{}}function et(t={}){let n=t.createService??(()=>new e.r),r=t.exit??(e=>process.exit(e)),i=t.launchBackgroundRun??Q,a=t.registerBackgroundChild??$e,o=t.logError??((e,t)=>console.error(e,t)),c=t.logInfo??(e=>process.stdout.write(`${e}\n`));return new s.Command(K).description(`Run a GitHub Actions workflow file locally on macOS`).argument(`<workflow>`,`Path to the workflow YAML file`).option(`-j, --job <name>`,`Run only this job (and its dependencies)`).option(`-i, --input <key=value...>`,`Set workflow_dispatch input (repeatable)`).option(`-e, --env <key=value...>`,`Set extra environment variable (repeatable)`).option(`--secret-file <path>`,`Load secrets from a dotenv-style file`).option(`--dry-run`,`Print steps without executing`).option(`--continue-on-error`,`Continue past step failures`).option(`--runner <runner>`,`Preferred runner key for step command maps`).option(`--cli-agent <agent>`,`Deprecated alias for --runner`).option(`-p, --prompt <text>`,`User prompt for user_prompt trigger workflows`).option(`-n, --name <name>`,`Name for the workflow run context directory`).option(`-w, --workspace <name>`,`Workspace for workflow registry storage`).option(`--keep-worktree`,`Keep worktree on completion (skip merge and cleanup for retry)`).option(`--skip-launch`,`Skip launch-command delegation (used by inner invocations)`).option(`-b, --background`,`Run the workflow in a detached background process`).action(async(e,t)=>{try{if(t.background){let n=i(e,t);c(`Started workflow in background${n?` (PID: ${n})`:``}`),r(0);return}let o=await a(e,t),s=n();try{let n=Z(t),i=await s.run({cliAgent:t.cliAgent,runner:n,workflowPath:e,job:t.job,inputs:X(t.input),env:X(t.env),secretFile:t.secretFile,dryRun:t.dryRun,continueOnError:t.continueOnError,keepWorktree:t.keepWorktree,prompt:t.prompt,name:t.name,workspace:t.workspace,skipLaunch:t.skipLaunch});await o(),r(i.exitCode)}catch(e){throw await o(),e}}catch(n){let i=n instanceof p?n:new p(`Error executing run-workflow.`,q,{background:!!t.background,command:K,workflow:e,workspace:t.workspace},{cause:n});o(`${i.message} [${i.code}]`,i),r(1)}})}const tt=et(),$=`schedule-cron`;function nt(t={}){let n=t.createService??(()=>new e.o),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(`${e}\n`));return new s.Command($).description(`Schedule a headless Claude Code or Codex CLI run via system crontab`).argument(`<name>`,`Unique name for this cron job`).option(`-d, --cwd <path>`,`Working directory for the CLI run`,process.cwd()).option(`-c, --cli <cli>`,`CLI to use: claude or codex`,e.s).option(`-p, --prompt <text>`,`Prompt to pass to the CLI`).option(`-f, --prompt-file <path>`,`Path to a file whose content is used as the prompt (read at cron execution time)`).option(`-s, --schedule <cron>`,`Cron expression (e.g., "*/10 * * * *")`).option(`-i, --interval-minutes <minutes>`,`Run every N minutes (alternative to --schedule)`).action(async(t,o)=>{try{let i=e.c.parse({name:t,cwd:o.cwd??process.cwd(),cli:o.cli??`claude`,prompt:o.prompt,promptFile:o.promptFile,schedule:o.schedule,intervalMinutes:o.intervalMinutes?Number.parseInt(o.intervalMinutes,10):void 0}),s=await n().schedule(i);a(`Scheduled cron job "${s.name}" with schedule: ${s.schedule}`),a(`CLI: ${s.cli} | CWD: ${s.cwd}`),s.prompt&&a(`Prompt: ${s.prompt}`),r(0)}catch(e){let n=e instanceof p?e:new p(`Error scheduling cron job.`,`SCHEDULE_CRON_COMMAND_FAILED`,{command:$,name:t},{cause:e});i(`${n.message} [${n.code}]`,n),r(1)}})}const rt=nt();async function it(){let e=new s.Command;e.name(`workflow-mcp`).description(`MCP server for running GitHub Actions workflows locally`).version(l),e.addCommand(We),e.addCommand(Ke),e.addCommand(g),e.addCommand(se),e.addCommand(O),e.addCommand(Ve),e.addCommand(He),e.addCommand(Qe),e.addCommand(tt),e.addCommand(rt),await e.parseAsync(process.argv)}it().catch(e=>{console.error(`[CLI_STARTUP_ERROR] workflow-mcp startup failed:`,e),process.exit(1)});
2
+ const e=require(`./stdio-BqdxXSzu.cjs`);let t=require(`node:child_process`),n=require(`node:fs/promises`),r=require(`node:path`),i=require(`node:fs`),a=require(`@agimon-ai/foundation-process-registry`),o=require(`node:readline`),s=require(`commander`),c=require(`node:module`);var l=`0.1.12`;const u=`WORKFLOW_STATUS_FILE`,ee={readStdin:oe,readStatusFile:se,writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await(0,n.writeFile)(e,`YES
3
+ `,`utf8`)},workflowStatusFile:process.env[u]};async function te(e=ee){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=d(n);if(!r?.conversationId)return;let i=await e.readStatusFile(t);if(!(i===null||i.trim()===`YES`)){if(!r.fullyIdle){e.writeStdout?.(ne(`In-progress work is still running. Continue the execution loop until background tasks finish.`));return}ie(r.error)||r.terminationReason!==`model_stop`||await e.writeStatusFile(t)}}function d(e){try{let t=JSON.parse(e);return ae(t)?{conversationId:typeof t.conversationId==`string`?t.conversationId:void 0,transcriptPath:re(t.transcriptPath),terminationReason:typeof t.terminationReason==`string`?t.terminationReason:void 0,error:typeof t.error==`string`?t.error:void 0,fullyIdle:t.fullyIdle===!0}:null}catch{return null}}function ne(e){return`${JSON.stringify({decision:`continue`,reason:e})}\n`}function re(e){return typeof e==`string`&&e.trim().length>0?e:null}function ie(e){return typeof e==`string`&&e.trim().length>0}function ae(e){return typeof e==`object`&&!!e}async function oe(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function se(e){try{return await(0,n.readFile)(e,`utf8`)}catch{return null}}const ce=new s.Command(`antigravity-stop-hook`).description(`Antigravity stop hook: reads Stop hook JSON on stdin and marks ${u} when the execution is fully idle`).action(async()=>{await te()});var f=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`WorkflowCommandError`}};const p=`check-codex-quota`;function m(t={}){let n=t.createService??(()=>new e.i),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(e));return new s.Command(p).description(`Check whether Codex quota is currently blocking new work`).action(async()=>{try{let e=await n().getQuotaStatus();if(a(`${JSON.stringify({blockingLimit:e?.blockingLimit??null,planType:e?.planType??null},null,2)}\n`),e?.blockingLimit){let t=new f(`Codex quota is blocking work at ${e.blockingLimit.limitId}/${e.blockingLimit.window}.`,`CODEX_QUOTA_BLOCKED`,{command:p,limitId:e.blockingLimit.limitId,limitName:e.blockingLimit.limitName,window:e.blockingLimit.window});i(`${t.message} [${t.code}]`,t),r(2);return}r(0)}catch(e){let t=e instanceof f?e:new f(`Error checking Codex quota.`,`CHECK_CODEX_QUOTA_COMMAND_FAILED`,{command:p},{cause:e});i(`${t.message} [${t.code}]`,t),r(1)}})}const h=m(),g=`WORKFLOW_STATUS_FILE`,_=`session_meta`,le=[`sub_agent`,`subagent`],ue=new Set([`in_progress`,`inProgress`,`running`,`pending_init`,`pendingInit`]),de=new Set([`collabAgentToolCall`,`commandExecution`,`dynamicToolCall`,`imageGeneration`,`local_shell_call`,`mcpToolCall`]),fe={readStdin:ye,readStatusFile:be,readTranscriptHead:b,readTranscriptState:x,writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await(0,n.writeFile)(e,`YES
4
+ `,`utf8`)},workflowStatusFile:process.env[g]};async function pe(e=fe){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=ge(n);if(!r||r.hook_event_name&&r.hook_event_name!==`Stop`||r.stop_hook_active&&!r.hasTaskRegistry)return;let i=_e(await e.readStatusFile(t));if(!r.session_id||r.session_id!==i)return;let a=v(r.transcript_path);if(!a)return;let o=await he(e,a),s=o.sessionMeta;if(!s||r.session_id&&s.id&&r.session_id!==s.id||ve(s.source))return;let c=r.hasTaskRegistry&&(r.background_tasks?.length??0)>0,l=r.hasTaskRegistry&&(r.session_crons?.length??0)>0;if(c||!r.hasTaskRegistry&&o.hasPendingWork){e.writeStdout?.(me(`In-progress work is still running. Wait for spawned agents and running tool calls to finish before ending the turn.`));return}l||await e.writeStatusFile(t)}function me(e){return`${JSON.stringify({decision:`block`,reason:e,suppressOutput:!0})}\n`}async function he(e,t){return e.readTranscriptState?e.readTranscriptState(t):e.readTranscriptHead===b?x(t):{sessionMeta:await e.readTranscriptHead(t),hasPendingWork:!1}}function ge(e){try{let t=JSON.parse(e);if(!y(t))return null;let n=Array.isArray(t.background_tasks)?t.background_tasks:void 0,r=Array.isArray(t.session_crons)?t.session_crons:void 0;return{hook_event_name:typeof t.hook_event_name==`string`?t.hook_event_name:void 0,session_id:typeof t.session_id==`string`?t.session_id:void 0,transcript_path:v(t.transcript_path),stop_hook_active:t.stop_hook_active===!0,background_tasks:n,session_crons:r,hasTaskRegistry:n!==void 0||r!==void 0}}catch{return null}}function v(e){return typeof e==`string`&&e.trim().length>0?e:null}function _e(e){if(!e)return null;let t=e.trim();if(!t||t===`YES`)return null;try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?e.sessionId:null}catch{return null}}function ve(e){return typeof e==`string`?e.startsWith(`subagent_`)||e.startsWith(`internal_`):y(e)?le.some(t=>t in e):!1}function y(e){return typeof e==`object`&&!!e}async function ye(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function be(e){try{return await(0,n.readFile)(e,`utf8`)}catch{return null}}async function b(e){return(await x(e)).sessionMeta}async function x(e){let t=(0,i.createReadStream)(e,{encoding:`utf8`}),n=(0,o.createInterface)({input:t,crlfDelay:1/0}),r=new Set,a=new Map,s=null;try{for await(let e of n){let t=e.trim();if(t.length===0)continue;let n=xe(t);n&&(s||=Se(n),Ce(n,r),we(n,a))}}finally{n.close(),t.destroy()}return{sessionMeta:s,hasPendingWork:r.size>0||Array.from(a.values()).some(w)}}function xe(e){try{let t=JSON.parse(e);return y(t)?t:null}catch{return null}}function Se(e){return e.type===_?e.payload??null:e.item?.type===_?e.item.payload??null:null}function Ce(e,t){let n=T(e,[`payload`,`item`])??T(e,[`item`,`payload`,`item`]);if(!n)return;let r=E(n.id),i=E(n.type);!r||!i||!de.has(i)||(w(n.status)?t.add(r):t.delete(r))}function we(e,t){let n=T(e,[`payload`])??T(e,[`item`,`payload`]);if(!n||!E(n.type)?.startsWith(`collab_`))return;S(t,E(n.new_thread_id),n.status),S(t,E(n.receiver_thread_id),n.status);let r=T(n,[`statuses`]);if(r)for(let[e,n]of Object.entries(r))S(t,e,n)}function S(e,t,n){let r=C(n);!t||!r||e.set(t,r)}function C(e){if(typeof e==`string`)return e;if(!y(e))return null;let[t]=Object.keys(e);return t??null}function w(e){let t=typeof e==`string`?e:C(e);return t!==null&&ue.has(t)}function T(e,t){let n=e;for(let e of t){if(!y(n))return null;n=n[e]}return y(n)?n:null}function E(e){return typeof e==`string`&&e.length>0?e:void 0}const D=new s.Command(`claude-stop-hook`).description(`Claude Code stop hook: reads Stop hook JSON on stdin and marks ${g} when the root session is complete`).action(async()=>{await pe()}),O=`WORKFLOW_STATUS_FILE`,k=`session_meta`,Te=[`sub_agent`,`subagent`],Ee=new Set([`in_progress`,`inProgress`,`running`,`pending_init`,`pendingInit`]),De=new Set([`collabAgentToolCall`,`commandExecution`,`dynamicToolCall`,`imageGeneration`,`local_shell_call`,`mcpToolCall`]),Oe={readStdin:M,readStatusFile:N,readTranscriptHead:P,readTranscriptState:F,writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await(0,n.writeFile)(e,`YES
5
+ `,`utf8`)},workflowStatusFile:process.env[O]},ke={readStdin:M,readStatusFile:N,writeSessionBinding:async(e,t)=>{await(0,n.writeFile)(e,`${JSON.stringify({sessionId:t})}\n`,`utf8`)},workflowStatusFile:process.env[O]};async function Ae(e=ke){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=Pe(n);if(!r?.session_id)return;let i=await e.readStatusFile(t);i===null||i.trim().length>0||await e.writeSessionBinding(t,r.session_id)}async function je(e=Oe){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=Fe(n);if(!r||r.hook_event_name&&r.hook_event_name!==`Stop`||r.stop_hook_active&&!r.hasTaskRegistry)return;let i=Ie(await e.readStatusFile(t));if(!r.session_id||r.session_id!==i)return;let a=A(r.transcript_path);if(!a)return;let o=await Ne(e,a),s=o.sessionMeta;if(!s||r.session_id&&s.id&&r.session_id!==s.id||Le(s.source))return;let c=r.hasTaskRegistry&&(r.background_tasks?.length??0)>0,l=r.hasTaskRegistry&&(r.session_crons?.length??0)>0;if(c||!r.hasTaskRegistry&&o.hasPendingWork){e.writeStdout?.(Me(`In-progress work is still running. Wait for spawned agents and running tool calls to finish before ending the turn.`));return}l||await e.writeStatusFile(t)}function Me(e){return`${JSON.stringify({decision:`block`,reason:e,suppressOutput:!0})}\n`}async function Ne(e,t){return e.readTranscriptState?e.readTranscriptState(t):e.readTranscriptHead===P?F(t):{sessionMeta:await e.readTranscriptHead(t),hasPendingWork:!1}}function Pe(e){try{let t=JSON.parse(e);return j(t)?{session_id:typeof t.session_id==`string`?t.session_id:void 0}:null}catch{return null}}function Fe(e){try{let t=JSON.parse(e);if(!j(t))return null;let n=Array.isArray(t.background_tasks)?t.background_tasks:void 0,r=Array.isArray(t.session_crons)?t.session_crons:void 0;return{hook_event_name:typeof t.hook_event_name==`string`?t.hook_event_name:void 0,session_id:typeof t.session_id==`string`?t.session_id:void 0,transcript_path:A(t.transcript_path),stop_hook_active:t.stop_hook_active===!0,background_tasks:n,session_crons:r,hasTaskRegistry:n!==void 0||r!==void 0}}catch{return null}}function A(e){return typeof e==`string`&&e.trim().length>0?e:null}function Ie(e){if(!e)return null;let t=e.trim();if(!t||t===`YES`)return null;try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?e.sessionId:null}catch{return null}}function Le(e){return typeof e==`string`?e.startsWith(`subagent_`)||e.startsWith(`internal_`):j(e)?Te.some(t=>t in e):!1}function j(e){return typeof e==`object`&&!!e}async function M(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function N(e){try{return await(0,n.readFile)(e,`utf8`)}catch{return null}}async function P(e){return(await F(e)).sessionMeta}async function F(e){let t=(0,i.createReadStream)(e,{encoding:`utf8`}),n=(0,o.createInterface)({input:t,crlfDelay:1/0}),r=new Set,a=new Map,s=null;try{for await(let e of n){let t=e.trim();if(t.length===0)continue;let n=Re(t);n&&(s||=ze(n),Be(n,r),Ve(n,a))}}finally{n.close(),t.destroy()}return{sessionMeta:s,hasPendingWork:r.size>0||Array.from(a.values()).some(R)}}function Re(e){try{let t=JSON.parse(e);return j(t)?t:null}catch{return null}}function ze(e){return e.type===k?e.payload??null:e.item?.type===k?e.item.payload??null:null}function Be(e,t){let n=z(e,[`payload`,`item`])??z(e,[`item`,`payload`,`item`]);if(!n)return;let r=B(n.id),i=B(n.type);!r||!i||!De.has(i)||(R(n.status)?t.add(r):t.delete(r))}function Ve(e,t){let n=z(e,[`payload`])??z(e,[`item`,`payload`]);if(!n||!B(n.type)?.startsWith(`collab_`))return;I(t,B(n.new_thread_id),n.status),I(t,B(n.receiver_thread_id),n.status);let r=z(n,[`statuses`]);if(r)for(let[e,n]of Object.entries(r))I(t,e,n)}function I(e,t,n){let r=L(n);!t||!r||e.set(t,r)}function L(e){if(typeof e==`string`)return e;if(!j(e))return null;let[t]=Object.keys(e);return t??null}function R(e){let t=typeof e==`string`?e:L(e);return t!==null&&Ee.has(t)}function z(e,t){let n=e;for(let e of t){if(!j(n))return null;n=n[e]}return j(n)?n:null}function B(e){return typeof e==`string`&&e.length>0?e:void 0}const He=new s.Command(`codex-session-start-hook`).description(`Codex session-start hook: binds ${O} to the first workflow Codex session id`).action(async()=>{await Ae()}),Ue=new s.Command(`codex-stop-hook`).description(`Codex stop hook: reads stop-hook JSON on stdin and marks ${O} when the root session ends`).action(async()=>{await je()}),V=`list-crons`;function We(t={}){let n=t.createService??(()=>new e.o),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(e));return new s.Command(V).description(`List cron jobs scheduled via workflow-mcp`).action(async()=>{try{let e=await n().list();a(`${JSON.stringify(e,null,2)}\n`),r(0)}catch(e){let t=new f(`Error listing cron jobs.`,`LIST_CRONS_COMMAND_FAILED`,{command:V},{cause:e});i(`${t.message} [${t.code}]`,t),r(1)}})}const Ge=We(),H=`list-workflow-statuses`;function U(e){let t=Number(e);if(!Number.isInteger(t)||t<1)throw new s.InvalidArgumentError(`Expected a positive integer.`);return t}function Ke(t={}){let n=t.createRegistry??(()=>new e.a),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(e));return new s.Command(H).description(`List tracked workflow runs and their current stages`).option(`--page <number>`,`Page number to return`,U,1).option(`--page-size <number>`,`Number of workflow runs per page`,U,20).option(`-w, --workspace <name>`,`Filter workflow runs by workspace`,`all`).action(async e=>{try{let t=n(),i=e.workspace===`all`?void 0:e.workspace,o=await t.listRunsPage({page:e.page,pageSize:e.pageSize,workspace:i});a(`${JSON.stringify(o,null,2)}\n`),r(0)}catch(t){let n=new f(`Error listing workflow statuses.`,`LIST_WORKFLOW_STATUSES_FAILED`,{command:H,workspace:e.workspace??`all`},{cause:t});i(`${n.message} [${n.code}]`,n),r(1)}})}const qe=Ke(),Je=()=>{try{return(0,c.createRequire)(require(`url`).pathToFileURL(__filename).href)(`@agimon-ai/foundation-process-registry`)}catch{return null}};function W(e,t){for(let n of e){if(n==null||typeof n!=`object`&&typeof n!=`function`)continue;let e=n;for(let n of t){let t=e[n];if(typeof t==`function`)return t}}return null}function Ye(e){let t=[e];if(e&&typeof e==`object`){let n=e.default;if(n&&(t.push(n),typeof n==`function`))try{t.push(new n)}catch{}}return t}async function Xe(e){let t=Je();if(!t)return async()=>{};let n=Ye(t),r=W(n,[`registerProcess`,`register`,`registerProcessInstance`]),i=W(n,[`unregisterProcess`,`unregister`,`releaseProcess`]);if(!r)return async()=>{};try{await Promise.resolve(r({name:e,pid:process.pid,command:process.argv.join(` `)}))}catch{return async()=>{}}return i?async()=>{await Promise.resolve(i({name:e,pid:process.pid}))}:async()=>{}}async function Ze(e,t){await e.start();let n=async n=>{console.error(`\\nReceived ${n}, shutting down gracefully...`);let r=0;try{await e.stop()}catch(e){console.error(`Error during shutdown:`,e),r=1}try{await t()}catch(e){console.error(`Error during resource cleanup:`,e),r=1}process.exit(r)};process.on(`SIGINT`,()=>{n(`SIGINT`)}),process.on(`SIGTERM`,()=>{n(`SIGTERM`)})}async function Qe(e){try{await e()}catch{}}const $e=new s.Command(`mcp-serve`).description(`Start MCP server with specified transport`).option(`-t, --type <type>`,`Transport type: stdio`,`stdio`).option(`--service-name <name>`,`Service name for registry tracking`,`workflow-mcp`).action(async t=>{let n=await Xe(t.serviceName);try{let r=t.type.toLowerCase();r===`stdio`?await Ze(new e.t(e.n()),n):(console.error(`Unknown transport type: ${r}. Use: stdio`),process.exit(1))}catch(e){await Qe(n),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),G=`run-workflow`,K=`RUN_WORKFLOW_COMMAND_FAILED`,q=`WORKFLOW_MCP_BACKGROUND_CHILD`,J=`workflow-mcp-background-run`;function Y(e){if(!e||e.length===0)return;let t={};for(let n of e){let[e,...r]=n.split(`=`);t[e]=r.join(`=`)}return Object.keys(t).length>0?t:void 0}function X(e){if(e.runner&&e.cliAgent&&e.runner!==e.cliAgent)throw new f(`Conflicting runner selectors.`,K,{cliAgent:e.cliAgent,command:G,runner:e.runner});return e.runner??e.cliAgent}function Z(e,n){let r=process.argv[1];if(!r)throw new f(`Unable to determine the workflow-mcp CLI entry point for background execution.`,`RUN_WORKFLOW_BACKGROUND_LAUNCH_FAILED`,{command:G,workflow:e,workspace:n.workspace});let i=[r,`run-workflow`,e];n.job&&i.push(`--job`,n.job);for(let e of n.input??[])i.push(`--input`,e);for(let e of n.env??[])i.push(`--env`,e);n.secretFile&&i.push(`--secret-file`,n.secretFile),n.dryRun&&i.push(`--dry-run`),n.continueOnError&&i.push(`--continue-on-error`),n.keepWorktree&&i.push(`--keep-worktree`);let a=X(n);a&&i.push(`--runner`,a),n.prompt&&i.push(`--prompt`,n.prompt),n.name&&i.push(`--name`,n.name),n.workspace&&i.push(`--workspace`,n.workspace);let o=(0,t.spawn)(process.execPath,i,{detached:!0,env:{...process.env,[q]:`1`},stdio:`ignore`});return o.unref(),o.pid}async function et(e,t){if(process.env[q]!==`1`)return async()=>{};let n=new a.ProcessRegistryService(process.env.PROCESS_REGISTRY_PATH),i=(0,r.resolve)(process.cwd()),o=process.env.NODE_ENV??`development`;return(await n.registerProcess({repositoryPath:i,serviceName:J,serviceType:`tool`,environment:o,pid:process.pid,command:process.argv.join(` `),args:process.argv.slice(2),metadata:{workflow:e,workspace:t.workspace,job:t.job,name:t.name,runner:t.runner??t.cliAgent},force:!0})).success?async()=>{let e=await n.releaseProcess({repositoryPath:i,serviceName:J,serviceType:`tool`,environment:o,pid:process.pid,kill:!1,releasePort:!1,force:!0});if(!e.success&&!e.error?.includes(`No matching process entry`))throw Error(e.error??`Failed to release workflow background process`)}:async()=>{}}function tt(t={}){let n=t.createService??(()=>new e.r),r=t.exit??(e=>process.exit(e)),i=t.launchBackgroundRun??Z,a=t.registerBackgroundChild??et,o=t.logError??((e,t)=>console.error(e,t)),c=t.logInfo??(e=>process.stdout.write(`${e}\n`));return new s.Command(G).description(`Run a GitHub Actions workflow file locally on macOS`).argument(`<workflow>`,`Path to the workflow YAML file`).option(`-j, --job <name>`,`Run only this job (and its dependencies)`).option(`-i, --input <key=value...>`,`Set workflow_dispatch input (repeatable)`).option(`-e, --env <key=value...>`,`Set extra environment variable (repeatable)`).option(`--secret-file <path>`,`Load secrets from a dotenv-style file`).option(`--dry-run`,`Print steps without executing`).option(`--continue-on-error`,`Continue past step failures`).option(`--runner <runner>`,`Preferred runner key for step command maps`).option(`--cli-agent <agent>`,`Deprecated alias for --runner`).option(`-p, --prompt <text>`,`User prompt for user_prompt trigger workflows`).option(`-n, --name <name>`,`Name for the workflow run context directory`).option(`-w, --workspace <name>`,`Workspace for workflow registry storage`).option(`--keep-worktree`,`Keep worktree on completion (skip merge and cleanup for retry)`).option(`--skip-launch`,`Skip launch-command delegation (used by inner invocations)`).option(`-b, --background`,`Run the workflow in a detached background process`).action(async(e,t)=>{try{if(t.background){let n=i(e,t);c(`Started workflow in background${n?` (PID: ${n})`:``}`),r(0);return}let o=await a(e,t),s=n();try{let n=X(t),i=await s.run({cliAgent:t.cliAgent,runner:n,workflowPath:e,job:t.job,inputs:Y(t.input),env:Y(t.env),secretFile:t.secretFile,dryRun:t.dryRun,continueOnError:t.continueOnError,keepWorktree:t.keepWorktree,prompt:t.prompt,name:t.name,workspace:t.workspace,skipLaunch:t.skipLaunch});await o(),r(i.exitCode)}catch(e){throw await o(),e}}catch(n){let i=n instanceof f?n:new f(`Error executing run-workflow.`,K,{background:!!t.background,command:G,workflow:e,workspace:t.workspace},{cause:n});o(`${i.message} [${i.code}]`,i),r(1)}})}const nt=tt(),Q=`schedule-cron`;function rt(t={}){let n=t.createService??(()=>new e.o),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(`${e}\n`));return new s.Command(Q).description(`Schedule a headless Claude Code or Codex CLI run via system crontab`).argument(`<name>`,`Unique name for this cron job`).option(`-d, --cwd <path>`,`Working directory for the CLI run`,process.cwd()).option(`-c, --cli <cli>`,`CLI to use: claude or codex`,e.s).option(`-p, --prompt <text>`,`Prompt to pass to the CLI`).option(`-f, --prompt-file <path>`,`Path to a file whose content is used as the prompt (read at cron execution time)`).option(`-s, --schedule <cron>`,`Cron expression (e.g., "*/10 * * * *")`).option(`-i, --interval-minutes <minutes>`,`Run every N minutes (alternative to --schedule)`).action(async(t,o)=>{try{let i=e.c.parse({name:t,cwd:o.cwd??process.cwd(),cli:o.cli??`claude`,prompt:o.prompt,promptFile:o.promptFile,schedule:o.schedule,intervalMinutes:o.intervalMinutes?Number.parseInt(o.intervalMinutes,10):void 0}),s=await n().schedule(i);a(`Scheduled cron job "${s.name}" with schedule: ${s.schedule}`),a(`CLI: ${s.cli} | CWD: ${s.cwd}`),s.prompt&&a(`Prompt: ${s.prompt}`),r(0)}catch(e){let n=e instanceof f?e:new f(`Error scheduling cron job.`,`SCHEDULE_CRON_COMMAND_FAILED`,{command:Q,name:t},{cause:e});i(`${n.message} [${n.code}]`,n),r(1)}})}const it=rt(),$=`stop-workflow`;function at(t={}){let n=t.createRegistry??(()=>new e.a),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(e));return new s.Command($).description(`Request a running workflow to stop gracefully`).argument(`<run-key>`,`Running workflow run key`).option(`-w, --workspace <name>`,`Workspace containing the running workflow`,`default`).option(`-r, --reason <text>`,`Optional stop reason to record`).action(async(e,t)=>{try{let i=n(),o=await i.requestStop(t.workspace,e,t.reason);a(`${JSON.stringify({reason:o.reason,requestedAt:o.requestedAt,runKey:e,workspace:i.resolveWorkspace(t.workspace)},null,2)}\n`),r(0)}catch(n){let a=new f(`Error requesting workflow stop.`,`STOP_WORKFLOW_FAILED`,{command:$,runKey:e,workspace:t.workspace},{cause:n});i(`${a.message} [${a.code}]`,a),r(1)}})}const ot=at();async function st(){let e=new s.Command;e.name(`workflow-mcp`).description(`MCP server for running GitHub Actions workflows locally`).version(l),e.addCommand(Ge),e.addCommand(qe),e.addCommand(h),e.addCommand(ce),e.addCommand(D),e.addCommand(He),e.addCommand(Ue),e.addCommand($e),e.addCommand(nt),e.addCommand(it),e.addCommand(ot),await e.parseAsync(process.argv)}st().catch(e=>{console.error(`[CLI_STARTUP_ERROR] workflow-mcp startup failed:`,e),process.exit(1)});
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{a as e,c as t,i as n,n as r,o as i,r as a,s as o,t as s}from"./stdio-LgqV_E3T.mjs";import{createRequire as c}from"node:module";import{spawn as l}from"node:child_process";import{readFile as u,writeFile as d}from"node:fs/promises";import{resolve as ee}from"node:path";import{createReadStream as f}from"node:fs";import{createInterface as p}from"node:readline";import{ProcessRegistryService as te}from"@agimon-ai/foundation-process-registry";import{Command as m,InvalidArgumentError as ne}from"commander";var re=`0.1.11`;const h=`WORKFLOW_STATUS_FILE`,ie={readStdin:de,readStatusFile:fe,writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await d(e,`YES
2
+ import{a as e,c as t,i as n,n as r,o as i,r as a,s as o,t as s}from"./stdio-gREqGN_j.mjs";import{createRequire as c}from"node:module";import{spawn as l}from"node:child_process";import{readFile as u,writeFile as d}from"node:fs/promises";import{resolve as ee}from"node:path";import{createReadStream as f}from"node:fs";import{ProcessRegistryService as te}from"@agimon-ai/foundation-process-registry";import{createInterface as p}from"node:readline";import{Command as m,InvalidArgumentError as ne}from"commander";var re=`0.1.12`;const h=`WORKFLOW_STATUS_FILE`,ie={readStdin:de,readStatusFile:fe,writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await d(e,`YES
3
3
  `,`utf8`)},workflowStatusFile:process.env[h]};async function ae(e=ie){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=oe(n);if(!r?.conversationId)return;let i=await e.readStatusFile(t);if(!(i===null||i.trim()===`YES`)){if(!r.fullyIdle){e.writeStdout?.(se(`In-progress work is still running. Continue the execution loop until background tasks finish.`));return}le(r.error)||r.terminationReason!==`model_stop`||await e.writeStatusFile(t)}}function oe(e){try{let t=JSON.parse(e);return ue(t)?{conversationId:typeof t.conversationId==`string`?t.conversationId:void 0,transcriptPath:ce(t.transcriptPath),terminationReason:typeof t.terminationReason==`string`?t.terminationReason:void 0,error:typeof t.error==`string`?t.error:void 0,fullyIdle:t.fullyIdle===!0}:null}catch{return null}}function se(e){return`${JSON.stringify({decision:`continue`,reason:e})}\n`}function ce(e){return typeof e==`string`&&e.trim().length>0?e:null}function le(e){return typeof e==`string`&&e.trim().length>0}function ue(e){return typeof e==`object`&&!!e}async function de(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function fe(e){try{return await u(e,`utf8`)}catch{return null}}const pe=new m(`antigravity-stop-hook`).description(`Antigravity stop hook: reads Stop hook JSON on stdin and marks ${h} when the execution is fully idle`).action(async()=>{await ae()});var g=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`WorkflowCommandError`}};const _=`check-codex-quota`;function me(e={}){let t=e.createService??(()=>new n),r=e.exit??(e=>process.exit(e)),i=e.logError??((e,t)=>console.error(e,t)),a=e.writeStdout??(e=>process.stdout.write(e));return new m(_).description(`Check whether Codex quota is currently blocking new work`).action(async()=>{try{let e=await t().getQuotaStatus();if(a(`${JSON.stringify({blockingLimit:e?.blockingLimit??null,planType:e?.planType??null},null,2)}\n`),e?.blockingLimit){let t=new g(`Codex quota is blocking work at ${e.blockingLimit.limitId}/${e.blockingLimit.window}.`,`CODEX_QUOTA_BLOCKED`,{command:_,limitId:e.blockingLimit.limitId,limitName:e.blockingLimit.limitName,window:e.blockingLimit.window});i(`${t.message} [${t.code}]`,t),r(2);return}r(0)}catch(e){let t=e instanceof g?e:new g(`Error checking Codex quota.`,`CHECK_CODEX_QUOTA_COMMAND_FAILED`,{command:_},{cause:e});i(`${t.message} [${t.code}]`,t),r(1)}})}const he=me(),v=`WORKFLOW_STATUS_FILE`,y=`session_meta`,ge=[`sub_agent`,`subagent`],_e=new Set([`in_progress`,`inProgress`,`running`,`pending_init`,`pendingInit`]),ve=new Set([`collabAgentToolCall`,`commandExecution`,`dynamicToolCall`,`imageGeneration`,`local_shell_call`,`mcpToolCall`]),ye={readStdin:Ee,readStatusFile:De,readTranscriptHead:S,readTranscriptState:C,writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await d(e,`YES
4
- `,`utf8`)},workflowStatusFile:process.env[v]};async function be(e=ye){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=Ce(n);if(!r||r.hook_event_name&&r.hook_event_name!==`Stop`||r.stop_hook_active&&!r.hasTaskRegistry)return;let i=we(await e.readStatusFile(t));if(!r.session_id||r.session_id!==i)return;let a=b(r.transcript_path);if(!a)return;let o=await Se(e,a),s=o.sessionMeta;if(!s||r.session_id&&s.id&&r.session_id!==s.id||Te(s.source))return;let c=r.hasTaskRegistry&&(r.background_tasks?.length??0)>0,l=r.hasTaskRegistry&&(r.session_crons?.length??0)>0;if(c||!r.hasTaskRegistry&&o.hasPendingWork){e.writeStdout?.(xe(`In-progress work is still running. Wait for spawned agents and running tool calls to finish before ending the turn.`));return}l||await e.writeStatusFile(t)}function xe(e){return`${JSON.stringify({decision:`block`,reason:e,suppressOutput:!0})}\n`}async function Se(e,t){return e.readTranscriptState?e.readTranscriptState(t):e.readTranscriptHead===S?C(t):{sessionMeta:await e.readTranscriptHead(t),hasPendingWork:!1}}function Ce(e){try{let t=JSON.parse(e);if(!x(t))return null;let n=Array.isArray(t.background_tasks)?t.background_tasks:void 0,r=Array.isArray(t.session_crons)?t.session_crons:void 0;return{hook_event_name:typeof t.hook_event_name==`string`?t.hook_event_name:void 0,session_id:typeof t.session_id==`string`?t.session_id:void 0,transcript_path:b(t.transcript_path),stop_hook_active:t.stop_hook_active===!0,background_tasks:n,session_crons:r,hasTaskRegistry:n!==void 0||r!==void 0}}catch{return null}}function b(e){return typeof e==`string`&&e.trim().length>0?e:null}function we(e){if(!e)return null;let t=e.trim();if(!t||t===`YES`)return null;try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?e.sessionId:null}catch{return null}}function Te(e){return typeof e==`string`?e.startsWith(`subagent_`)||e.startsWith(`internal_`):x(e)?ge.some(t=>t in e):!1}function x(e){return typeof e==`object`&&!!e}async function Ee(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function De(e){try{return await u(e,`utf8`)}catch{return null}}async function S(e){return(await C(e)).sessionMeta}async function C(e){let t=f(e,{encoding:`utf8`}),n=p({input:t,crlfDelay:1/0}),r=new Set,i=new Map,a=null;try{for await(let e of n){let t=e.trim();if(t.length===0)continue;let n=Oe(t);n&&(a||=ke(n),Ae(n,r),je(n,i))}}finally{n.close(),t.destroy()}return{sessionMeta:a,hasPendingWork:r.size>0||Array.from(i.values()).some(E)}}function Oe(e){try{let t=JSON.parse(e);return x(t)?t:null}catch{return null}}function ke(e){return e.type===y?e.payload??null:e.item?.type===y?e.item.payload??null:null}function Ae(e,t){let n=D(e,[`payload`,`item`])??D(e,[`item`,`payload`,`item`]);if(!n)return;let r=O(n.id),i=O(n.type);!r||!i||!ve.has(i)||(E(n.status)?t.add(r):t.delete(r))}function je(e,t){let n=D(e,[`payload`])??D(e,[`item`,`payload`]);if(!n||!O(n.type)?.startsWith(`collab_`))return;w(t,O(n.new_thread_id),n.status),w(t,O(n.receiver_thread_id),n.status);let r=D(n,[`statuses`]);if(r)for(let[e,n]of Object.entries(r))w(t,e,n)}function w(e,t,n){let r=T(n);!t||!r||e.set(t,r)}function T(e){if(typeof e==`string`)return e;if(!x(e))return null;let[t]=Object.keys(e);return t??null}function E(e){let t=typeof e==`string`?e:T(e);return t!==null&&_e.has(t)}function D(e,t){let n=e;for(let e of t){if(!x(n))return null;n=n[e]}return x(n)?n:null}function O(e){return typeof e==`string`&&e.length>0?e:void 0}const Me=new m(`claude-stop-hook`).description(`Claude Code stop hook: reads Stop hook JSON on stdin and marks ${v} when the root session is complete`).action(async()=>{await be()}),k=`WORKFLOW_STATUS_FILE`,A=`session_meta`,Ne=[`sub_agent`,`subagent`],Pe=new Set([`in_progress`,`inProgress`,`running`,`pending_init`,`pendingInit`]),Fe=new Set([`collabAgentToolCall`,`commandExecution`,`dynamicToolCall`,`imageGeneration`,`local_shell_call`,`mcpToolCall`]),Ie={readStdin:P,readStatusFile:F,readTranscriptHead:I,readTranscriptState:L,writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await d(e,`YES
5
- `,`utf8`)},workflowStatusFile:process.env[k]},Le={readStdin:P,readStatusFile:F,writeSessionBinding:async(e,t)=>{await d(e,`${JSON.stringify({sessionId:t})}\n`,`utf8`)},workflowStatusFile:process.env[k]};async function Re(e=Le){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=He(n);if(!r?.session_id)return;let i=await e.readStatusFile(t);i===null||i.trim().length>0||await e.writeSessionBinding(t,r.session_id)}async function ze(e=Ie){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=Ue(n);if(!r||r.hook_event_name&&r.hook_event_name!==`Stop`||r.stop_hook_active&&!r.hasTaskRegistry)return;let i=M(await e.readStatusFile(t));if(!r.session_id||r.session_id!==i)return;let a=j(r.transcript_path);if(!a)return;let o=await Ve(e,a),s=o.sessionMeta;if(!s||r.session_id&&s.id&&r.session_id!==s.id||We(s.source))return;let c=r.hasTaskRegistry&&(r.background_tasks?.length??0)>0,l=r.hasTaskRegistry&&(r.session_crons?.length??0)>0;if(c||!r.hasTaskRegistry&&o.hasPendingWork){e.writeStdout?.(Be(`In-progress work is still running. Wait for spawned agents and running tool calls to finish before ending the turn.`));return}l||await e.writeStatusFile(t)}function Be(e){return`${JSON.stringify({decision:`block`,reason:e,suppressOutput:!0})}\n`}async function Ve(e,t){return e.readTranscriptState?e.readTranscriptState(t):e.readTranscriptHead===I?L(t):{sessionMeta:await e.readTranscriptHead(t),hasPendingWork:!1}}function He(e){try{let t=JSON.parse(e);return N(t)?{session_id:typeof t.session_id==`string`?t.session_id:void 0}:null}catch{return null}}function Ue(e){try{let t=JSON.parse(e);if(!N(t))return null;let n=Array.isArray(t.background_tasks)?t.background_tasks:void 0,r=Array.isArray(t.session_crons)?t.session_crons:void 0;return{hook_event_name:typeof t.hook_event_name==`string`?t.hook_event_name:void 0,session_id:typeof t.session_id==`string`?t.session_id:void 0,transcript_path:j(t.transcript_path),stop_hook_active:t.stop_hook_active===!0,background_tasks:n,session_crons:r,hasTaskRegistry:n!==void 0||r!==void 0}}catch{return null}}function j(e){return typeof e==`string`&&e.trim().length>0?e:null}function M(e){if(!e)return null;let t=e.trim();if(!t||t===`YES`)return null;try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?e.sessionId:null}catch{return null}}function We(e){return typeof e==`string`?e.startsWith(`subagent_`)||e.startsWith(`internal_`):N(e)?Ne.some(t=>t in e):!1}function N(e){return typeof e==`object`&&!!e}async function P(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function F(e){try{return await u(e,`utf8`)}catch{return null}}async function I(e){return(await L(e)).sessionMeta}async function L(e){let t=f(e,{encoding:`utf8`}),n=p({input:t,crlfDelay:1/0}),r=new Set,i=new Map,a=null;try{for await(let e of n){let t=e.trim();if(t.length===0)continue;let n=Ge(t);n&&(a||=Ke(n),qe(n,r),Je(n,i))}}finally{n.close(),t.destroy()}return{sessionMeta:a,hasPendingWork:r.size>0||Array.from(i.values()).some(B)}}function Ge(e){try{let t=JSON.parse(e);return N(t)?t:null}catch{return null}}function Ke(e){return e.type===A?e.payload??null:e.item?.type===A?e.item.payload??null:null}function qe(e,t){let n=V(e,[`payload`,`item`])??V(e,[`item`,`payload`,`item`]);if(!n)return;let r=H(n.id),i=H(n.type);!r||!i||!Fe.has(i)||(B(n.status)?t.add(r):t.delete(r))}function Je(e,t){let n=V(e,[`payload`])??V(e,[`item`,`payload`]);if(!n||!H(n.type)?.startsWith(`collab_`))return;R(t,H(n.new_thread_id),n.status),R(t,H(n.receiver_thread_id),n.status);let r=V(n,[`statuses`]);if(r)for(let[e,n]of Object.entries(r))R(t,e,n)}function R(e,t,n){let r=z(n);!t||!r||e.set(t,r)}function z(e){if(typeof e==`string`)return e;if(!N(e))return null;let[t]=Object.keys(e);return t??null}function B(e){let t=typeof e==`string`?e:z(e);return t!==null&&Pe.has(t)}function V(e,t){let n=e;for(let e of t){if(!N(n))return null;n=n[e]}return N(n)?n:null}function H(e){return typeof e==`string`&&e.length>0?e:void 0}const Ye=new m(`codex-session-start-hook`).description(`Codex session-start hook: binds ${k} to the first workflow Codex session id`).action(async()=>{await Re()}),Xe=new m(`codex-stop-hook`).description(`Codex stop hook: reads stop-hook JSON on stdin and marks ${k} when the root session ends`).action(async()=>{await ze()}),U=`list-crons`;function Ze(e={}){let t=e.createService??(()=>new i),n=e.exit??(e=>process.exit(e)),r=e.logError??((e,t)=>console.error(e,t)),a=e.writeStdout??(e=>process.stdout.write(e));return new m(U).description(`List cron jobs scheduled via workflow-mcp`).action(async()=>{try{let e=await t().list();a(`${JSON.stringify(e,null,2)}\n`),n(0)}catch(e){let t=new g(`Error listing cron jobs.`,`LIST_CRONS_COMMAND_FAILED`,{command:U},{cause:e});r(`${t.message} [${t.code}]`,t),n(1)}})}const Qe=Ze(),W=`list-workflow-statuses`;function G(e){let t=Number(e);if(!Number.isInteger(t)||t<1)throw new ne(`Expected a positive integer.`);return t}function $e(t={}){let n=t.createRegistry??(()=>new e),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(e));return new m(W).description(`List tracked workflow runs and their current stages`).option(`--page <number>`,`Page number to return`,G,1).option(`--page-size <number>`,`Number of workflow runs per page`,G,20).option(`-w, --workspace <name>`,`Filter workflow runs by workspace`,`all`).action(async e=>{try{let t=n(),i=e.workspace===`all`?void 0:e.workspace,o=await t.listRunsPage({page:e.page,pageSize:e.pageSize,workspace:i});a(`${JSON.stringify(o,null,2)}\n`),r(0)}catch(t){let n=new g(`Error listing workflow statuses.`,`LIST_WORKFLOW_STATUSES_FAILED`,{command:W,workspace:e.workspace??`all`},{cause:t});i(`${n.message} [${n.code}]`,n),r(1)}})}const et=$e(),tt=()=>{try{return c(import.meta.url)(`@agimon-ai/foundation-process-registry`)}catch{return null}};function K(e,t){for(let n of e){if(n==null||typeof n!=`object`&&typeof n!=`function`)continue;let e=n;for(let n of t){let t=e[n];if(typeof t==`function`)return t}}return null}function nt(e){let t=[e];if(e&&typeof e==`object`){let n=e.default;if(n&&(t.push(n),typeof n==`function`))try{t.push(new n)}catch{}}return t}async function rt(e){let t=tt();if(!t)return async()=>{};let n=nt(t),r=K(n,[`registerProcess`,`register`,`registerProcessInstance`]),i=K(n,[`unregisterProcess`,`unregister`,`releaseProcess`]);if(!r)return async()=>{};try{await Promise.resolve(r({name:e,pid:process.pid,command:process.argv.join(` `)}))}catch{return async()=>{}}return i?async()=>{await Promise.resolve(i({name:e,pid:process.pid}))}:async()=>{}}async function it(e,t){await e.start();let n=async n=>{console.error(`\\nReceived ${n}, shutting down gracefully...`);let r=0;try{await e.stop()}catch(e){console.error(`Error during shutdown:`,e),r=1}try{await t()}catch(e){console.error(`Error during resource cleanup:`,e),r=1}process.exit(r)};process.on(`SIGINT`,()=>{n(`SIGINT`)}),process.on(`SIGTERM`,()=>{n(`SIGTERM`)})}async function at(e){try{await e()}catch{}}const ot=new m(`mcp-serve`).description(`Start MCP server with specified transport`).option(`-t, --type <type>`,`Transport type: stdio`,`stdio`).option(`--service-name <name>`,`Service name for registry tracking`,`workflow-mcp`).action(async e=>{let t=await rt(e.serviceName);try{let n=e.type.toLowerCase();n===`stdio`?await it(new s(r()),t):(console.error(`Unknown transport type: ${n}. Use: stdio`),process.exit(1))}catch(e){await at(t),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),q=`run-workflow`,J=`RUN_WORKFLOW_COMMAND_FAILED`,Y=`WORKFLOW_MCP_BACKGROUND_CHILD`,X=`workflow-mcp-background-run`;function Z(e){if(!e||e.length===0)return;let t={};for(let n of e){let[e,...r]=n.split(`=`);t[e]=r.join(`=`)}return Object.keys(t).length>0?t:void 0}function Q(e){if(e.runner&&e.cliAgent&&e.runner!==e.cliAgent)throw new g(`Conflicting runner selectors.`,J,{cliAgent:e.cliAgent,command:q,runner:e.runner});return e.runner??e.cliAgent}function st(e,t){let n=process.argv[1];if(!n)throw new g(`Unable to determine the workflow-mcp CLI entry point for background execution.`,`RUN_WORKFLOW_BACKGROUND_LAUNCH_FAILED`,{command:q,workflow:e,workspace:t.workspace});let r=[n,`run-workflow`,e];t.job&&r.push(`--job`,t.job);for(let e of t.input??[])r.push(`--input`,e);for(let e of t.env??[])r.push(`--env`,e);t.secretFile&&r.push(`--secret-file`,t.secretFile),t.dryRun&&r.push(`--dry-run`),t.continueOnError&&r.push(`--continue-on-error`),t.keepWorktree&&r.push(`--keep-worktree`);let i=Q(t);i&&r.push(`--runner`,i),t.prompt&&r.push(`--prompt`,t.prompt),t.name&&r.push(`--name`,t.name),t.workspace&&r.push(`--workspace`,t.workspace);let a=l(process.execPath,r,{detached:!0,env:{...process.env,[Y]:`1`},stdio:`ignore`});return a.unref(),a.pid}async function ct(e,t){if(process.env[Y]!==`1`)return async()=>{};let n=new te(process.env.PROCESS_REGISTRY_PATH),r=ee(process.cwd()),i=process.env.NODE_ENV??`development`;return(await n.registerProcess({repositoryPath:r,serviceName:X,serviceType:`tool`,environment:i,pid:process.pid,command:process.argv.join(` `),args:process.argv.slice(2),metadata:{workflow:e,workspace:t.workspace,job:t.job,name:t.name,runner:t.runner??t.cliAgent},force:!0})).success?async()=>{let e=await n.releaseProcess({repositoryPath:r,serviceName:X,serviceType:`tool`,environment:i,pid:process.pid,kill:!1,releasePort:!1,force:!0});if(!e.success&&!e.error?.includes(`No matching process entry`))throw Error(e.error??`Failed to release workflow background process`)}:async()=>{}}function lt(e={}){let t=e.createService??(()=>new a),n=e.exit??(e=>process.exit(e)),r=e.launchBackgroundRun??st,i=e.registerBackgroundChild??ct,o=e.logError??((e,t)=>console.error(e,t)),s=e.logInfo??(e=>process.stdout.write(`${e}\n`));return new m(q).description(`Run a GitHub Actions workflow file locally on macOS`).argument(`<workflow>`,`Path to the workflow YAML file`).option(`-j, --job <name>`,`Run only this job (and its dependencies)`).option(`-i, --input <key=value...>`,`Set workflow_dispatch input (repeatable)`).option(`-e, --env <key=value...>`,`Set extra environment variable (repeatable)`).option(`--secret-file <path>`,`Load secrets from a dotenv-style file`).option(`--dry-run`,`Print steps without executing`).option(`--continue-on-error`,`Continue past step failures`).option(`--runner <runner>`,`Preferred runner key for step command maps`).option(`--cli-agent <agent>`,`Deprecated alias for --runner`).option(`-p, --prompt <text>`,`User prompt for user_prompt trigger workflows`).option(`-n, --name <name>`,`Name for the workflow run context directory`).option(`-w, --workspace <name>`,`Workspace for workflow registry storage`).option(`--keep-worktree`,`Keep worktree on completion (skip merge and cleanup for retry)`).option(`--skip-launch`,`Skip launch-command delegation (used by inner invocations)`).option(`-b, --background`,`Run the workflow in a detached background process`).action(async(e,a)=>{try{if(a.background){let t=r(e,a);s(`Started workflow in background${t?` (PID: ${t})`:``}`),n(0);return}let o=await i(e,a),c=t();try{let t=Q(a),r=await c.run({cliAgent:a.cliAgent,runner:t,workflowPath:e,job:a.job,inputs:Z(a.input),env:Z(a.env),secretFile:a.secretFile,dryRun:a.dryRun,continueOnError:a.continueOnError,keepWorktree:a.keepWorktree,prompt:a.prompt,name:a.name,workspace:a.workspace,skipLaunch:a.skipLaunch});await o(),n(r.exitCode)}catch(e){throw await o(),e}}catch(t){let r=t instanceof g?t:new g(`Error executing run-workflow.`,J,{background:!!a.background,command:q,workflow:e,workspace:a.workspace},{cause:t});o(`${r.message} [${r.code}]`,r),n(1)}})}const ut=lt(),$=`schedule-cron`;function dt(e={}){let n=e.createService??(()=>new i),r=e.exit??(e=>process.exit(e)),a=e.logError??((e,t)=>console.error(e,t)),s=e.writeStdout??(e=>process.stdout.write(`${e}\n`));return new m($).description(`Schedule a headless Claude Code or Codex CLI run via system crontab`).argument(`<name>`,`Unique name for this cron job`).option(`-d, --cwd <path>`,`Working directory for the CLI run`,process.cwd()).option(`-c, --cli <cli>`,`CLI to use: claude or codex`,o).option(`-p, --prompt <text>`,`Prompt to pass to the CLI`).option(`-f, --prompt-file <path>`,`Path to a file whose content is used as the prompt (read at cron execution time)`).option(`-s, --schedule <cron>`,`Cron expression (e.g., "*/10 * * * *")`).option(`-i, --interval-minutes <minutes>`,`Run every N minutes (alternative to --schedule)`).action(async(e,i)=>{try{let a=t.parse({name:e,cwd:i.cwd??process.cwd(),cli:i.cli??`claude`,prompt:i.prompt,promptFile:i.promptFile,schedule:i.schedule,intervalMinutes:i.intervalMinutes?Number.parseInt(i.intervalMinutes,10):void 0}),o=await n().schedule(a);s(`Scheduled cron job "${o.name}" with schedule: ${o.schedule}`),s(`CLI: ${o.cli} | CWD: ${o.cwd}`),o.prompt&&s(`Prompt: ${o.prompt}`),r(0)}catch(t){let n=t instanceof g?t:new g(`Error scheduling cron job.`,`SCHEDULE_CRON_COMMAND_FAILED`,{command:$,name:e},{cause:t});a(`${n.message} [${n.code}]`,n),r(1)}})}const ft=dt();async function pt(){let e=new m;e.name(`workflow-mcp`).description(`MCP server for running GitHub Actions workflows locally`).version(re),e.addCommand(Qe),e.addCommand(et),e.addCommand(he),e.addCommand(pe),e.addCommand(Me),e.addCommand(Ye),e.addCommand(Xe),e.addCommand(ot),e.addCommand(ut),e.addCommand(ft),await e.parseAsync(process.argv)}pt().catch(e=>{console.error(`[CLI_STARTUP_ERROR] workflow-mcp startup failed:`,e),process.exit(1)});export{};
4
+ `,`utf8`)},workflowStatusFile:process.env[v]};async function be(e=ye){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=Ce(n);if(!r||r.hook_event_name&&r.hook_event_name!==`Stop`||r.stop_hook_active&&!r.hasTaskRegistry)return;let i=we(await e.readStatusFile(t));if(!r.session_id||r.session_id!==i)return;let a=b(r.transcript_path);if(!a)return;let o=await Se(e,a),s=o.sessionMeta;if(!s||r.session_id&&s.id&&r.session_id!==s.id||Te(s.source))return;let c=r.hasTaskRegistry&&(r.background_tasks?.length??0)>0,l=r.hasTaskRegistry&&(r.session_crons?.length??0)>0;if(c||!r.hasTaskRegistry&&o.hasPendingWork){e.writeStdout?.(xe(`In-progress work is still running. Wait for spawned agents and running tool calls to finish before ending the turn.`));return}l||await e.writeStatusFile(t)}function xe(e){return`${JSON.stringify({decision:`block`,reason:e,suppressOutput:!0})}\n`}async function Se(e,t){return e.readTranscriptState?e.readTranscriptState(t):e.readTranscriptHead===S?C(t):{sessionMeta:await e.readTranscriptHead(t),hasPendingWork:!1}}function Ce(e){try{let t=JSON.parse(e);if(!x(t))return null;let n=Array.isArray(t.background_tasks)?t.background_tasks:void 0,r=Array.isArray(t.session_crons)?t.session_crons:void 0;return{hook_event_name:typeof t.hook_event_name==`string`?t.hook_event_name:void 0,session_id:typeof t.session_id==`string`?t.session_id:void 0,transcript_path:b(t.transcript_path),stop_hook_active:t.stop_hook_active===!0,background_tasks:n,session_crons:r,hasTaskRegistry:n!==void 0||r!==void 0}}catch{return null}}function b(e){return typeof e==`string`&&e.trim().length>0?e:null}function we(e){if(!e)return null;let t=e.trim();if(!t||t===`YES`)return null;try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?e.sessionId:null}catch{return null}}function Te(e){return typeof e==`string`?e.startsWith(`subagent_`)||e.startsWith(`internal_`):x(e)?ge.some(t=>t in e):!1}function x(e){return typeof e==`object`&&!!e}async function Ee(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function De(e){try{return await u(e,`utf8`)}catch{return null}}async function S(e){return(await C(e)).sessionMeta}async function C(e){let t=f(e,{encoding:`utf8`}),n=p({input:t,crlfDelay:1/0}),r=new Set,i=new Map,a=null;try{for await(let e of n){let t=e.trim();if(t.length===0)continue;let n=Oe(t);n&&(a||=ke(n),Ae(n,r),je(n,i))}}finally{n.close(),t.destroy()}return{sessionMeta:a,hasPendingWork:r.size>0||Array.from(i.values()).some(E)}}function Oe(e){try{let t=JSON.parse(e);return x(t)?t:null}catch{return null}}function ke(e){return e.type===y?e.payload??null:e.item?.type===y?e.item.payload??null:null}function Ae(e,t){let n=D(e,[`payload`,`item`])??D(e,[`item`,`payload`,`item`]);if(!n)return;let r=O(n.id),i=O(n.type);!r||!i||!ve.has(i)||(E(n.status)?t.add(r):t.delete(r))}function je(e,t){let n=D(e,[`payload`])??D(e,[`item`,`payload`]);if(!n||!O(n.type)?.startsWith(`collab_`))return;w(t,O(n.new_thread_id),n.status),w(t,O(n.receiver_thread_id),n.status);let r=D(n,[`statuses`]);if(r)for(let[e,n]of Object.entries(r))w(t,e,n)}function w(e,t,n){let r=T(n);!t||!r||e.set(t,r)}function T(e){if(typeof e==`string`)return e;if(!x(e))return null;let[t]=Object.keys(e);return t??null}function E(e){let t=typeof e==`string`?e:T(e);return t!==null&&_e.has(t)}function D(e,t){let n=e;for(let e of t){if(!x(n))return null;n=n[e]}return x(n)?n:null}function O(e){return typeof e==`string`&&e.length>0?e:void 0}const Me=new m(`claude-stop-hook`).description(`Claude Code stop hook: reads Stop hook JSON on stdin and marks ${v} when the root session is complete`).action(async()=>{await be()}),k=`WORKFLOW_STATUS_FILE`,A=`session_meta`,Ne=[`sub_agent`,`subagent`],Pe=new Set([`in_progress`,`inProgress`,`running`,`pending_init`,`pendingInit`]),Fe=new Set([`collabAgentToolCall`,`commandExecution`,`dynamicToolCall`,`imageGeneration`,`local_shell_call`,`mcpToolCall`]),Ie={readStdin:N,readStatusFile:P,readTranscriptHead:F,readTranscriptState:I,writeStdout:e=>{process.stdout.write(e)},writeStatusFile:async e=>{await d(e,`YES
5
+ `,`utf8`)},workflowStatusFile:process.env[k]},Le={readStdin:N,readStatusFile:P,writeSessionBinding:async(e,t)=>{await d(e,`${JSON.stringify({sessionId:t})}\n`,`utf8`)},workflowStatusFile:process.env[k]};async function Re(e=Le){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=He(n);if(!r?.session_id)return;let i=await e.readStatusFile(t);i===null||i.trim().length>0||await e.writeSessionBinding(t,r.session_id)}async function ze(e=Ie){let t=e.workflowStatusFile;if(!t)return;let n=await e.readStdin();if(n.trim().length===0)return;let r=Ue(n);if(!r||r.hook_event_name&&r.hook_event_name!==`Stop`||r.stop_hook_active&&!r.hasTaskRegistry)return;let i=We(await e.readStatusFile(t));if(!r.session_id||r.session_id!==i)return;let a=j(r.transcript_path);if(!a)return;let o=await Ve(e,a),s=o.sessionMeta;if(!s||r.session_id&&s.id&&r.session_id!==s.id||Ge(s.source))return;let c=r.hasTaskRegistry&&(r.background_tasks?.length??0)>0,l=r.hasTaskRegistry&&(r.session_crons?.length??0)>0;if(c||!r.hasTaskRegistry&&o.hasPendingWork){e.writeStdout?.(Be(`In-progress work is still running. Wait for spawned agents and running tool calls to finish before ending the turn.`));return}l||await e.writeStatusFile(t)}function Be(e){return`${JSON.stringify({decision:`block`,reason:e,suppressOutput:!0})}\n`}async function Ve(e,t){return e.readTranscriptState?e.readTranscriptState(t):e.readTranscriptHead===F?I(t):{sessionMeta:await e.readTranscriptHead(t),hasPendingWork:!1}}function He(e){try{let t=JSON.parse(e);return M(t)?{session_id:typeof t.session_id==`string`?t.session_id:void 0}:null}catch{return null}}function Ue(e){try{let t=JSON.parse(e);if(!M(t))return null;let n=Array.isArray(t.background_tasks)?t.background_tasks:void 0,r=Array.isArray(t.session_crons)?t.session_crons:void 0;return{hook_event_name:typeof t.hook_event_name==`string`?t.hook_event_name:void 0,session_id:typeof t.session_id==`string`?t.session_id:void 0,transcript_path:j(t.transcript_path),stop_hook_active:t.stop_hook_active===!0,background_tasks:n,session_crons:r,hasTaskRegistry:n!==void 0||r!==void 0}}catch{return null}}function j(e){return typeof e==`string`&&e.trim().length>0?e:null}function We(e){if(!e)return null;let t=e.trim();if(!t||t===`YES`)return null;try{let e=JSON.parse(t);return typeof e.sessionId==`string`&&e.sessionId.length>0?e.sessionId:null}catch{return null}}function Ge(e){return typeof e==`string`?e.startsWith(`subagent_`)||e.startsWith(`internal_`):M(e)?Ne.some(t=>t in e):!1}function M(e){return typeof e==`object`&&!!e}async function N(){let e=[];for await(let t of process.stdin)e.push(Buffer.isBuffer(t)?t:Buffer.from(String(t)));return Buffer.concat(e).toString(`utf8`)}async function P(e){try{return await u(e,`utf8`)}catch{return null}}async function F(e){return(await I(e)).sessionMeta}async function I(e){let t=f(e,{encoding:`utf8`}),n=p({input:t,crlfDelay:1/0}),r=new Set,i=new Map,a=null;try{for await(let e of n){let t=e.trim();if(t.length===0)continue;let n=Ke(t);n&&(a||=qe(n),Je(n,r),Ye(n,i))}}finally{n.close(),t.destroy()}return{sessionMeta:a,hasPendingWork:r.size>0||Array.from(i.values()).some(z)}}function Ke(e){try{let t=JSON.parse(e);return M(t)?t:null}catch{return null}}function qe(e){return e.type===A?e.payload??null:e.item?.type===A?e.item.payload??null:null}function Je(e,t){let n=B(e,[`payload`,`item`])??B(e,[`item`,`payload`,`item`]);if(!n)return;let r=V(n.id),i=V(n.type);!r||!i||!Fe.has(i)||(z(n.status)?t.add(r):t.delete(r))}function Ye(e,t){let n=B(e,[`payload`])??B(e,[`item`,`payload`]);if(!n||!V(n.type)?.startsWith(`collab_`))return;L(t,V(n.new_thread_id),n.status),L(t,V(n.receiver_thread_id),n.status);let r=B(n,[`statuses`]);if(r)for(let[e,n]of Object.entries(r))L(t,e,n)}function L(e,t,n){let r=R(n);!t||!r||e.set(t,r)}function R(e){if(typeof e==`string`)return e;if(!M(e))return null;let[t]=Object.keys(e);return t??null}function z(e){let t=typeof e==`string`?e:R(e);return t!==null&&Pe.has(t)}function B(e,t){let n=e;for(let e of t){if(!M(n))return null;n=n[e]}return M(n)?n:null}function V(e){return typeof e==`string`&&e.length>0?e:void 0}const Xe=new m(`codex-session-start-hook`).description(`Codex session-start hook: binds ${k} to the first workflow Codex session id`).action(async()=>{await Re()}),Ze=new m(`codex-stop-hook`).description(`Codex stop hook: reads stop-hook JSON on stdin and marks ${k} when the root session ends`).action(async()=>{await ze()}),H=`list-crons`;function Qe(e={}){let t=e.createService??(()=>new i),n=e.exit??(e=>process.exit(e)),r=e.logError??((e,t)=>console.error(e,t)),a=e.writeStdout??(e=>process.stdout.write(e));return new m(H).description(`List cron jobs scheduled via workflow-mcp`).action(async()=>{try{let e=await t().list();a(`${JSON.stringify(e,null,2)}\n`),n(0)}catch(e){let t=new g(`Error listing cron jobs.`,`LIST_CRONS_COMMAND_FAILED`,{command:H},{cause:e});r(`${t.message} [${t.code}]`,t),n(1)}})}const $e=Qe(),U=`list-workflow-statuses`;function W(e){let t=Number(e);if(!Number.isInteger(t)||t<1)throw new ne(`Expected a positive integer.`);return t}function et(t={}){let n=t.createRegistry??(()=>new e),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(e));return new m(U).description(`List tracked workflow runs and their current stages`).option(`--page <number>`,`Page number to return`,W,1).option(`--page-size <number>`,`Number of workflow runs per page`,W,20).option(`-w, --workspace <name>`,`Filter workflow runs by workspace`,`all`).action(async e=>{try{let t=n(),i=e.workspace===`all`?void 0:e.workspace,o=await t.listRunsPage({page:e.page,pageSize:e.pageSize,workspace:i});a(`${JSON.stringify(o,null,2)}\n`),r(0)}catch(t){let n=new g(`Error listing workflow statuses.`,`LIST_WORKFLOW_STATUSES_FAILED`,{command:U,workspace:e.workspace??`all`},{cause:t});i(`${n.message} [${n.code}]`,n),r(1)}})}const tt=et(),nt=()=>{try{return c(import.meta.url)(`@agimon-ai/foundation-process-registry`)}catch{return null}};function G(e,t){for(let n of e){if(n==null||typeof n!=`object`&&typeof n!=`function`)continue;let e=n;for(let n of t){let t=e[n];if(typeof t==`function`)return t}}return null}function rt(e){let t=[e];if(e&&typeof e==`object`){let n=e.default;if(n&&(t.push(n),typeof n==`function`))try{t.push(new n)}catch{}}return t}async function it(e){let t=nt();if(!t)return async()=>{};let n=rt(t),r=G(n,[`registerProcess`,`register`,`registerProcessInstance`]),i=G(n,[`unregisterProcess`,`unregister`,`releaseProcess`]);if(!r)return async()=>{};try{await Promise.resolve(r({name:e,pid:process.pid,command:process.argv.join(` `)}))}catch{return async()=>{}}return i?async()=>{await Promise.resolve(i({name:e,pid:process.pid}))}:async()=>{}}async function at(e,t){await e.start();let n=async n=>{console.error(`\\nReceived ${n}, shutting down gracefully...`);let r=0;try{await e.stop()}catch(e){console.error(`Error during shutdown:`,e),r=1}try{await t()}catch(e){console.error(`Error during resource cleanup:`,e),r=1}process.exit(r)};process.on(`SIGINT`,()=>{n(`SIGINT`)}),process.on(`SIGTERM`,()=>{n(`SIGTERM`)})}async function ot(e){try{await e()}catch{}}const st=new m(`mcp-serve`).description(`Start MCP server with specified transport`).option(`-t, --type <type>`,`Transport type: stdio`,`stdio`).option(`--service-name <name>`,`Service name for registry tracking`,`workflow-mcp`).action(async e=>{let t=await it(e.serviceName);try{let n=e.type.toLowerCase();n===`stdio`?await at(new s(r()),t):(console.error(`Unknown transport type: ${n}. Use: stdio`),process.exit(1))}catch(e){await ot(t),console.error(`Failed to start MCP server:`,e),process.exit(1)}}),K=`run-workflow`,q=`RUN_WORKFLOW_COMMAND_FAILED`,J=`WORKFLOW_MCP_BACKGROUND_CHILD`,Y=`workflow-mcp-background-run`;function X(e){if(!e||e.length===0)return;let t={};for(let n of e){let[e,...r]=n.split(`=`);t[e]=r.join(`=`)}return Object.keys(t).length>0?t:void 0}function Z(e){if(e.runner&&e.cliAgent&&e.runner!==e.cliAgent)throw new g(`Conflicting runner selectors.`,q,{cliAgent:e.cliAgent,command:K,runner:e.runner});return e.runner??e.cliAgent}function ct(e,t){let n=process.argv[1];if(!n)throw new g(`Unable to determine the workflow-mcp CLI entry point for background execution.`,`RUN_WORKFLOW_BACKGROUND_LAUNCH_FAILED`,{command:K,workflow:e,workspace:t.workspace});let r=[n,`run-workflow`,e];t.job&&r.push(`--job`,t.job);for(let e of t.input??[])r.push(`--input`,e);for(let e of t.env??[])r.push(`--env`,e);t.secretFile&&r.push(`--secret-file`,t.secretFile),t.dryRun&&r.push(`--dry-run`),t.continueOnError&&r.push(`--continue-on-error`),t.keepWorktree&&r.push(`--keep-worktree`);let i=Z(t);i&&r.push(`--runner`,i),t.prompt&&r.push(`--prompt`,t.prompt),t.name&&r.push(`--name`,t.name),t.workspace&&r.push(`--workspace`,t.workspace);let a=l(process.execPath,r,{detached:!0,env:{...process.env,[J]:`1`},stdio:`ignore`});return a.unref(),a.pid}async function lt(e,t){if(process.env[J]!==`1`)return async()=>{};let n=new te(process.env.PROCESS_REGISTRY_PATH),r=ee(process.cwd()),i=process.env.NODE_ENV??`development`;return(await n.registerProcess({repositoryPath:r,serviceName:Y,serviceType:`tool`,environment:i,pid:process.pid,command:process.argv.join(` `),args:process.argv.slice(2),metadata:{workflow:e,workspace:t.workspace,job:t.job,name:t.name,runner:t.runner??t.cliAgent},force:!0})).success?async()=>{let e=await n.releaseProcess({repositoryPath:r,serviceName:Y,serviceType:`tool`,environment:i,pid:process.pid,kill:!1,releasePort:!1,force:!0});if(!e.success&&!e.error?.includes(`No matching process entry`))throw Error(e.error??`Failed to release workflow background process`)}:async()=>{}}function ut(e={}){let t=e.createService??(()=>new a),n=e.exit??(e=>process.exit(e)),r=e.launchBackgroundRun??ct,i=e.registerBackgroundChild??lt,o=e.logError??((e,t)=>console.error(e,t)),s=e.logInfo??(e=>process.stdout.write(`${e}\n`));return new m(K).description(`Run a GitHub Actions workflow file locally on macOS`).argument(`<workflow>`,`Path to the workflow YAML file`).option(`-j, --job <name>`,`Run only this job (and its dependencies)`).option(`-i, --input <key=value...>`,`Set workflow_dispatch input (repeatable)`).option(`-e, --env <key=value...>`,`Set extra environment variable (repeatable)`).option(`--secret-file <path>`,`Load secrets from a dotenv-style file`).option(`--dry-run`,`Print steps without executing`).option(`--continue-on-error`,`Continue past step failures`).option(`--runner <runner>`,`Preferred runner key for step command maps`).option(`--cli-agent <agent>`,`Deprecated alias for --runner`).option(`-p, --prompt <text>`,`User prompt for user_prompt trigger workflows`).option(`-n, --name <name>`,`Name for the workflow run context directory`).option(`-w, --workspace <name>`,`Workspace for workflow registry storage`).option(`--keep-worktree`,`Keep worktree on completion (skip merge and cleanup for retry)`).option(`--skip-launch`,`Skip launch-command delegation (used by inner invocations)`).option(`-b, --background`,`Run the workflow in a detached background process`).action(async(e,a)=>{try{if(a.background){let t=r(e,a);s(`Started workflow in background${t?` (PID: ${t})`:``}`),n(0);return}let o=await i(e,a),c=t();try{let t=Z(a),r=await c.run({cliAgent:a.cliAgent,runner:t,workflowPath:e,job:a.job,inputs:X(a.input),env:X(a.env),secretFile:a.secretFile,dryRun:a.dryRun,continueOnError:a.continueOnError,keepWorktree:a.keepWorktree,prompt:a.prompt,name:a.name,workspace:a.workspace,skipLaunch:a.skipLaunch});await o(),n(r.exitCode)}catch(e){throw await o(),e}}catch(t){let r=t instanceof g?t:new g(`Error executing run-workflow.`,q,{background:!!a.background,command:K,workflow:e,workspace:a.workspace},{cause:t});o(`${r.message} [${r.code}]`,r),n(1)}})}const dt=ut(),Q=`schedule-cron`;function ft(e={}){let n=e.createService??(()=>new i),r=e.exit??(e=>process.exit(e)),a=e.logError??((e,t)=>console.error(e,t)),s=e.writeStdout??(e=>process.stdout.write(`${e}\n`));return new m(Q).description(`Schedule a headless Claude Code or Codex CLI run via system crontab`).argument(`<name>`,`Unique name for this cron job`).option(`-d, --cwd <path>`,`Working directory for the CLI run`,process.cwd()).option(`-c, --cli <cli>`,`CLI to use: claude or codex`,o).option(`-p, --prompt <text>`,`Prompt to pass to the CLI`).option(`-f, --prompt-file <path>`,`Path to a file whose content is used as the prompt (read at cron execution time)`).option(`-s, --schedule <cron>`,`Cron expression (e.g., "*/10 * * * *")`).option(`-i, --interval-minutes <minutes>`,`Run every N minutes (alternative to --schedule)`).action(async(e,i)=>{try{let a=t.parse({name:e,cwd:i.cwd??process.cwd(),cli:i.cli??`claude`,prompt:i.prompt,promptFile:i.promptFile,schedule:i.schedule,intervalMinutes:i.intervalMinutes?Number.parseInt(i.intervalMinutes,10):void 0}),o=await n().schedule(a);s(`Scheduled cron job "${o.name}" with schedule: ${o.schedule}`),s(`CLI: ${o.cli} | CWD: ${o.cwd}`),o.prompt&&s(`Prompt: ${o.prompt}`),r(0)}catch(t){let n=t instanceof g?t:new g(`Error scheduling cron job.`,`SCHEDULE_CRON_COMMAND_FAILED`,{command:Q,name:e},{cause:t});a(`${n.message} [${n.code}]`,n),r(1)}})}const pt=ft(),$=`stop-workflow`;function mt(t={}){let n=t.createRegistry??(()=>new e),r=t.exit??(e=>process.exit(e)),i=t.logError??((e,t)=>console.error(e,t)),a=t.writeStdout??(e=>process.stdout.write(e));return new m($).description(`Request a running workflow to stop gracefully`).argument(`<run-key>`,`Running workflow run key`).option(`-w, --workspace <name>`,`Workspace containing the running workflow`,`default`).option(`-r, --reason <text>`,`Optional stop reason to record`).action(async(e,t)=>{try{let i=n(),o=await i.requestStop(t.workspace,e,t.reason);a(`${JSON.stringify({reason:o.reason,requestedAt:o.requestedAt,runKey:e,workspace:i.resolveWorkspace(t.workspace)},null,2)}\n`),r(0)}catch(n){let a=new g(`Error requesting workflow stop.`,`STOP_WORKFLOW_FAILED`,{command:$,runKey:e,workspace:t.workspace},{cause:n});i(`${a.message} [${a.code}]`,a),r(1)}})}const ht=mt();async function gt(){let e=new m;e.name(`workflow-mcp`).description(`MCP server for running GitHub Actions workflows locally`).version(re),e.addCommand($e),e.addCommand(tt),e.addCommand(he),e.addCommand(pe),e.addCommand(Me),e.addCommand(Xe),e.addCommand(Ze),e.addCommand(st),e.addCommand(dt),e.addCommand(pt),e.addCommand(ht),await e.parseAsync(process.argv)}gt().catch(e=>{console.error(`[CLI_STARTUP_ERROR] workflow-mcp startup failed:`,e),process.exit(1)});export{};
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./stdio-Dd8bf1mi.cjs`);exports.StdioTransportHandler=e.t,exports.createServer=e.n;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./stdio-BqdxXSzu.cjs`);exports.StdioTransportHandler=e.t,exports.createServer=e.n;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{n as e,t}from"./stdio-LgqV_E3T.mjs";export{t as StdioTransportHandler,e as createServer};
1
+ import{n as e,t}from"./stdio-gREqGN_j.mjs";export{t as StdioTransportHandler,e as createServer};
@@ -0,0 +1,52 @@
1
+ let e=require(`@modelcontextprotocol/sdk/server/index.js`),t=require(`@modelcontextprotocol/sdk/types.js`),n=require(`zod`),r=require(`node:child_process`),i=require(`node:util`),a=require(`node:fs/promises`),o=require(`node:os`),s=require(`node:path`),c=require(`node:fs`),l=require(`@agimon-ai/foundation-port-registry`),u=require(`@agimon-ai/foundation-process-registry`),d=require(`node:crypto`),f=require(`node:readline`),p=require(`js-yaml`),m=require(`@modelcontextprotocol/sdk/server/stdio.js`);var h=class extends Error{code=`WORKFLOW_TOOL_NOT_FOUND`;constructor(e,t){super(`Unknown tool "${e}". Available tools: ${t.join(`, `)}`),this.toolName=e,this.availableTools=t,this.name=`WorkflowToolNotFoundError`}},g=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`WorkflowToolError`}},_=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`CronError`}};const v=`# workflow-mcp:cron:`,y=`crontab`,b=`claude`,x=`codex`,S=`--cwd`,C=`CRON_WRITE_FAILED`,ee=n.z.enum([b,x]),te=n.z.object({name:n.z.string().min(1),cwd:n.z.string().min(1),cli:ee,schedule:n.z.string().min(1),prompt:n.z.string().optional(),promptFile:n.z.string().optional(),createdAt:n.z.string().min(1)}),w=n.z.object({name:n.z.string().min(1),cwd:n.z.string().min(1),cli:ee.optional(),prompt:n.z.string().optional(),promptFile:n.z.string().optional(),schedule:n.z.string().optional(),intervalMinutes:n.z.number().positive().optional()});function T(e){return`'${e.replace(/'/g,`'\\''`)}'`}function ne(e,t,n,r){let i=n?T(n):r?`"$(cat ${T(r)})"`:void 0;if(e===`codex`){let e=[x,`--approval-mode`,`full-auto`];return i&&e.push(`-q`,i),e.push(S,T(t)),e.join(` `)}let a=[b,`--dangerously-skip-permissions`];return i&&a.push(`-p`,i),a.push(S,T(t)),a.join(` `)}function E(e){if(e<=0)throw Error(`Interval must be a positive number of minutes`);if(e<60)return`*/${e} * * * *`;let t=Math.floor(e/60);return t<24?`0 */${t} * * *`:`0 0 */${Math.floor(t/24)} * *`}const re=(0,i.promisify)(r.execFile);var D=class{logger;constructor(e){this.logger=e??{info:e=>process.stdout.write(`${e}\n`),error:e=>console.error(e),warn:e=>console.error(e)}}async readCrontab(){try{let{stdout:e}=await re(y,[`-l`]);return e}catch(e){let t=e.code;if(t===`ENOENT`||(e.stderr??``).includes(`no crontab for`))return``;throw this.logger.error(`[CronService.readCrontab] failed exitCode=${t}`),new _(`Failed to read current crontab.`,`CRON_READ_FAILED`,{exitCode:t},{cause:e})}}async writeCrontab(e){let t=``,n=(0,r.execFile)(y,[`-`]);n.stderr?.on(`data`,e=>{t+=e}),n.stdin?.write(e),n.stdin?.end(),await new Promise((r,i)=>{n.on(`close`,n=>{n===0?r():(this.logger.error(`[CronService.writeCrontab] failed exitCode=${n} stderr=${t}`),i(new _(`crontab rejected input (exit ${n}).`,C,{exitCode:n,stderr:t,contentLength:e.length})))}),n.on(`error`,e=>{this.logger.error(`[CronService.writeCrontab] spawn failed: ${e.message}`),i(new _(`Failed to spawn crontab process.`,C,{},{cause:e}))})})}async schedule(e){let t=e.cli??`claude`;if(!e.schedule&&!e.intervalMinutes)throw new _(`Either schedule (cron expression) or intervalMinutes must be provided.`,`CRON_INVALID_INPUT`,{name:e.name});let n=e.schedule??E(e.intervalMinutes),r=ne(t,e.cwd,e.prompt,e.promptFile),i=new Date().toISOString(),a=te.parse({name:e.name,cwd:e.cwd,cli:t,schedule:n,prompt:e.prompt,promptFile:e.promptFile,createdAt:i}),o=await this.readCrontab(),s=this.removeCronBlock(o,e.name),c=`${`${v}${e.name}`}\n${`# workflow-mcp:meta:${JSON.stringify(a)}`}\n${`${n} ${r}`}`,l=s.trimEnd()?`${s.trimEnd()}\n${c}\n`:`${c}\n`;return await this.writeCrontab(l),this.logger.info(`Scheduled cron "${e.name}" [${n}]`),a}async list(){let e=await this.readCrontab(),t=[],n=e.split(`
2
+ `);for(let e=0;e<n.length;e++){let r=n[e];if(r.startsWith(`# workflow-mcp:meta:`))try{let e=r.slice(20),n=te.parse(JSON.parse(e));t.push(n)}catch(t){this.logger.warn(`[CronService.list] skipping malformed metadata at line ${e+1}: ${t.message}`)}}return t}async remove(e){let t=await this.readCrontab(),n=this.removeCronBlock(t,e);return n===t?!1:(await this.writeCrontab(n),this.logger.info(`Removed cron "${e}"`),!0)}removeCronBlock(e,t){let n=e.split(`
3
+ `),r=[],i=`${v}${t}`,a=!1;for(let e of n){if(e===i){a=!0;continue}if(a){if(e.startsWith(`# workflow-mcp:meta:`))continue;a=!1;continue}r.push(e)}return r.join(`
4
+ `)}},O=class e{static TOOL_NAME=`list-crons`;constructor(e=new D){this.service=e}getInputSchema(){return n.z.object({})}getDefinition(){return{name:e.TOOL_NAME,description:`List all cron jobs scheduled via workflow-mcp, showing name, schedule, CLI, cwd, and prompt.`,inputSchema:n.z.toJSONSchema(n.z.object({}))}}async execute(){try{let e=await this.service.list();return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}catch(t){let n=new g(`Failed to list cron jobs.`,`LIST_CRONS_TOOL_FAILED`,{tool:e.TOOL_NAME},{cause:t});return console.error(`[${e.TOOL_NAME}] ${n.message}`,n.context),{content:[{type:`text`,text:JSON.stringify({code:n.code,context:n.context,message:n.message},null,2)}],isError:!0}}}},k=class extends Error{code=`INVALID_WORKFLOW_RUN_RECORD`;constructor(e,t,n){super(`Invalid workflow run record at "${e}": ${t}`,n),this.recordPath=e,this.name=`InvalidWorkflowRunRecordError`}},ie=class extends Error{code=`WORKFLOW_RUN_CONFLICT`;constructor(e,t){super(`Workflow "${t}" is already running in workspace "${e}"`),this.workspace=e,this.runKey=t,this.name=`WorkflowRunConflictError`}};const ae=n.z.record(n.z.string(),n.z.coerce.string()),oe=n.z.object({description:n.z.string().optional(),required:n.z.boolean().optional(),default:n.z.string().optional(),type:n.z.string().optional(),options:n.z.array(n.z.string()).optional()}),se=n.z.record(n.z.string(),n.z.string()),ce=n.z.record(n.z.string(),n.z.string()),le=n.z.union([n.z.string(),ce]),ue=n.z.object({"fail-fast":n.z.boolean().optional(),matrix:n.z.looseObject({include:n.z.array(se).optional()}).optional()}),de=n.z.object({name:n.z.string().optional(),uses:n.z.string().optional(),run:le.optional(),interactiveRun:le.optional(),"timeout-minutes":n.z.number().optional(),env:ae.optional(),with:n.z.record(n.z.string(),n.z.string()).optional(),"working-directory":n.z.string().optional(),if:n.z.string().optional(),"continue-on-error":n.z.boolean().optional(),"timeout-retries":n.z.number().optional(),id:n.z.string().optional()}),fe=n.z.object({name:n.z.string(),run:n.z.string(),env:ae.optional(),"working-directory":n.z.string().optional(),"ready-check":n.z.union([n.z.string(),n.z.boolean()]).optional(),"ready-timeout":n.z.number().optional(),host:n.z.string().optional(),port:n.z.number().int().min(1).max(65535).optional(),"port-range":n.z.object({min:n.z.number().int().min(1).max(65535),max:n.z.number().int().min(1).max(65535)}).optional(),"service-type":n.z.enum([`tool`,`service`]).optional()}),pe=n.z.object({services:n.z.array(fe).optional(),steps:n.z.array(de).optional()}),me=n.z.object({steps:n.z.array(de).optional()}),he=n.z.object({"runs-on":n.z.string().optional(),needs:n.z.union([n.z.string(),n.z.array(n.z.string())]).optional(),extends:n.z.union([n.z.string(),n.z.array(n.z.string())]).optional(),description:n.z.string().optional(),strategy:ue.optional(),steps:n.z.array(de),preJob:me.optional(),postJob:me.optional(),env:ae.optional(),if:n.z.string().optional(),"system-prompt":n.z.string().optional(),context:n.z.string().optional()}),ge=n.z.object({steps:n.z.array(de)}),_e=n.z.object({steps:n.z.array(de)}),ve=n.z.object({beforeCreate:_e.optional(),afterCreate:_e.optional(),beforeCompleted:_e.optional(),afterCompleted:_e.optional()}),ye=n.z.object({STOP:n.z.string().optional()}),be=n.z.object({name:n.z.string().optional(),workspace:n.z.string().optional(),imports:n.z.array(n.z.string()).optional(),"system-prompt":n.z.string().optional(),"max-workflows":n.z.number().int().positive().optional(),"launch-command":n.z.string().optional(),keybindings:ye.optional(),on:n.z.looseObject({workflow_dispatch:n.z.object({inputs:n.z.record(n.z.string(),oe).optional()}).nullable().optional(),agent_decision:ge.optional()}).optional(),env:ae.optional(),pre:pe.optional(),post:pe.optional(),worktree:ve.optional(),jobs:n.z.record(n.z.string(),he)}),xe=n.z.enum([`running`,`completed`,`error`]),Se=n.z.enum([`success`,`skipped`,`failed`,`interrupted`]),Ce=n.z.object({displayName:n.z.string(),dryRun:n.z.boolean(),errorMessage:n.z.string().optional(),exitCode:n.z.number().optional(),finishedAt:n.z.string().optional(),outcome:Se.optional(),pid:n.z.number().int().positive().optional(),runKey:n.z.string(),stale:n.z.boolean().optional(),staleReason:n.z.string().optional(),stage:xe,startedAt:n.z.string(),workflowPath:n.z.string(),workspace:n.z.string()});n.z.object({changelogPath:n.z.string(),contextPath:n.z.string(),displayName:n.z.string(),recordPath:n.z.string(),runDir:n.z.string(),runKey:n.z.string(),stopRequestPath:n.z.string(),workspace:n.z.string()});const we=`default`,A=`running`,Te=`completed`,j=`error`,Ee=`workspaces`,M=`run.json`,De=`stop-request.json`,Oe=`workflow-run`,ke=`Workflow process is no longer running`;var Ae=class{constructor(e=(0,s.resolve)((0,o.homedir)(),`.workflow-mcp`)){this.homeDir=e}resolveWorkspace(e,t){return this.slugifySegment(e||t||we,we)}async createRun(e){let t=this.resolveWorkspace(e.workspace,e.workflowWorkspace),n=this.slugifySegment(e.displayName,Oe);await this.ensureWorkspaceStructure(t);let r=await this.findReusableRunStage(t,n);if(r===A)throw new ie(t,n);let i=this.getRunDirectory(t,A,n);r?(await(0,a.rm)(i,{recursive:!0,force:!0}),await(0,a.rename)(this.getRunDirectory(t,r,n),i),await this.removeFileIfExists((0,s.resolve)(i,`fix.md`)),await this.removeFileIfExists((0,s.resolve)(i,De))):await(0,a.mkdir)(i,{recursive:!0});let o={displayName:e.displayName,dryRun:e.dryRun,runKey:n,stage:A,startedAt:new Date().toISOString(),pid:e.pid??process.pid,workflowPath:e.workflowPath,workspace:t},c=(0,s.resolve)(i,M);return await this.writeRunRecord(c,o),{changelogPath:(0,s.resolve)(i,`changelog.md`),contextPath:(0,s.resolve)(i,`context.md`),displayName:e.displayName,recordPath:c,runDir:i,runKey:n,stopRequestPath:(0,s.resolve)(i,De),workspace:t}}async finalizeRun(e,t){await this.ensureWorkspaceStructure(e.workspace);let n=this.getRunDirectory(e.workspace,t.stage,e.runKey),r=await this.readRunRecord(e.recordPath),i={...r,errorMessage:t.errorMessage,exitCode:t.exitCode,finishedAt:new Date().toISOString(),outcome:t.outcome,pid:r.pid,stage:t.stage};e.runDir!==n&&(await(0,a.rm)(n,{recursive:!0,force:!0}),await(0,a.rename)(e.runDir,n));let o=(0,s.resolve)(n,M);return await this.writeRunRecord(o,i),{...e,recordPath:o,runDir:n,stopRequestPath:(0,s.resolve)(n,De)}}async requestStop(e,t,n){let r=this.resolveWorkspace(e),i=this.slugifySegment(t,Oe),o=this.getRunDirectory(r,A,i),c=(0,s.resolve)(o,M);if(!await this.pathExists(c))throw Error(`Running workflow not found: ${r}/${t}`);let l=await this.inspectRunningRecord(r,i);if(l.stale)throw Error(l.staleReason??`Running workflow is stale: ${r}/${t}`);let u={reason:n?.trim()?n.trim():void 0,requestedAt:new Date().toISOString()};return await(0,a.writeFile)((0,s.resolve)(o,De),`${JSON.stringify(u,null,2)}\n`,`utf-8`),u}async hasStopRequest(e){return await this.pathExists(e.stopRequestPath)}async readRunRecord(e){try{return this.validateRunRecord(JSON.parse(await(0,a.readFile)(e,`utf-8`)),e)}catch(t){throw t instanceof k?t:new k(e,t instanceof Error?t.message:`Unknown parse failure`,{cause:t})}}async listRuns(e){let t=e?[this.resolveWorkspace(e)]:await this.listWorkspaceNames();return(await Promise.all(t.map(async e=>this.listWorkspaceRuns(e)))).flat().sort((e,t)=>this.compareRunRecordsByLatest(e,t))}async listRunsPage(e={}){let t=e.page??1,n=e.pageSize??20,r=await this.listRuns(e.workspace),i=r.length,a=Math.ceil(i/n),o=(t-1)*n,s=r.slice(o,o+n);return{hasNextPage:t<a,hasPreviousPage:t>1&&i>0,items:s,page:t,pageSize:n,total:i,totalPages:a}}async countRunningWorkflows(e){let t=this.getRunStageDirectory(e,A);if(!await this.pathExists(t))return 0;let n=await(0,a.readdir)(t,{withFileTypes:!0}),r=0;for(let e of n){if(!e.isDirectory())continue;let n=(0,s.resolve)(t,e.name,M);if(await this.pathExists(n))try{let e=await this.readRunRecord(n);e.pid&&this.isProcessAlive(e.pid)&&r++}catch{}}return r}async ensureWorkspaceStructure(e){await(0,a.mkdir)(this.getRunStageDirectory(e,A),{recursive:!0}),await(0,a.mkdir)(this.getRunStageDirectory(e,Te),{recursive:!0}),await(0,a.mkdir)(this.getRunStageDirectory(e,j),{recursive:!0})}async findRunStage(e,t){for(let n of[A,Te,j])if(await this.pathExists(this.getRunDirectory(e,n,t)))return n;return null}async listWorkspaceRuns(e){let t=new Map;for(let n of[A,Te,j]){let r=this.getRunStageDirectory(e,n);if(!await this.pathExists(r))continue;let i=await(0,a.readdir)(r,{withFileTypes:!0});for(let a of i){if(!a.isDirectory())continue;let i=(0,s.resolve)(r,a.name,M);if(!await this.pathExists(i))continue;let o=await this.readListableRunRecord(e,n,a.name,i);o&&t.set(o.runKey,o)}}return[...t.values()]}async findReusableRunStage(e,t){let n=await this.findRunStage(e,t);return n===A?(await this.reconcileStaleRunningRecord(e,t),this.findRunStage(e,t)):n}async inspectRunningRecord(e,t){let n=(0,s.resolve)(this.getRunDirectory(e,A,t),M),r=await this.readRunRecord(n);return!r.pid||this.isProcessAlive(r.pid)?r:{...r,stale:!0,staleReason:`${ke}: pid ${r.pid}`}}async readListableRunRecord(e,t,n,r){try{return t===A?await this.inspectRunningRecord(e,n):await this.readRunRecord(r)}catch(e){if(e instanceof k)return null;throw e}}async reconcileStaleRunningRecord(e,t){let n=this.getRunDirectory(e,A,t),r=(0,s.resolve)(n,M),i=await this.readRunRecord(r);if(!i.pid||this.isProcessAlive(i.pid))return i;let o=this.getRunDirectory(e,j,t),c={...i,errorMessage:`${ke}: pid ${i.pid}`,exitCode:130,finishedAt:new Date().toISOString(),outcome:`interrupted`,stage:j};return await(0,a.rm)(o,{recursive:!0,force:!0}),await(0,a.rename)(n,o),await this.writeRunRecord((0,s.resolve)(o,M),c),c}async listWorkspaceNames(){let e=(0,s.resolve)(this.homeDir,Ee);return await this.pathExists(e)?(await(0,a.readdir)(e,{withFileTypes:!0})).filter(e=>e.isDirectory()).map(e=>e.name).sort((e,t)=>e.localeCompare(t)):[]}compareRunRecordsByLatest(e,t){let n=e.finishedAt??e.startedAt,r=(t.finishedAt??t.startedAt).localeCompare(n);if(r!==0)return r;let i=e.workspace.localeCompare(t.workspace);return i===0?e.runKey.localeCompare(t.runKey):i}async writeRunRecord(e,t){await(0,a.writeFile)(e,`${JSON.stringify(t,null,2)}\n`,`utf-8`)}validateRunRecord(e,t){let n=Ce.safeParse(e);if(!n.success)throw new k(t,n.error.message);return n.data}getRunStageDirectory(e,t){return(0,s.resolve)(this.homeDir,Ee,e,t)}getRunDirectory(e,t,n){return(0,s.resolve)(this.getRunStageDirectory(e,t),n)}async pathExists(e){try{return await(0,a.access)(e),!0}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async removeFileIfExists(e){try{await(0,a.unlink)(e)}catch(e){if(e.code!==`ENOENT`)throw e}}slugifySegment(e,t){return e.trim().toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``)||t}isProcessAlive(e){try{return process.kill(e,0),!0}catch(e){let t=e.code;if(t===`ESRCH`)return!1;if(t===`EPERM`)return!0;throw e}}};const je=n.z.object({page:n.z.number().int().min(1).optional().describe(`Page number to return. Defaults to 1.`),pageSize:n.z.number().int().min(1).max(100).optional().describe(`Number of workflow runs per page. Defaults to 20 and is capped at 100.`),workspace:n.z.string().optional().describe(`Optional workspace filter. When omitted, runs from all workspaces are returned.`)});var Me=class e{static TOOL_NAME=`list_workflow_statuses`;constructor(e=new Ae){this.registry=e}getInputSchema(){return je}getDefinition(){return{name:e.TOOL_NAME,description:`List tracked workflow runs from the local workflow registry, including their workspace, stage, outcome, and timestamps.`,inputSchema:n.z.toJSONSchema(je)}}async execute(e={}){try{let t=je.parse(e),n=await this.registry.listRunsPage({page:t.page??1,pageSize:t.pageSize??20,workspace:t.workspace});return{content:[{type:`text`,text:JSON.stringify(n,null,2)}]}}catch(t){let n=new g(`Failed to list workflow statuses.`,`LIST_WORKFLOW_STATUSES_TOOL_FAILED`,{workspace:e.workspace??`all`},{cause:t});return{content:[{type:`text`,text:JSON.stringify({code:n.code,context:n.context,message:n.message},null,2)}],isError:!0}}}},Ne=class extends Error{code=`WORKFLOW_CAPACITY_EXCEEDED`;constructor(e,t,n){super(`Workspace "${e}" is at capacity: ${t}/${n} workflows running.\n → Check running workflows: list-workflow-statuses --workspace ${e}\n → Wait for a running workflow to complete, or cancel one before dispatching again.`),this.workspace=e,this.running=t,this.max=n,this.name=`WorkflowCapacityError`}},N=class extends Error{code=`WORKFLOW_INTERRUPTED`;context;constructor(e,t={}){let n=t.context?` during ${t.context.phase}`:``;super(`Workflow interrupted by ${e}${n}`,{cause:t.cause??t.context}),this.signal=e,this.name=`WorkflowInterruptedError`,this.context=t.context}};const P=`\x1B[0m`,Pe=`\x1B[1m`,Fe=`\x1B[2m`,Ie=`\x1B[31m`,Le=`\x1B[34m`,Re=[`pnpm-workspace.yaml`,`nx.json`,`.git`];function ze(e){let t=(0,s.resolve)(e);for(;;){for(let e of Re)if((0,c.existsSync)((0,s.join)(t,e)))return t;let n=(0,s.dirname)(t);if(n===t)return e;t=n}}var Be=class{runningServices=[];processRegistry=new u.ProcessRegistryService(process.env.PROCESS_REGISTRY_PATH);portRegistry=new l.PortRegistryService(process.env.PORT_REGISTRY_PATH);constructor(e){this.logger=e}resolveWorkingDirectory(e,t){return e[`working-directory`]?(0,s.resolve)(t,e[`working-directory`]):t}getRunningServices(){return this.runningServices}async startService(e,t,n,i){let a=e.run;if(i)return this.logger.info(` ${Le}dry ${P} ${e.name}`),this.logger.info(` ${Fe}$ ${a}${P}`),null;this.logger.info(` ${Le}start${P} ${e.name}`),this.logger.info(` ${Fe}$ ${a}${P}`);let o={...process.env,...t};if(e.env)for(let[t,n]of Object.entries(e.env))o[t]=String(n);let s=this.resolveWorkingDirectory(e,n),c=ze(s),l=o.NODE_ENV??process.env.NODE_ENV??`development`,u=e.host??`127.0.0.1`,d;if(e.port!==void 0||e[`port-range`]){let t=await this.portRegistry.reservePort({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,preferredPort:e.port,portRange:e[`port-range`],host:u,force:!0});if(!t.success||t.port===void 0)throw Error(t.error??`Failed to reserve port for background service ${e.name}`);d=t.port,o.PORT??=String(d),o.SERVICE_PORT??=String(d),o.HOST??=u,o.SERVICE_HOST??=u}let f=(0,r.spawn)(a,[],{stdio:[`ignore`,`pipe`,`pipe`],cwd:s,env:{...o,FORCE_COLOR:`1`},shell:`/bin/zsh`,detached:!0});if(f.pid===void 0)throw d!==void 0&&await this.portRegistry.releasePort({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,force:!0}),Error(`Failed to spawn background service ${e.name}`);let p=`${Le}[${e.name}]${P}`;f.stdout?.on(`data`,e=>{for(let t of e.toString().split(`
5
+ `))t.trim()&&process.stdout.write(`${p} ${t}\n`)}),f.stderr?.on(`data`,e=>{for(let t of e.toString().split(`
6
+ `))t.trim()&&process.stderr.write(`${p} ${t}\n`)}),f.on(`error`,e=>{this.logger.error(`${p} Process error: ${e.message}`)});let m=async()=>{d!==void 0&&await this.portRegistry.releasePort({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,force:!0})};if(f.pid!==void 0){let t=await this.processRegistry.registerProcess({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,pid:f.pid,port:d,host:d===void 0?void 0:u,command:a,args:[],metadata:{workingDirectory:s,readyCheck:e[`ready-check`]},force:!0});if(!t.success){try{process.kill(-f.pid,`SIGTERM`)}catch{f.kill(`SIGTERM`)}throw d!==void 0&&await this.portRegistry.releasePort({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,force:!0}),Error(t.error??`Failed to register process for background service ${e.name}`)}m=async()=>{let t=await this.processRegistry.releaseProcess({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,pid:f.pid,kill:!0,releasePort:!0,force:!0});if(!t.success&&!t.error?.includes(`No matching process entry`))throw Error(t.error??`Failed to release ${e.name}`)}}let h={name:e.name,process:f,env:o,release:m};return this.runningServices.push(h),h}async waitForServiceReady(e,t,n,i){if(!e[`ready-check`]||typeof e[`ready-check`]!=`string`)return!0;let a=(e[`ready-timeout`]||30)*1e3,o=Date.now(),s=e[`ready-check`],c=this.resolveWorkingDirectory(e,t),l=this.runningServices.find(t=>t.name===e.name)?.env??{...process.env,...n};for(this.logger.info(` ${Fe}wait${P} Waiting for ${e.name} to be ready...`);Date.now()-o<a;){if(i?.())return!1;try{return(0,r.execSync)(s,{stdio:`ignore`,cwd:c,env:l,shell:`/bin/zsh`,timeout:5e3}),this.logger.info(` ready${P} ${e.name}`),!0}catch{if(i?.())return!1}await new Promise(e=>{setTimeout(e,1e3)})}return i?.()||this.logger.error(` ${Ie}timeout${P} ${e.name} not ready after ${e[`ready-timeout`]||30}s`),!1}async stopAll(){if(this.runningServices.length!==0){this.logger.info(`\n ${Le}${Pe}pre${P} ${Pe}Stopping services${P}`),this.logger.info(` ${`─`.repeat(50)}`);for(let e of this.runningServices){if(!e.process.killed&&e.process.pid&&this.logger.info(` ${Fe}stop${P} ${e.name} (pid ${e.process.pid})`),e.release)try{await e.release();continue}catch(t){this.logger.warn(` ${Ie}warn${P} Failed registry cleanup for ${e.name}: ${t instanceof Error?t.message:String(t)}`)}if(!e.process.killed&&e.process.pid)try{process.kill(-e.process.pid,`SIGTERM`)}catch{e.process.kill(`SIGTERM`)}}this.runningServices=[]}}};const F=`\x1B[0m`,Ve=`\x1B[1m`,I=`\x1B[2m`,He=`\x1B[33m`,Ue=`WORKFLOW_JOB_ID`,We=`WORKFLOW_JOB_NAME`,Ge=`PROCESS_REGISTRY_TAG`;var Ke=class{constructor(e,t,n){this.parser=e,this.stepRunner=t,this.logger=n}async runJob(e,t,n,r){let i=this.parser.expandMatrix(t),a=r.maxRetries;for(let o of i){let i=(0,d.randomUUID)(),l=Object.keys(o).length>0?` ${I}(${Object.entries(o).map(([e,t])=>`${e}=${t}`).join(`, `)})${F}`:``;this.logger.info(`\n ${Ve}job${F} ${Ve}${e}${F}${l}`),this.logger.info(` ${`─`.repeat(50)}`);let u={...n.workflowEnv,[Ue]:i,[We]:e,[Ge]:i};if(t.env)for(let[e,r]of Object.entries(t.env))u[e]=this.parser.interpolate(String(r),{inputs:n.inputs,matrix:o,secrets:n.secrets,env:u});u[Ue]=i,u[We]=e,u[Ge]=i,this.logger.info(` ${I}jobid${F} ${i}`);let f=[];if(u.WORKFLOW_RUN_DIR){let t=n.jobOrder.map(e=>{let t=n.allJobs[e]?.description;return t?` - ${e}: ${t}`:` - ${e}`}).join(`
7
+ `);f.push(`You are currently running job "${e}" in the following workflow pipeline:\n${t}\n\nIf you find bugs, missing features, or issues that need fixes from a previous job, write to ${u.WORKFLOW_RUN_DIR}/fix.md with a YAML frontmatter restart-from field specifying which job should handle the fix, followed by a detailed description. Example:\n---\nrestart-from: development\n---\nDescription of what needs to be fixed...\n\nThe workflow runner will restart from the specified job. Only create fix.md for issues that require code changes — fix minor issues yourself.`)}if(n.workflowSystemPrompt&&f.push(this.parser.interpolate(n.workflowSystemPrompt,{inputs:n.inputs,matrix:o,secrets:n.secrets,env:u})),t[`system-prompt`]&&f.push(this.parser.interpolate(t[`system-prompt`],{inputs:n.inputs,matrix:o,secrets:n.secrets,env:u})),f.length>0){let e=f.join(`
8
+
9
+ `);u.JOB_SYSTEM_PROMPT=e,this.logger.info(` ${I}prompt${F} ${e.split(`
10
+ `)[0].slice(0,80)}${e.includes(`
11
+ `)?`...`:``}`)}let p=t.context?this.parser.interpolate(t.context,{inputs:n.inputs,matrix:o,secrets:n.secrets,env:u}):u.CONTEXT_FILE;if(p){let e=(0,s.resolve)(r.workflowDir,p);(0,c.existsSync)(e)?(u.WORKFLOW_CONTEXT=e,u.WORKFLOW_CONTEXT_FILE=e,this.logger.info(` ${I}ctx ${F} ${p}`)):r.dryRun||this.logger.warn(` ${He}warn${F} context file not found: ${p}`)}if(u.CHANGELOG_FILE){let e=(0,s.resolve)(r.workflowDir,u.CHANGELOG_FILE),t=(0,s.dirname)(e);(0,c.existsSync)(t)||(0,c.mkdirSync)(t,{recursive:!0}),(0,c.existsSync)(e)||(0,c.writeFileSync)(e,`# Changelog
12
+ `,`utf-8`),u.WORKFLOW_CHANGELOG=e,this.logger.info(` ${I}log ${F} changelog at ${u.CHANGELOG_FILE}`)}let m={inputs:n.inputs,matrix:o,secrets:n.secrets,env:u},h=!1,g=t.preJob?.steps??[],_=t.steps??[],v=t.postJob?.steps??[];for(let t=1;t<=a;t++){t>1&&(this.logger.info(`\n ${He}retry${F} ${Ve}${e}${F}${l} (attempt ${t}/${a})`),this.logger.info(` ${`─`.repeat(50)}`));let n=!0;if(g.length>0&&(n=await this.runStepSequence(g,m,r)),n&&=await this.runStepSequence(_,m,r),v.length>0){let e=await this.runStepSequence(v,m,r);n&&=e}if(n){h=!0;break}t<a&&this.logger.warn(` ${He}Job "${e}" failed, retrying...${F}`)}if(h)this.logger.info(` Job "${e}" completed${F}${l}`);else if(this.logger.error(`\n Job "${e}" failed after ${a} attempts${F}`),t.strategy?.[`fail-fast`]!==!1)return!1}return!0}async runStepSequence(e,t,n){for(let r of e)if(!await this.stepRunner.runStep(r,t,n))return!1;return!0}},qe=class extends Error{code=`WORKFLOW_STEP_SPAWN_FAILED`;constructor(e,t={}){super(`Failed to start workflow step "${e.stepName}"`,{cause:t.cause??e}),this.context=e,this.name=`WorkflowStepSpawnError`}},Je=class extends Error{code=`WORKFLOW_STEP_TIMEOUT_CONFIG_INVALID`;constructor(e,t={}){super(`Invalid ${e.configKey} for workflow step "${e.stepName}"`,{cause:t.cause??e}),this.context=e,this.name=`WorkflowStepTimeoutConfigError`}};const Ye=`/backend-api`;var Xe=class{codexHome;fetchFn;now;readTextFile;constructor(e={}){this.codexHome=e.codexHome??(0,s.join)((0,o.homedir)(),`.codex`),this.fetchFn=e.fetchFn??fetch,this.now=e.now??(()=>Date.now()),this.readTextFile=e.readTextFile??(e=>(0,a.readFile)(e,`utf8`))}isCodexCommand(e,t){let n=e.trim();return n===`codex`||n.startsWith(`codex `)}async getQuotaStatus(){let e=await this.readAuthFile(),t=e?.tokens?.access_token?.trim(),n=e?.tokens?.account_id?.trim();if(!t||!n)return null;let r=await this.readChatgptBaseUrl(),i=await this.fetchFn(this.buildUsageUrl(r),{headers:{Authorization:`Bearer ${t}`,"ChatGPT-Account-Id":n,"User-Agent":`codex-cli`}});if(!i.ok)throw Error(`codex quota request failed with HTTP ${i.status}`);let a=await i.json();return{blockingLimit:this.findBlockingLimit(a),planType:a.plan_type??null}}async readAuthFile(){let e=await this.readOptionalTextFile((0,s.join)(this.codexHome,`auth.json`));return e?JSON.parse(e):null}async readChatgptBaseUrl(){let e=(await this.readOptionalTextFile((0,s.join)(this.codexHome,`config.toml`)))?.match(/^\s*chatgpt_base_url\s*=\s*"([^"]+)"/m);return this.normalizeBaseUrl(e?.[1]??`https://chatgpt.com/backend-api/`)}buildUsageUrl(e){return e.includes(Ye)?`${e}/wham/usage`:`${e}/api/codex/usage`}normalizeBaseUrl(e){let t=e.trim().replace(/\/+$/,``);return(t.startsWith(`https://chatgpt.com`)||t.startsWith(`https://chat.openai.com`))&&!t.includes(Ye)&&(t=`${t}${Ye}`),t}async readOptionalTextFile(e){try{return await this.readTextFile(e)}catch(e){if(e.code===`ENOENT`)return null;throw e}}findBlockingLimit(e){let t=[];this.collectBlockingLimit(t,`codex`,`codex`,e.rate_limit);for(let n of e.additional_rate_limits??[])this.collectBlockingLimit(t,n.metered_feature??n.limit_name??`codex`,n.limit_name??n.metered_feature??`codex`,n.rate_limit);return t.sort((e,t)=>e.resetAt-t.resetAt||t.usedPercent-e.usedPercent),t[0]??null}collectBlockingLimit(e,t,n,r){if(!r)return;let i=[{payload:r.primary_window,window:`primary`},{payload:r.secondary_window,window:`secondary`}];for(let{payload:a,window:o}of i){let i=a?.reset_at,s=a?.used_percent??0;typeof i==`number`&&(s>=100||r.limit_reached===!0&&i*1e3>this.now()||r.allowed===!1&&i*1e3>this.now())&&e.push({limitId:t,limitName:n,resetAfterSeconds:a?.reset_after_seconds??null,resetAt:i,usedPercent:s,window:o,windowSeconds:a?.limit_window_seconds??null})}}};const L=`\x1B[0m`,R=`\x1B[2m`,Ze=`\x1B[36m`,Qe=`\x1B[32m`,z=`\x1B[33m`,B=`\x1B[31m`,$e=`SIGINT`,V=`SIGTERM`,et=`unnamed`,tt=`/bin/zsh`,nt=`FORCE_COLOR`,H=`inherit`,rt=`pipe`,it=` `,at=`
13
+ ${it}\$ `,ot=`skip`,U=`warn`,W=`error`,G=`fail`,K=`interrupted`,st=`already stopping`,ct=`failed to signal`,q=`(continue-on-error)`,lt=`ESRCH`,ut=process.platform!==`win32`,dt=`interactiveRun`,ft=`timeout-minutes`,pt=`timeout-retries`,mt=`retry`,ht=`SIGKILL`,gt=1e3,_t=6e4,vt=Math.floor(2147483647/_t),yt=`WORKFLOW_STEP_DISPLAY`,bt=`workflow-mcp`,xt={status:`status_completed`},St=new Set([`ENOENT`,`EBUSY`,`EAGAIN`,`EPERM`]),Ct=1e3,wt=`done`,Tt=`stop`,Et=1e3,Dt=`stop_requested`,Ot=n.z.number().positive().finite().max(vt),kt=n.z.number().int().min(0).max(10).default(2);var At=class{activeStep=null;activeQuotaWait=null;constructor(e,t,n=new Xe,r=c.watch){this.parser=e,this.logger=t,this.quotaService=n,this.watchStatusFile=r}stopActiveStep(e){if(this.activeQuotaWait){if(this.activeQuotaWait.controller.signal.aborted){this.logger.info(` ${R}${ot}${L} ${this.activeQuotaWait.stepName} ${st}`);return}this.activeQuotaWait.signal=e,this.activeQuotaWait.controller.abort(),this.logger.info(` ${z}${K}${L} cancelling quota wait for ${this.activeQuotaWait.stepName}`);return}if(!this.activeStep){this.logger.info(` ${R}${ot}${L} no active step to stop`);return}if(this.activeStep.process.killed){this.logger.info(` ${R}${ot}${L} ${this.activeStep.stepName} ${st}`);return}this.logger.info(` ${z}${K}${L} sending ${e} to ${this.activeStep.stepName}`),this.killActiveStep(e)||this.logger.warn(` ${z}${U}${L} ${ct} ${this.activeStep.stepName}`)}async runStep(e,t,n){let r=this.resolveRunner(n),i=this.resolveStepCommand(e,t,r),a=e.name?this.parser.interpolate(e.name,t):i?.command.slice(0,60)||e.uses||et;if(e.uses){let n=this.parser.interpolate(e.uses,t);return this.parser.isActionSkipped(n)?(this.logger.info(` ${R}${ot}${L} ${n}`),!0):(this.logger.warn(` ${z}${U}${L} Unsupported action: ${n} (skipped)`),!0)}if(!i)return!0;if(i.missingRunner)return this.logger.error(` ${B}${W}${L} runner "${i.missingRunner.runner}" not found for step "${a}" (available runner keys: ${i.missingRunner.availableKeys.join(`, `)})`),e[`continue-on-error`]||n.continueOnError?(this.logger.warn(` ${z}${G}${L} ${a} ${q}\n`),!0):(this.logger.error(` ${B}${G}${L} ${a}\n`),!1);let{command:o,interactive:l}=i,u=this.quotaService.isCodexCommand(o,i.agentKey),d=e[`continue-on-error`]||n.continueOnError,f;try{f=this.resolveTimeoutMs(e,a)}catch(e){if(!(e instanceof Je))throw e;return this.logger.error(` ${B}${W}${L} invalid ${e.context.configKey} for "${a}": ${String(e.context.timeoutValue)}`),d?(this.logger.warn(` ${z}${G}${L} ${a} ${q}\n`),!0):(this.logger.error(` ${B}${G}${L} ${a}\n`),!1)}let p={...process.env,...t.env};if(e.env)for(let[n,r]of Object.entries(e.env))p[n]=this.parser.interpolate(String(r),t);p.WORKFLOW_STEP_NAME=a,p[yt]=this.formatStepDisplay(p.WORKFLOW_JOB_NAME,a);let m;l&&(m=this.createStatusFile(),p.WORKFLOW_STATUS_FILE=m,this.logger.info(` ${R}status-file${L} ${m}`));let h=e[`working-directory`]?(0,s.resolve)(n.workflowDir,this.parser.interpolate(e[`working-directory`],t)):n.workflowDir;if(n.dryRun)return this.logger.info(` ${Ze}dry ${L} ${a}`),this.logger.info(`${it}${R}\$ ${o.split(`
14
+ `).join(at)}${L}`),!0;let g;try{g=this.resolveTimeoutRetries(e,a)}catch(e){if(!(e instanceof Je))throw e;return this.logger.error(` ${B}${W}${L} invalid ${e.context.configKey} for "${a}": ${String(e.context.timeoutValue)}`),d?(this.logger.warn(` ${z}${G}${L} ${a} ${q}\n`),!0):(this.logger.error(` ${B}${G}${L} ${a}\n`),!1)}let _=!1;for(let e=0;e<=g;e++){if(await this.waitForCodexQuotaAvailability(a,o,u),e>0?this.logger.warn(` ${z}${mt}${L} retrying step after timeout "${a}" (attempt ${e+1}/${g+1})`):this.logger.info(` ${Ze}run ${L} ${a}`),this.logger.info(`${it}${R}\$ ${o.split(`
15
+ `)[0]}${o.includes(`
16
+ `)?` ...`:``}${L}`),n.stopRequestPath&&(0,c.existsSync)(n.stopRequestPath))throw this.logger.info(`\n ${z}${K}${L} ${a} (${Tt})`),new N(V,{cause:Error(`Workflow stop requested before "${a}"`),context:this.createInterruptContext(a,o)});let t=l?this.withInteractiveTerminalTitle(o,p):o,r=this.executeCommand(t,h,p,a,l,f),i=l&&m?await this.raceInteractiveCompletion(r,m,a,n.stopRequestPath):n.stopRequestPath?await this.raceStopRequest(r,a,n.stopRequestPath):await r;if(i.status===`stop_requested`)throw this.logger.info(`\n ${z}${K}${L} ${a} (${Tt})`),new N(V,{cause:Error(`Workflow stop requested during "${a}"`),context:this.createInterruptContext(a,o)});if(i.status===`signaled`)throw this.logger.info(`\n ${z}${K}${L} ${a}`),new N(i.signal,{cause:Error(`Step process ended with ${i.signal}`),context:this.createInterruptContext(a,o)});if(i.status===`completed`&&(i.exitCode===130||i.exitCode===143)){let e=i.exitCode===130?$e:V;throw this.logger.info(`\n ${z}${K}${L} ${a}`),new N(e,{cause:Error(`Step process exited with ${i.exitCode}`),context:this.createInterruptContext(a,o)})}if(i.status===`spawn_error`)return this.logger.error(` ${B}${W}${L} unable to start step "${a}" in ${i.error.context.workDir} (${this.formatSpawnError(i.error)})`),d?(this.logger.warn(` ${z}${G}${L} ${a} ${q}\n`),!0):(this.logger.error(` ${B}${G}${L} ${a}\n`),!1);if(i.status===`timed_out`){if(this.logger.error(` ${B}${W}${L} step timed out after ${this.formatTimeoutMs(i.timeoutMs)} "${a}" (${o.split(`
17
+ `)[0]})`),e<g){if(m)try{(0,c.writeFileSync)(m,``,`utf-8`)}catch(e){this.logger.warn(` ${z}${U}${L} status-file reset failed for ${a}: ${e instanceof Error?e.message:String(e)}`)}continue}return m&&this.cleanupStatusFile(m),d?(this.logger.warn(` ${z}${G}${L} ${a} ${q}\n`),!0):(this.logger.error(` ${B}${G}${L} ${a}\n`),!1)}if(m&&this.cleanupStatusFile(m),i.status===`status_completed`)return this.logger.info(`\n ${Qe}${wt}${L} ${a} (status-file signaled completion)\n`),!0;if(i.status===`completed`&&i.exitCode===0)return this.logger.info(`\n ${Qe}pass${L} ${a}\n`),!0;if(i.status===`completed`&&this.logger.error(` ${B}${W}${L} step "${a}" exited with code ${i.exitCode} (${o.split(`
18
+ `)[0]})`),!_&&await this.shouldRetryForCodexQuota(a,u)){_=!0,this.logger.warn(` ${z}${mt}${L} codex quota reached after failed step, retrying after reset`),--e;continue}return d?(this.logger.warn(` ${z}${G}${L} ${a} ${q}\n`),!0):(this.logger.error(` ${B}${G}${L} ${a}\n`),!1)}return!1}async executeCommand(e,t,n,i,a,o){return a&&this.logger.info(` ${R}(interactive: output renders directly to terminal)${L}`),await new Promise(s=>{let c=a?this.captureTerminalState():null,l=a?(0,r.spawn)(e,[],{stdio:[H,H,H],cwd:t,detached:!1,env:{...n,[nt]:`1`},shell:tt}):(0,r.spawn)(e,[],{stdio:[H,rt,rt],cwd:t,detached:ut,env:{...n,[nt]:`1`},shell:tt});this.activeStep={command:e,process:l,processGroupId:ut&&!a&&typeof l.pid==`number`?l.pid:null,stepName:i};let u=null,d=!1,f=!1,p=e=>{f||(f=!0,m&&clearTimeout(m),u&&clearTimeout(u),this.activeStep=null,a&&this.restoreTerminalState(c),s(e))},m=o===null?null:setTimeout(()=>{if(d=!0,this.killActiveStep(V)){u=setTimeout(()=>{f||(this.logger.warn(` ${z}${U}${L} ${i} forcing SIGKILL`),this.killActiveStep(ht))},gt);return}p({status:`timed_out`,timeoutMs:o})},o);l.stdout?.on(`data`,e=>{for(let t of e.toString().split(`
19
+ `))t&&this.logger.info(t)}),l.stderr?.on(`data`,e=>{for(let t of e.toString().split(`
20
+ `))t&&this.logger.error(t)}),l.on(`error`,n=>{p({status:`spawn_error`,error:new qe({commandPreview:e.split(`
21
+ `)[0],source:`StepRunnerService.executeCommand`,stepName:i,workDir:t},{cause:n})})}),l.on(`exit`,(e,t)=>{if(this.cleanupProcessGroup(),d&&o!==null){p({status:`timed_out`,timeoutMs:o});return}if(t===$e){p({status:`signaled`,signal:$e});return}if(t===V){p({status:`signaled`,signal:V});return}p({status:`completed`,exitCode:e??1})})})}createInterruptContext(e,t){return{phase:`step_execution`,stepName:e,commandPreview:t.split(`
22
+ `)[0],source:`StepRunnerService.runStep`}}formatSpawnError(e){let t=e.cause;return t instanceof Error?`${`code`in t&&typeof t.code==`string`?t.code:`unknown-spawn-error`}${`path`in t&&typeof t.path==`string`?` at ${t.path}`:``}; verify command, shell, and permissions`:`check shell availability, PATH, and permissions`}resolveStepCommand(e,t,n){let r=e[dt]!=null,i=r?{command:e[dt],interactive:!0}:{command:e.run,interactive:!1},a=r?{command:e.run,interactive:!1}:{command:e[dt],interactive:!0},o=this.resolveExactAgentCommand(i.command,t,n);if(o)return{...o,interactive:i.interactive};let s=this.resolveExactAgentCommand(a.command,t,n);if(s)return{...s,interactive:a.interactive};let c=this.resolveMissingExplicitRunner(e,n);if(c)return{command:``,interactive:!1,missingRunner:c};let l=this.resolveStringCommand(i.command,t);if(l)return{command:l,interactive:i.interactive};let u=this.resolveStringCommand(a.command,t);if(u)return{command:u,interactive:a.interactive};let d=this.resolveFirstMappedCommand(i.command,t);if(d)return{command:d,interactive:i.interactive};let f=this.resolveFirstMappedCommand(a.command,t);return f?{command:f,interactive:a.interactive}:null}resolveExactAgentCommand(e,t,n){if(!n||!e||typeof e==`string`)return null;let r=e[n];return r?{agentKey:n,command:this.parser.interpolate(r,t),interactive:!1}:null}resolveStringCommand(e,t){return!e||typeof e!=`string`?null:this.parser.interpolate(e,t)}resolveFirstMappedCommand(e,t){if(!e||typeof e==`string`)return null;let[n]=Object.values(e);return n?this.parser.interpolate(n,t):null}resolveMissingExplicitRunner(e,t){if(!t)return null;let n=this.collectMappedRunnerKeys(e);return n.length===0||n.includes(t)?null:{availableKeys:n,runner:t}}collectMappedRunnerKeys(e){let t=new Set;for(let n of[e[dt],e.run])if(n&&typeof n!=`string`)for(let e of Object.keys(n))t.add(e);return[...t]}resolveRunner(e){return e.runner??e.cliAgent}formatStepDisplay(e,t){return e?`${e} > ${t}`:t}withInteractiveTerminalTitle(e,t){let n=`${bt}: ${t[yt]??e.split(`
23
+ `)[0]??et}`,r=`[ -t 1 ] && printf '\\033]0;%s\\007' ${this.escapeShellArg(n)}`,i=`[ -t 1 ] && printf '\\033]0;%s\\007' ${this.escapeShellArg(bt)}`;return`trap ${this.escapeShellArg(i)} EXIT
24
+ ${r}
25
+ ${e}`}escapeShellArg(e){return`'${e.replace(/'/g,`'\\''`)}'`}async shouldRetryForCodexQuota(e,t){return t?(await this.readCodexQuotaStatus(e))?.blockingLimit!=null:!1}async waitForCodexQuotaAvailability(e,t,n){if(n)for(;;){let n=await this.readCodexQuotaStatus(e),r=n?.blockingLimit;if(!r)return;let i=r.resetAt*1e3+Et,a=Math.max(i-Date.now(),Et),o=new Date(i).toLocaleString(),s=n?.planType?` (${n.planType})`:``;this.logger.warn(` ${z}wait ${L} codex quota ${r.limitName}/${r.window} is at ${r.usedPercent}%${s}; waiting until ${o}`),await this.waitForQuotaDelay(e,t,a)}}async readCodexQuotaStatus(e){try{return await this.quotaService.getQuotaStatus()}catch(t){return this.logger.warn(` ${z}${U}${L} unable to read codex quota for "${e}" (${this.formatQuotaError(t)})`),null}}async waitForQuotaDelay(e,t,n){let r=new AbortController;this.activeQuotaWait={controller:r,signal:null,stepName:e};try{let e=n;for(;e>0;){let t=Math.min(e,3e4);await this.waitForDelay(t,r.signal),e-=t}}catch(n){throw r.signal.aborted?new N(this.activeQuotaWait?.signal??V,{cause:Error(`Interrupted while waiting for Codex quota reset`),context:{...this.createInterruptContext(e,t),phase:`StepRunnerService.waitForCodexQuota`}}):n}finally{this.activeQuotaWait=null}}async waitForDelay(e,t){await new Promise((n,r)=>{let i=setTimeout(()=>{t.removeEventListener(`abort`,a),n()},e),a=()=>{clearTimeout(i),t.removeEventListener(`abort`,a),r(Error(`aborted`))};t.addEventListener(`abort`,a,{once:!0})})}formatQuotaError(e){return e instanceof Error&&e.message.trim().length>0?e.message:`unknown quota error`}resolveTimeoutMs(e,t){let n=e[ft];if(n===void 0)return null;try{let e=Ot.parse(n);return Math.ceil(e*_t)}catch(e){throw new Je({stepName:t,configKey:ft,timeoutValue:n},{cause:e})}}resolveTimeoutRetries(e,t){let n=e[pt];try{return kt.parse(n)}catch(e){throw new Je({stepName:t,configKey:pt,timeoutValue:n},{cause:e})}}formatTimeoutMs(e){return e%_t===0?`${e/_t} minute(s)`:`${e}ms`}cleanupProcessGroup(){if(!this.activeStep)return;let{processGroupId:e,stepName:t}=this.activeStep;if(e!==null)try{process.kill(-e,V)}catch(n){let r=n.code;r===lt?this.logger.info(` ${R}cleanup${L} process group ${e} already exited (ESRCH)`):this.logger.warn(` ${z}${U}${L} failed to clean up process group for ${t} (pgid=${e}, error=${r})`)}}killActiveStep(e){if(!this.activeStep)return!1;let{process:t,processGroupId:n}=this.activeStep;if(n!==null)try{return process.kill(-n,e),!0}catch(e){e.code===lt?this.logger.info(` ${R}signal${L} process group for ${this.activeStep.stepName} already exited (ESRCH)`):this.logger.warn(` ${z}${U}${L} ${ct} process group for ${this.activeStep.stepName}`)}try{return t.kill(e)}catch(e){return this.logger.warn(` ${z}${U}${L} ${ct} ${this.activeStep.stepName} (${e.message})`),!1}}createStatusFile(){let e=(0,s.join)((0,o.tmpdir)(),`workflow-step-${(0,d.randomUUID)()}.status`);return(0,c.writeFileSync)(e,``,`utf-8`),e}async raceInteractiveCompletion(e,t,n,r){return new Promise(i=>{let a=!1,o=!1,s=!1,l=null,u=null,d=null,f=null,p=null,m=e=>{a||(a=!0,u&&=(u.close(),null),d&&=(d.close(),null),f&&=(clearInterval(f),null),p&&=(clearInterval(p),null),l&&=(clearTimeout(l),null),i(e))},h=!1,g=!1,_=()=>{try{return(0,c.existsSync)(t)&&(0,c.readFileSync)(t,`utf-8`).trim()===`YES`}catch(e){let t=e.code;return!t||!St.has(t)?this.logger.warn(` ${z}${U}${L} status-file read failed for ${n}: ${e instanceof Error?e.message:String(e)}`):this.logger.info(` ${R}status-file${L} transient FS error (${t}) polling ${n}`),!1}},v=()=>{if(!(o||!_())){if(o=!0,h=this.killActiveStep(V),!h){this.logger.warn(` ${z}${U}${L} ${n} status-file: SIGTERM could not be delivered, process may have already exited`);return}l=setTimeout(()=>{a||(g=!0,this.logger.warn(` ${z}${U}${L} ${n} did not exit after status-file SIGTERM, forcing SIGKILL`),this.killActiveStep(ht))},gt)}},y=()=>!!(r&&(0,c.existsSync)(r)),b=()=>{if(!(s||!y())){if(s=!0,h=this.killActiveStep(V),!h){this.logger.warn(` ${z}${U}${L} ${n} stop request: SIGTERM could not be delivered, process may have already exited`);return}l=setTimeout(()=>{a||(g=!0,this.logger.warn(` ${z}${U}${L} ${n} did not exit after stop-request SIGTERM, forcing SIGKILL`),this.killActiveStep(ht))},gt)}};try{u=this.watchStatusFile(t,()=>v())}catch(e){this.logger.warn(` ${z}${U}${L} status-file watcher setup failed for ${n}: ${e instanceof Error?e.message:String(e)}`)}if(f=setInterval(v,1e3),r){try{d=this.watchStatusFile(r,()=>b())}catch(e){e.code!==`ENOENT`&&this.logger.warn(` ${z}${U}${L} stop-request watcher setup failed for ${n}: ${e instanceof Error?e.message:String(e)}`)}p=setInterval(b,Ct)}v(),b(),e.then(e=>{if(s){g&&this.logger.warn(` ${z}${U}${L} ${n} required SIGKILL escalation after stop request`),m({status:Dt});return}if(!(o||_())){m(e);return}switch(o||(o=!0,this.logger.info(` ${Qe}${wt}${L} ${n} status-file observed during process exit handling`)),e.status){case`spawn_error`:case`timed_out`:m(e);return;case`signaled`:g&&this.logger.warn(` ${z}${U}${L} ${n} required SIGKILL escalation (work completed via status-file)`),m(xt);return;case`completed`:e.exitCode===0||e.exitCode>128||g||!h?(g&&this.logger.warn(` ${z}${U}${L} ${n} required SIGKILL escalation (work completed via status-file, exit code ${e.exitCode})`),m(xt)):m(e);return;case`status_completed`:m(e);return;case Dt:m(e);return;default:m(e)}})})}async raceStopRequest(e,t,n){return new Promise(r=>{let i=!1,a=!1,o=null,s=null,l=null,u=e=>{i||(i=!0,l&&=(l.close(),null),s&&=(clearInterval(s),null),o&&=(clearTimeout(o),null),r(e))},d=()=>{if(!(a||!(0,c.existsSync)(n))){if(a=!0,!this.killActiveStep(V)){this.logger.warn(` ${z}${U}${L} ${t} stop request: SIGTERM could not be delivered, process may have already exited`);return}o=setTimeout(()=>{i||(this.logger.warn(` ${z}${U}${L} ${t} did not exit after stop-request SIGTERM, forcing SIGKILL`),this.killActiveStep(ht))},gt)}};try{l=this.watchStatusFile(n,()=>d())}catch(e){e.code!==`ENOENT`&&this.logger.warn(` ${z}${U}${L} stop-request watcher setup failed for ${t}: ${e instanceof Error?e.message:String(e)}`)}s=setInterval(d,Ct),d(),e.then(e=>{u(a?{status:Dt}:e)})})}captureTerminalState(){if(!process.stdin.isTTY)return null;try{return(0,r.execFileSync)(`stty`,[`-g`],{encoding:`utf-8`,stdio:[H,rt,`ignore`]}).trim()}catch{return null}}restoreTerminalState(e){if(e&&process.stdin.isTTY)try{(0,r.execFileSync)(`stty`,[e],{stdio:[H,`ignore`,`ignore`]})}catch{}process.stdout.isTTY&&process.stdout.write(`\x1B[0m\x1B[?25h\x1B[?1049l`)}cleanupStatusFile(e){try{(0,c.existsSync)(e)&&(0,c.unlinkSync)(e)}catch(e){this.logger.warn(` ${z}${U}${L} status-file cleanup failed: ${e instanceof Error?e.message:String(e)}`)}}};const J=`\x1B[0m`,jt=`\x1B[1m`,Mt=`\x1B[2m`,Nt=`\x1B[36m`,Pt=`\x1B[32m`,Ft=`\x1B[31m`,It=`\x1B[34m`;var Lt=class{activePrompt=null;activePromptSignal=null;constructor(e,t){this.stepRunner=e,this.logger=t}async pathExists(e){try{return await(0,a.access)(e),!0}catch{return!1}}abortActivePrompt(e){this.activePromptSignal=e,this.activePrompt?.close()}async handleUserPrompt(e,t,n,r,i){if(!e.on||!(`user_prompt`in e.on))return!0;this.logger.info(`\n ${It}${jt}trigger${J} ${jt}user_prompt${J}`),this.logger.info(` ${`─`.repeat(50)}`);let o=t,c=n.CONTEXT_FILE;if(!o&&c){let e=(0,s.resolve)(r,c);await this.pathExists(e)&&(o=(await(0,a.readFile)(e,`utf-8`)).trim(),o&&this.logger.info(` ${Pt}found${J} Existing context.md -> ${c}`))}if(!o)if(i)this.logger.info(` ${Nt}dry ${J} Would prompt user for input`),o=`(dry-run: no prompt collected)`;else{this.logger.info(` ${Nt}input${J} Enter your prompt (press Enter twice to finish):\n`);let e=[],t=(0,f.createInterface)({input:process.stdin,output:process.stdout});this.activePrompt=t,this.activePromptSignal=null,o=await new Promise((n,r)=>{let i=!1,a=e=>{i||(i=!0,this.activePrompt=null,e())};t.on(`line`,n=>{n===``&&e.length>0&&e[e.length-1]===``?t.close():e.push(n)}),t.on(`SIGINT`,()=>{this.activePromptSignal=`SIGINT`,t.close()}),t.on(`close`,()=>a(()=>{let t=this.activePromptSignal;if(this.activePromptSignal=null,t){r(new N(t));return}e.length>0&&e[e.length-1]===``&&e.pop(),n(e.join(`
26
+ `))}))})}if(!o)return this.logger.error(` ${Ft}fail${J} No prompt provided. Aborting.`),!1;if(c){let e=(0,s.resolve)(r,c);await(0,a.mkdir)((0,s.dirname)(e),{recursive:!0}),await(0,a.writeFile)(e,o,`utf-8`),this.logger.info(` ${Pt}saved${J} ${o.split(`
27
+ `).length} line(s) -> ${c}`)}return n.USER_PROMPT=o,this.logger.info(` ${Mt}prompt${J} ${o.split(`
28
+ `)[0].slice(0,80)}${o.includes(`
29
+ `)?`...`:``}`),!0}async handleAgentDecision(e,t,n,r,i,o){let c=e.on?.agent_decision;if(!c?.steps)return!0;this.logger.info(`\n ${It}${jt}trigger${J} ${jt}agent_decision${J}`),this.logger.info(` ${`─`.repeat(50)}`),o&&(t.HEARTBEAT_PROMPT=o,this.logger.info(` ${Mt}prompt${J} ${o.split(`
30
+ `)[0].slice(0,80)}${o.includes(`
31
+ `)?`...`:``}`));let l=t.CONTEXT_FILE;l&&await(0,a.mkdir)((0,s.dirname)((0,s.resolve)(i.workflowDir,l)),{recursive:!0});let u={inputs:n,matrix:{},secrets:r,env:t};for(let e of c.steps)if(!await this.stepRunner.runStep(e,u,i))return this.logger.error(`\n ${Ft}Agent decision aborted workflow${J}`),!1;if(l&&!i.dryRun){let e=(0,s.resolve)(i.workflowDir,l);if(!await this.pathExists(e))return this.logger.error(` ${Ft}fail${J} Context file not created: ${l}`),!1;let t=(await(0,a.readFile)(e,`utf-8`)).trim();if(!t)return this.logger.error(` ${Ft}fail${J} Context file is empty: ${l}`),!1;this.logger.info(` ${Pt}ready${J} context has ${t.split(`
32
+ `).length} line(s)`)}return!0}};const Rt=[`actions/checkout`,`actions/setup-node`,`actions/cache`,`actions/upload-artifact`,`actions/download-artifact`,`pnpm/action-setup`],zt=`bold.calm.cool.dark.deep.fair.fast.free.gold.keen.kind.loud.neat.pure.rare.safe.slim.soft.tall.warm.wild.wise.blue.gray.jade.iron.zinc.ruby.opal.onyx`.split(`.`),Bt=`arch.beam.bird.bolt.cape.cove.dawn.dove.echo.fern.flux.gale.hawk.haze.iris.jade.kite.lake.lynx.mesa.moth.node.palm.peak.pine.raft.reef.sage.tide.vale`.split(`.`);var Vt=class{generateHumanReadableId(){return`${zt[Math.floor(Math.random()*zt.length)]}-${Bt[Math.floor(Math.random()*Bt.length)]}`}parseWorkflowFile(e){let t=(0,s.resolve)(e);if(!(0,c.existsSync)(t))throw Error(`Workflow file not found: ${t}`);let n=(0,c.readFileSync)(t,`utf-8`),r=be.parse((0,p.load)(n));if(r.imports&&this.resolveImports(r,t),!r.jobs||Object.keys(r.jobs).length===0)throw Error(`No jobs found in workflow file`);return this.resolveExtends(r),r}resolveImports(e,t,n=new Set){let r=(0,s.resolve)(t);if(n.has(r))throw Error(`Circular import detected: ${r}`);n.add(r);let i=r.replace(/\/[^/]+$/,``);for(let t of e.imports??[]){let a=(0,s.resolve)(i,t);if(!(0,c.existsSync)(a))throw Error(`Imported workflow file not found: ${a} (from ${r})`);let o=(0,p.load)((0,c.readFileSync)(a,`utf-8`));if(o.imports&&this.resolveImports(o,a,n),o.env&&(e.env={...o.env,...e.env}),o.pre&&(e.pre||={},o.pre.services&&(e.pre.services=[...o.pre.services,...e.pre.services??[]]),o.pre.steps&&(e.pre.steps=[...o.pre.steps,...e.pre.steps??[]])),o.post&&(e.post||={},o.post.services&&(e.post.services=[...o.post.services,...e.post.services??[]]),o.post.steps&&(e.post.steps=[...o.post.steps,...e.post.steps??[]])),o.worktree){e.worktree||={};for(let t of[`beforeCreate`,`afterCreate`,`beforeCompleted`,`afterCompleted`])o.worktree[t]&&!e.worktree[t]&&(e.worktree[t]=o.worktree[t])}if(o[`max-workflows`]&&!e[`max-workflows`]&&(e[`max-workflows`]=o[`max-workflows`]),o[`launch-command`]&&!e[`launch-command`]&&(e[`launch-command`]=o[`launch-command`]),o.keybindings&&(e.keybindings={...o.keybindings,...e.keybindings}),o[`system-prompt`]&&(e[`system-prompt`]?e[`system-prompt`]=`${o[`system-prompt`]}\n\n${e[`system-prompt`]}`:e[`system-prompt`]=o[`system-prompt`]),o.jobs)for(let[t,n]of Object.entries(o.jobs))t in(e.jobs??{})||(e.jobs||={},e.jobs[t]=n)}delete e.imports}resolveExtends(e){let{jobs:t}=e,n=new Map;for(let[e,r]of Object.entries(t))e.startsWith(`.`)&&n.set(e,r);for(let[e,r]of Object.entries(t)){if(e.startsWith(`.`)||!r.extends)continue;let t=Array.isArray(r.extends)?r.extends:[r.extends],i=[...r.steps??[]],a=[...r.preJob?.steps??[]],o=[...r.postJob?.steps??[]],s=[],c=[],l=[];for(let i of t){let t=n.get(i);if(!t)throw Error(`Job "${e}" extends "${i}" but template not found`);s.push(...t.steps??[]),c.push(...t.preJob?.steps??[]),l.push(...t.postJob?.steps??[]),this.applyTemplate(r,t)}r.steps=[...s,...i],this.assignJobHookSteps(r,`preJob`,[...c,...a]),this.assignJobHookSteps(r,`postJob`,[...l,...o]),delete r.extends}for(let e of n.keys())delete t[e]}applyTemplate(e,t){e.steps=[...t.steps??[],...e.steps??[]],this.mergeJobHookSteps(e,t,`preJob`),this.mergeJobHookSteps(e,t,`postJob`),(t.env||e.env)&&(e.env={...t.env,...e.env}),t[`system-prompt`]&&e[`system-prompt`]?e[`system-prompt`]=`${t[`system-prompt`]}\n\n${e[`system-prompt`]}`:t[`system-prompt`]&&(e[`system-prompt`]=t[`system-prompt`]),t[`runs-on`]&&!e[`runs-on`]&&(e[`runs-on`]=t[`runs-on`]),t.strategy&&!e.strategy&&(e.strategy=t.strategy),t.context&&!e.context&&(e.context=t.context)}mergeJobHookSteps(e,t,n){let r=t[n]?.steps??[],i=e[n]?.steps??[];this.assignJobHookSteps(e,n,[...r,...i])}assignJobHookSteps(e,t,n){if(n.length>0){e[t]={steps:n};return}delete e[t]}loadSecrets(e){let t=new Map;if(!(0,c.existsSync)(e))return t;let n=(0,c.readFileSync)(e,`utf-8`);for(let e of n.split(`
33
+ `)){let n=e.trim();if(!n||n.startsWith(`#`))continue;let r=n.indexOf(`=`);r!==-1&&t.set(n.slice(0,r),n.slice(r+1))}return t}interpolate(e,t){return e.replace(/\$\{\{\s*(.+?)\s*\}\}/g,(e,n)=>{let i=n.trim().split(`.`);if(i[0]===`inputs`&&i[1])return t.inputs.get(i[1])||``;if(i[0]===`matrix`&&i[1])return t.matrix[i[1]]||``;if(i[0]===`secrets`&&i[1])return t.secrets.get(i[1])||process.env[i[1]]||``;if(i[0]===`env`&&i[1])return t.env[i[1]]||process.env[i[1]]||``;if(i[0]===`runner`&&i[1]===`os`)return`macOS`;if(i[0]===`github`&&i[1]===`sha`)try{return(0,r.execSync)(`git rev-parse HEAD`,{encoding:`utf-8`,stdio:[`ignore`,`pipe`,`ignore`]}).trim()}catch{return`local`}return n.includes(`hashFiles`)?`local`:`\${{ ${n} }}`})}isActionSkipped(e){return Rt.some(t=>e.startsWith(t))}resolveJobOrder(e,t){let n=new Set,r=[],i=t=>{if(n.has(t))return;n.add(t);let a=e[t];if(!a)throw Error(`Job not found: ${t}`);let o=a.needs?Array.isArray(a.needs)?a.needs:[a.needs]:[];for(let e of o)i(e);r.push(t)};if(t)i(t);else for(let t of Object.keys(e))i(t);return r}expandMatrix(e){if(!e.strategy?.matrix)return[{}];let{include:t,...n}=e.strategy.matrix;if(t&&t.length>0)return t;let r=Object.keys(n).filter(e=>Array.isArray(n[e]));if(r.length===0)return[{}];let i=[{}];for(let e of r){let t=n[e],r=[];for(let n of i)for(let i of t)r.push({...n,[e]:String(i)});i=r}return i}};const Ht=`\x1B[0m`,Ut=`\x1B[2m`;var Wt=class{constructor(e,t){this.stepRunner=e,this.logger=t}createWorktree(e,t){let n=t.toLowerCase().replace(/[^a-z0-9-]+/g,`-`).replace(/^-|-$/g,``),i=(0,s.resolve)((0,s.dirname)(e),`${(0,s.basename)(e)}-worktree`),a=(0,s.resolve)(i,n);(0,c.existsSync)(i)||(0,c.mkdirSync)(i,{recursive:!0});let o=`worktree/${n}`;(0,c.existsSync)(a)&&(this.logger.info(` ${Ut}clean${Ht} Removing stale worktree at ${a}`),this.cleanupWorktreePath(e,a));try{(0,r.execSync)(`git branch -D "${o}"`,{cwd:e,stdio:[`ignore`,`pipe`,`ignore`]})}catch{}return this.logger.info(` create${Ht} Worktree at ${a} (branch: ${o})`),(0,r.execSync)(`git worktree add -b "${o}" "${a}" HEAD`,{cwd:e,stdio:[`ignore`,`pipe`,`ignore`]}),a}removeWorktree(e,t){if((0,c.existsSync)(t)){this.logger.info(` ${Ut}remove${Ht} Worktree at ${t}`);try{this.cleanupWorktreePath(e,t)}catch{this.logger.warn(` warn${Ht} Failed to remove worktree (may need manual cleanup)`)}}}cleanupWorktreePath(e,t){if(this.isRegisteredWorktree(e,t)){(0,r.execSync)(`git worktree remove --force "${t}"`,{cwd:e,stdio:[`ignore`,`pipe`,`ignore`]});return}(0,c.rmSync)(t,{recursive:!0,force:!0})}isRegisteredWorktree(e,t){try{return(0,r.execSync)(`git worktree list --porcelain`,{cwd:e,encoding:`utf-8`,stdio:[`ignore`,`pipe`,`ignore`]}).split(`
34
+ `).some(e=>e.startsWith(`worktree `)&&(0,s.resolve)(e.slice(9))===t)}catch{return!1}}async runHook(e,t,n,r,i,a){let o={inputs:r,matrix:{},secrets:i,env:n};for(let n of e.steps)if(!await this.stepRunner.runStep(n,o,{...a,workflowDir:t}))return!1;return!0}};const Y=`\x1B[0m`,X=`\x1B[1m`,Gt=`\x1B[2m`,Kt=`\x1B[32m`,Z=`\x1B[33m`,Q=`\x1B[31m`,$=`\x1B[34m`,qt=`SIGINT`,Jt=`SIGTERM`,Yt=`workflow_execution`,Xt=`restart-from`,Zt=`user_prompt`,Qt=`agent_decision`,$t=`workflow_dispatch`,en=`Workflow post-cleanup failed`;var tn=class{logger;parser;serviceManager;stepRunner;jobRunner;triggerService;worktreeService;registry;outputLines=[];interruptedSignal=null;constructor(e,t={}){let n=e||{info:e=>process.stdout.write(`${e}\n`),error:e=>console.error(e),warn:e=>console.error(e)},r=e=>{this.outputLines.push(e),this.outputLines.length>5e3&&this.outputLines.shift()};this.logger={info:e=>{r(e),n.info(e)},error:e=>{r(e),n.error(e)},warn:e=>{r(e),n.warn(e)}},this.parser=t.parser??new Vt,this.serviceManager=t.serviceManager??new Be(this.logger),this.stepRunner=t.stepRunner??new At(this.parser,this.logger),this.jobRunner=t.jobRunner??new Ke(this.parser,this.stepRunner,this.logger),this.triggerService=t.triggerService??new Lt(this.stepRunner,this.logger),this.worktreeService=t.worktreeService??new Wt(this.stepRunner,this.logger),this.registry=t.registry??new Ae}async run(e){this.outputLines=[],this.interruptedSignal=null;let t=e.dryRun??!1,n=e.continueOnError??!1,r=!1,i=e=>{r||(r=!0,this.interrupt(e))},a=()=>i(qt),o=()=>i(Jt);process.on(qt,a),process.on(Jt,o);try{return await this.executeWorkflow(e,t,n,e.keepWorktree??!1)}catch(e){if(e instanceof N||this.isInterrupted())return await this.serviceManager.stopAll(),this.createInterruptedResult();throw e}finally{process.off(qt,a),process.off(Jt,o),await this.serviceManager.stopAll()}}interrupt(e,t={phase:Yt}){if(this.interruptedSignal)return;this.interruptedSignal=e;let n=t.phase===Yt?``:` (${t.phase})`;this.logger.info(`\n\n${Z}${X}Interrupted — shutting down...${Y}${n}`),this.triggerService.abortActivePrompt(e),this.stepRunner.stopActiveStep(e),this.serviceManager.stopAll()}isInterrupted(){return this.interruptedSignal===qt||this.interruptedSignal===Jt}createInterruptedResult(){return{exitCode:130,output:this.outputLines.join(`
35
+ `)}}parseFixRestartFrom(e,t){if(!e.startsWith(`---`))return{};let n=e.indexOf(`---`,3);if(n<0)return{};let r=e.slice(3,n);for(let e of r.split(`
36
+ `)){let n=e.trim();if(n.startsWith(`${Xt}:`)){let e=n.slice(`${Xt}:`.length).trim();return t.includes(e)?{restartFrom:e}:{invalidTarget:e}}}return{}}async pathExists(e){try{return await(0,a.access)(e),!0}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async waitForServiceStartup(){await new Promise(e=>{setTimeout(e,2e3)})}resolveRunner(e){if(e.runner&&e.cliAgent&&e.runner!==e.cliAgent)throw Error(`Conflicting runner selectors: runner="${e.runner}" cliAgent="${e.cliAgent}"`);return e.runner??e.cliAgent}async executeWorkflow(e,t,n,i=!1){let o=(0,s.resolve)(e.workflowPath),c=this.parser.parseWorkflowFile(o),l=this.resolveRunner(e),u=null,d=null,f=null,p=(0,s.dirname)(o),m=p,h=null,g=!1,_=!1,v=!!c.worktree,y=new Map,b=e.secretFile?this.parser.loadSecrets(e.secretFile):new Map,x={};try{let f=c.on?.[$t]?.inputs||{};for(let[e,t]of Object.entries(f))t.default&&y.set(e,t.default);if(e.inputs)for(let[t,n]of Object.entries(e.inputs))y.set(t,n);if(c.env)for(let[e,t]of Object.entries(c.env))x[e]=String(t);if(e.env)for(let[t,n]of Object.entries(e.env))x[t]=n;l&&(x.WORKFLOW_RUNNER=l,x.WORKFLOW_CLI_AGENT=l);let S=p;for(;S!==`/`;){if(await this.pathExists((0,s.resolve)(S,`.git`))){p=S;break}S=(0,s.dirname)(S)}let C=c[`max-workflows`];if(C){let t=this.registry.resolveWorkspace(e.workspace,c.workspace),n=await this.registry.countRunningWorkflows(t);if(n>=C)throw new Ne(t,n,C)}let ee=c[`launch-command`];if(ee&&!e.skipLaunch){let t=this.buildLaunchCliCommand(e,o),n=e.name||c.name||(0,s.basename)(o),i=e.name?n:`${n}-${this.parser.generateHumanReadableId()}`,a=ee.replace(`{name}`,i).replace(`{command}`,t);this.logger.info(`${Gt}Delegating via launch-command: ${a}${Y}`);try{return(0,r.execSync)(a,{stdio:`inherit`,cwd:p}),{exitCode:0,output:this.outputLines.join(`
37
+ `)}}catch(e){return{exitCode:e.status??1,output:this.outputLines.join(`
38
+ `)}}}let te=e.name||c.name||(0,s.basename)(o),w=e.name?te:`${te}-${this.parser.generateHumanReadableId()}`;if(u=await this.registry.createRun({displayName:w,dryRun:t,workflowPath:o,workflowWorkspace:c.workspace,workspace:e.workspace}),x.WORKFLOW_RUN_DIR=u.runDir,x.WORKFLOW_WORKSPACE=u.workspace,x.CONTEXT_FILE&&(x.CONTEXT_FILE=u.contextPath,x.CHANGELOG_FILE=u.changelogPath),this.logger.info(`${X}${`═`.repeat(60)}${Y}`),this.logger.info(` ${X}run-workflow${Y} - ${u.displayName}`),this.logger.info(`${X}${`═`.repeat(60)}${Y}`),this.logger.info(` File: ${o}`),this.logger.info(` Workspace: ${u.workspace||`default`}`),this.logger.info(` Run Dir: ${u.runDir}`),this.logger.info(` CWD: ${p}`),y.size>0&&this.logger.info(` Inputs: ${[...y.entries()].map(([e,t])=>`${e}=${t}`).join(`, `)}`),b.size>0&&this.logger.info(` Secrets: ${[...b.keys()].join(`, `)}`),e.job&&this.logger.info(` Target: ${e.job}`),l&&this.logger.info(` Runner: ${l}`),c.pre){let e=c.pre.services?.length||0,t=c.pre.steps?.length||0;this.logger.info(` Pre: ${e} service(s), ${t} setup step(s)`)}if(c.post){let e=c.post.services?.length||0,t=c.post.steps?.length||0;this.logger.info(` Post: ${e} service(s), ${t} cleanup step(s)`)}v&&this.logger.info(` Worktree: enabled${i?` (keep on completion)`:``}`);let T=c.on&&Zt in c.on?Zt:c.on?.[Qt]?Qt:$t;if(this.logger.info(` Trigger: ${T}`),this.logger.info(` Mode: ${t?`dry-run`:`execute`}`),this.logger.info(`${X}${`═`.repeat(60)}${Y}`),!await this.triggerService.handleUserPrompt(c,e.prompt||null,x,p,t))return this.isInterrupted()?(d=this.createInterruptedResult(),d):(this.logger.error(`\n${Q}${X}Workflow aborted: no prompt provided${Y}`),d={exitCode:1,output:this.outputLines.join(`
39
+ `)},d);if(!await this.triggerService.handleAgentDecision(c,x,y,b,{runner:l,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,stopRequestPath:u.stopRequestPath,workflowDir:p},e.prompt||null))return this.isInterrupted()?(d=this.createInterruptedResult(),d):(this.logger.info(`\n${Z}${X}Workflow skipped: agent decided nothing to do${Y}`),d={exitCode:2,output:this.outputLines.join(`
40
+ `)},d);if(m=p,v&&!t)c.worktree?.beforeCreate&&(this.logger.info(`\n ${$}${X}worktree${Y} ${X}beforeCreate${Y}`),this.logger.info(` ${`─`.repeat(50)}`),await this.worktreeService.runHook(c.worktree.beforeCreate,p,x,y,b,{runner:l,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,stopRequestPath:u.stopRequestPath})||(this.isInterrupted()?d=this.createInterruptedResult():(this.logger.error(`\n${Q}${X}Workflow aborted: worktree beforeCreate hook failed${Y}`),d={exitCode:1,output:this.outputLines.join(`
41
+ `)}))),d||(this.logger.info(`\n ${$}${X}worktree${Y} ${X}Creating worktree${Y}`),this.logger.info(` ${`─`.repeat(50)}`),h=this.worktreeService.createWorktree(p,u.displayName),p=h,x.WORKTREE_PATH=h,x.WORKFLOW_NAME=u.displayName,x.ORIGINAL_REPO_PATH=m,c.worktree?.afterCreate&&(this.logger.info(`\n ${$}${X}worktree${Y} ${X}afterCreate${Y}`),this.logger.info(` ${`─`.repeat(50)}`),await this.worktreeService.runHook(c.worktree.afterCreate,h,x,y,b,{runner:l,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,stopRequestPath:u.stopRequestPath})||(this.isInterrupted()?d=this.createInterruptedResult():(this.logger.error(`\n${Q}${X}Workflow aborted: worktree afterCreate hook failed${Y}`),d={exitCode:1,output:this.outputLines.join(`
42
+ `)}),_=!i)));else if(v&&t){let e=(0,s.dirname)(p),t=(0,s.basename)(p),n=u.displayName.toLowerCase().replace(/[^a-z0-9-]+/g,`-`).replace(/^-|-$/g,``),r=(0,s.resolve)(e,`${t}-worktree`,n);this.logger.info(`\n ${$}${X}worktree${Y} ${X}(dry-run)${Y} Would create at ${r}`)}!d&&c.pre&&(await this.runPreBlock(c.pre,x,y,b,{runner:l,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,stopRequestPath:u.stopRequestPath,workflowDir:p})||(this.isInterrupted()?d=this.createInterruptedResult():(this.logger.error(`\n${Q}${X}Workflow aborted: pre-conditions failed${Y}`),d={exitCode:1,output:this.outputLines.join(`
43
+ `)}),_=!!(h&&!i)));let ne=Number(x.MAX_RETRIES)||3,E=this.parser.resolveJobOrder(c.jobs,e.job||null);this.logger.info(`\n ${Gt}Job order: ${E.join(` -> `)} (max retries: ${ne})${Y}`);let re=new Set,D=x.WORKFLOW_RUN_DIR?(0,s.resolve)(x.WORKFLOW_RUN_DIR,`fix.md`):null,O=0,k=0;for(;!d&&k<E.length;){let r=E[k],i=c.jobs[r],o=await this.jobRunner.runJob(r,i,{inputs:y,secrets:b,workflowEnv:x,workflowSystemPrompt:c[`system-prompt`],jobOrder:E,allJobs:c.jobs},{runner:l,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,stopRequestPath:u.stopRequestPath,workflowDir:p,maxRetries:ne});if(this.isInterrupted()&&(d=this.createInterruptedResult()),!d&&!o&&(this.logger.error(`\n${Q}${X}Workflow failed at job "${r}"${Y}`),await this.serviceManager.stopAll(),d={exitCode:1,output:this.outputLines.join(`
44
+ `)}),!d){if(re.add(r),D&&await this.pathExists(D)&&!t){if(O++,O>3){this.logger.error(`\n${Q}${X}Fix loop limit reached (3 cycles). Aborting.${Y}`),d={exitCode:1,output:this.outputLines.join(`
45
+ `)};continue}let e=(await(0,a.readFile)(D,`utf-8`)).trim();await(0,a.unlink)(D);let{restartFrom:t,invalidTarget:n}=this.parseFixRestartFrom(e,E);if(this.logger.info(`\n${Z}${X}fix.md detected after "${r}" (cycle ${O}/3)${Y}`),this.logger.info(`${Gt}${e.split(`
46
+ `)[0].slice(0,100)}${e.includes(`
47
+ `)?`...`:``}${Y}`),n&&this.logger.warn(`${Z}fix.md restart-from "${n}" is not a valid job (available: ${E.join(`, `)}), falling back to default${Y}`),x.CONTEXT_FILE&&await this.pathExists(x.CONTEXT_FILE)){let t=await(0,a.readFile)(x.CONTEXT_FILE,`utf-8`);await(0,a.writeFile)(x.CONTEXT_FILE,`${t}\n\n---\n## Fix Request (cycle ${O})\n${e}`,`utf-8`)}let i=t?E.indexOf(t):-1,o=E.indexOf(`development`);k=i>=0?i:o>=0?o:1,this.logger.info(`${Z}Restarting from job "${E[k]}"${Y}\n`);continue}k++}}!d&&this.isInterrupted()&&(d=this.createInterruptedResult()),d||=(g=!0,this.logger.info(`\n${X}${`═`.repeat(60)}${Y}`),this.logger.info(` ${Kt}${X}Workflow completed successfully${Y}`),this.logger.info(` Jobs run: ${[...re].join(`, `)}`),h&&this.logger.info(` Worktree: ${h}`),this.logger.info(`${X}${`═`.repeat(60)}${Y}\n`),{exitCode:0,output:this.outputLines.join(`
48
+ `)})}catch(e){if(f=e,e instanceof N||this.isInterrupted())d=this.createInterruptedResult();else throw e}finally{if(g&&d?.exitCode===0&&v&&c.worktree?.beforeCompleted&&!t&&h&&(this.logger.info(`\n ${$}${X}worktree${Y} ${X}beforeCompleted${Y}`),this.logger.info(` ${`─`.repeat(50)}`),await this.worktreeService.runHook(c.worktree.beforeCompleted,h,x,y,b,{runner:l,cliAgent:e.cliAgent,dryRun:t,continueOnError:n})||(this.isInterrupted()?d=this.createInterruptedResult():this.logger.warn(`\n${Z}${X}Warning: worktree beforeCompleted hook failed${Y}`))),c.post)try{await this.runPostBlock(c.post,x,y,b,{runner:l,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,workflowDir:p})||(this.logger.error(`\n${Q}${X}${en}${Y}`),(!d||d.exitCode===0||d.exitCode===2)&&(d={exitCode:1,output:this.outputLines.join(`
49
+ `)}))}catch(e){f??=e,this.logger.error(`\n${Q}${X}${en}${Y}`),(!d||d.exitCode===0||d.exitCode===2)&&(d={exitCode:1,output:this.outputLines.join(`
50
+ `)})}g&&d?.exitCode===0&&v&&c.worktree?.afterCompleted&&!t&&!i&&(this.logger.info(`\n ${$}${X}worktree${Y} ${X}afterCompleted${Y}`),this.logger.info(` ${`─`.repeat(50)}`),await this.worktreeService.runHook(c.worktree.afterCompleted,m,x,y,b,{runner:l,cliAgent:e.cliAgent,dryRun:t,continueOnError:n})||(this.isInterrupted()?d=this.createInterruptedResult():this.logger.warn(`\n${Z}${X}Warning: worktree afterCompleted hook failed${Y}`))),_&&h&&this.worktreeService.removeWorktree(m,h),d&&={...d,output:this.outputLines.join(`
51
+ `)},u&&await this.finalizeRegistryRun(u,d,f)}return d??{exitCode:1,output:this.outputLines.join(`
52
+ `)}}async finalizeRegistryRun(e,t,n){let r=t?.exitCode===130||n instanceof N,i=t?.exitCode??(r?130:1),a=i===0||i===2?`completed`:`error`,o=i===0?`success`:i===2?`skipped`:r||this.isInterrupted()?`interrupted`:`failed`,s=n instanceof Error?n.message:i===1?this.lastMeaningfulOutputLine():void 0;await this.registry.finalizeRun(e,{errorMessage:s,exitCode:i,outcome:o,stage:a})}resolveCliPath(){let e=__dirname,t=(0,s.resolve)(`/`);for(;e!==t;){let t=(0,s.resolve)(e,`cli.cjs`);if((0,c.existsSync)(t))return t;let n=(0,s.resolve)(e,`dist`,`cli.cjs`);if((0,c.existsSync)(n))return n;e=(0,s.dirname)(e)}throw Error(`Unable to locate workflow-mcp cli.cjs from `+__dirname)}buildLaunchCliCommand(e,t){let n=[`node`,this.resolveCliPath(),`run-workflow`,t,`--skip-launch`],r=this.resolveRunner(e);if(e.job&&n.push(`--job`,e.job),r&&n.push(`--runner`,r),e.dryRun&&n.push(`--dry-run`),e.continueOnError&&n.push(`--continue-on-error`),e.keepWorktree&&n.push(`--keep-worktree`),e.prompt&&n.push(`--prompt`,`'${e.prompt.replace(/'/g,`'\\''`)}'`),e.name&&n.push(`--name`,`'${e.name}'`),e.workspace&&n.push(`--workspace`,`'${e.workspace}'`),e.secretFile&&n.push(`--secret-file`,e.secretFile),e.inputs)for(let[t,r]of Object.entries(e.inputs))n.push(`--input`,`${t}=${r}`);if(e.env)for(let[t,r]of Object.entries(e.env))n.push(`--env`,`${t}=${r}`);return n.join(` `)}lastMeaningfulOutputLine(){return[...this.outputLines].reverse().find(e=>e.trim().length>0)}async runPreBlock(e,t,n,r,i){if(this.logger.info(`\n ${$}${X}pre${Y} ${X}Pre-conditions${Y}`),this.logger.info(` ${`─`.repeat(50)}`),t.CONTEXT_FILE&&await(0,a.mkdir)((0,s.dirname)((0,s.resolve)(i.workflowDir,t.CONTEXT_FILE)),{recursive:!0}),e.services&&e.services.length>0){this.logger.info(`\n ${$}${X}services${Y}`);for(let n of e.services)if(await this.serviceManager.startService(n,t,i.workflowDir,i.dryRun)&&n[`ready-check`]&&!i.dryRun){let e=await this.serviceManager.waitForServiceReady(n,i.workflowDir,t,()=>this.interruptedSignal!==null);if(this.isInterrupted())return!1;if(!e&&!i.continueOnError)return await this.serviceManager.stopAll(),!1}!i.dryRun&&this.serviceManager.getRunningServices().length>0&&await this.waitForServiceStartup()}if(e.steps&&e.steps.length>0){this.logger.info(`\n ${$}${X}setup${Y}`);let a={inputs:n,matrix:{},secrets:r,env:t};for(let t of e.steps)if(!await this.stepRunner.runStep(t,a,i))return this.logger.error(`\n ${Q}Pre-condition step failed${Y}`),await this.serviceManager.stopAll(),!1}return this.logger.info(`\n ${Kt}Pre-conditions ready${Y}`),!0}async runPostBlock(e,t,n,r,i){this.logger.info(`\n ${$}${X}post${Y} ${X}Cleanup${Y}`),this.logger.info(` ${`─`.repeat(50)}`),await this.serviceManager.stopAll();try{if(e.services&&e.services.length>0){this.logger.info(`\n ${$}${X}services${Y}`);for(let n of e.services)if(await this.serviceManager.startService(n,t,i.workflowDir,i.dryRun)&&n[`ready-check`]&&!i.dryRun&&!await this.serviceManager.waitForServiceReady(n,i.workflowDir,t,()=>!1)&&!i.continueOnError)return!1;!i.dryRun&&this.serviceManager.getRunningServices().length>0&&await this.waitForServiceStartup()}if(e.steps&&e.steps.length>0){this.logger.info(`\n ${$}${X}cleanup${Y}`);let a={inputs:n,matrix:{},secrets:r,env:t};for(let t of e.steps)if(!await this.stepRunner.runStep(t,a,i))return this.logger.error(`\n ${Q}Post-cleanup step failed${Y}`),!1}return this.logger.info(`\n ${Kt}Cleanup complete${Y}`),!0}finally{await this.serviceManager.stopAll()}}};const nn=n.z.object({workflowPath:n.z.string().describe(`Path to the workflow YAML file (e.g., .github/workflows/deploy.yml)`),runner:n.z.string().optional().describe(`Preferred runner key for step command maps (e.g. ollama, claude, codex)`),cliAgent:n.z.string().optional().describe(`Deprecated alias for runner. Preferred runner command key for step command maps.`),job:n.z.string().optional().describe(`Run only this job (and its dependencies)`),inputs:n.z.record(n.z.string(),n.z.string()).optional().describe(`Workflow dispatch inputs as key-value pairs`),env:n.z.record(n.z.string(),n.z.string()).optional().describe(`Extra environment variables as key-value pairs`),secretFile:n.z.string().optional().describe(`Path to a dotenv-style secrets file`),dryRun:n.z.boolean().optional().describe(`Print steps without executing`),continueOnError:n.z.boolean().optional().describe(`Continue past step failures`),keepWorktree:n.z.boolean().optional().describe(`Keep worktree on completion (skip merge and cleanup for retry)`),prompt:n.z.string().optional().describe(`User prompt for user_prompt trigger workflows`),name:n.z.string().optional().describe(`Name for the workflow run context directory`),workspace:n.z.string().optional().describe(`Workspace for workflow registry storage`)});var rn=class e{static TOOL_NAME=`run_workflow`;constructor(e=new tn){this.service=e}getInputSchema(){return nn}getDefinition(){return{name:e.TOOL_NAME,description:`Run a GitHub Actions workflow file locally. Parses the workflow YAML and executes run steps on the host machine, respecting job dependencies, matrix strategies, environment variables, and workflow_dispatch inputs.`,inputSchema:n.z.toJSONSchema(nn)}}async execute(e){try{let t=nn.parse(e);if(t.runner&&t.cliAgent&&t.runner!==t.cliAgent)throw Error(`Conflicting runner selectors: runner="${t.runner}" cliAgent="${t.cliAgent}"`);let n={cliAgent:t.cliAgent,runner:t.runner??t.cliAgent,workflowPath:t.workflowPath,job:t.job,inputs:t.inputs,env:t.env,secretFile:t.secretFile,dryRun:t.dryRun,continueOnError:t.continueOnError,keepWorktree:t.keepWorktree,prompt:t.prompt,name:t.name,workspace:t.workspace},r=await this.service.run(n);return r.exitCode!==0&&r.exitCode!==2?{content:[{type:`text`,text:`Workflow failed (exit code ${r.exitCode}):\n\n${r.output}`}],isError:!0}:{content:[{type:`text`,text:r.output||`Workflow completed successfully.`}]}}catch(e){return{content:[{type:`text`,text:`Error: ${e instanceof Error?e.message:`Unknown error`}`}],isError:!0}}}},an=class e{static TOOL_NAME=`schedule-cron`;constructor(e=new D){this.service=e}getInputSchema(){return w}getDefinition(){return{name:e.TOOL_NAME,description:`Schedule a headless Claude Code or Codex CLI run as a system cron job. Specify either a cron expression or an interval in minutes.`,inputSchema:n.z.toJSONSchema(w)}}async execute(t){try{let e=w.parse(t),n=await this.service.schedule(e);return{content:[{type:`text`,text:JSON.stringify(n,null,2)}]}}catch(n){let r=new g(`Failed to schedule cron job.`,`SCHEDULE_CRON_TOOL_FAILED`,{tool:e.TOOL_NAME,name:t.name,cwd:t.cwd},{cause:n});return console.error(`[${e.TOOL_NAME}] ${r.message}`,r.context),{content:[{type:`text`,text:JSON.stringify({code:r.code,context:r.context,message:r.message},null,2)}],isError:!0}}}};const on=n.z.object({reason:n.z.string().optional().describe(`Optional stop reason to record.`),runKey:n.z.string().min(1).describe(`Run key of the running workflow to stop.`),workspace:n.z.string().optional().describe(`Workspace containing the running workflow. Defaults to default.`)});var sn=class e{static TOOL_NAME=`stop_workflow`;constructor(e=new Ae){this.registry=e}getInputSchema(){return on}getDefinition(){return{name:e.TOOL_NAME,description:`Request a running workflow to stop gracefully without taking over terminal stdin.`,inputSchema:n.z.toJSONSchema(on)}}async execute(e){try{let t=on.parse(e),n=await this.registry.requestStop(t.workspace,t.runKey,t.reason);return{content:[{type:`text`,text:JSON.stringify({reason:n.reason,requestedAt:n.requestedAt,runKey:t.runKey,workspace:this.registry.resolveWorkspace(t.workspace)},null,2)}]}}catch(e){return{content:[{type:`text`,text:`Failed to request workflow stop. ${e instanceof Error?e.message:`Unknown error`}`}],isError:!0}}}};const cn=[rn.TOOL_NAME,Me.TOOL_NAME,an.TOOL_NAME,O.TOOL_NAME,sn.TOOL_NAME];function ln(){let n=new e.Server({name:`workflow-mcp`,version:`0.1.0`},{capabilities:{tools:{}}}),r=new rn,i=new Me,a=new an,o=new O,s=new sn;return n.setRequestHandler(t.ListToolsRequestSchema,async()=>({tools:[r.getDefinition(),i.getDefinition(),a.getDefinition(),o.getDefinition(),s.getDefinition()]})),n.setRequestHandler(t.CallToolRequestSchema,async e=>{let{name:t,arguments:n}=e.params;if(t===rn.TOOL_NAME)return await r.execute(n);if(t===Me.TOOL_NAME)return await i.execute(n);if(t===an.TOOL_NAME)return await a.execute(n);if(t===O.TOOL_NAME)return await o.execute();if(t===sn.TOOL_NAME)return await s.execute(n);throw new h(t,cn)}),n}var un=class{server;transport=null;constructor(e){this.server=e}async start(){this.transport=new m.StdioServerTransport,await this.server.connect(this.transport),console.error(`workflow-mcp MCP server started on stdio`)}async stop(){this.transport&&=(await this.transport.close(),null)}};Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return Ae}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return w}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return Xe}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return ln}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return D}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return tn}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return`claude`}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return un}});
@@ -0,0 +1,52 @@
1
+ import{Server as e}from"@modelcontextprotocol/sdk/server/index.js";import{CallToolRequestSchema as t,ListToolsRequestSchema as n}from"@modelcontextprotocol/sdk/types.js";import{z as r}from"zod";import{execFile as i,execFileSync as a,execSync as o,spawn as s}from"node:child_process";import{promisify as c}from"node:util";import{access as l,mkdir as u,readFile as d,readdir as f,rename as p,rm as m,unlink as h,writeFile as g}from"node:fs/promises";import{homedir as _,tmpdir as ee}from"node:os";import v,{basename as te,dirname as y,join as b,resolve as x}from"node:path";import{fileURLToPath as S}from"node:url";import{existsSync as C,mkdirSync as w,readFileSync as T,rmSync as ne,unlinkSync as re,watch as ie,writeFileSync as ae}from"node:fs";import{PortRegistryService as oe}from"@agimon-ai/foundation-port-registry";import{ProcessRegistryService as E}from"@agimon-ai/foundation-process-registry";import{randomUUID as se}from"node:crypto";import{createInterface as D}from"node:readline";import{load as O}from"js-yaml";import{StdioServerTransport as k}from"@modelcontextprotocol/sdk/server/stdio.js";var ce=class extends Error{code=`WORKFLOW_TOOL_NOT_FOUND`;constructor(e,t){super(`Unknown tool "${e}". Available tools: ${t.join(`, `)}`),this.toolName=e,this.availableTools=t,this.name=`WorkflowToolNotFoundError`}},le=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`WorkflowToolError`}},ue=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`CronError`}};const de=`# workflow-mcp:cron:`,fe=`crontab`,pe=`claude`,me=`codex`,he=pe,ge=`--cwd`,_e=`CRON_WRITE_FAILED`,ve=r.enum([pe,me]),ye=r.object({name:r.string().min(1),cwd:r.string().min(1),cli:ve,schedule:r.string().min(1),prompt:r.string().optional(),promptFile:r.string().optional(),createdAt:r.string().min(1)}),be=r.object({name:r.string().min(1),cwd:r.string().min(1),cli:ve.optional(),prompt:r.string().optional(),promptFile:r.string().optional(),schedule:r.string().optional(),intervalMinutes:r.number().positive().optional()});function xe(e){return`'${e.replace(/'/g,`'\\''`)}'`}function Se(e,t,n,r){let i=n?xe(n):r?`"$(cat ${xe(r)})"`:void 0;if(e===`codex`){let e=[me,`--approval-mode`,`full-auto`];return i&&e.push(`-q`,i),e.push(ge,xe(t)),e.join(` `)}let a=[pe,`--dangerously-skip-permissions`];return i&&a.push(`-p`,i),a.push(ge,xe(t)),a.join(` `)}function Ce(e){if(e<=0)throw Error(`Interval must be a positive number of minutes`);if(e<60)return`*/${e} * * * *`;let t=Math.floor(e/60);return t<24?`0 */${t} * * *`:`0 0 */${Math.floor(t/24)} * *`}const we=c(i);var Te=class{logger;constructor(e){this.logger=e??{info:e=>process.stdout.write(`${e}\n`),error:e=>console.error(e),warn:e=>console.error(e)}}async readCrontab(){try{let{stdout:e}=await we(fe,[`-l`]);return e}catch(e){let t=e.code;if(t===`ENOENT`||(e.stderr??``).includes(`no crontab for`))return``;throw this.logger.error(`[CronService.readCrontab] failed exitCode=${t}`),new ue(`Failed to read current crontab.`,`CRON_READ_FAILED`,{exitCode:t},{cause:e})}}async writeCrontab(e){let t=``,n=i(fe,[`-`]);n.stderr?.on(`data`,e=>{t+=e}),n.stdin?.write(e),n.stdin?.end(),await new Promise((r,i)=>{n.on(`close`,n=>{n===0?r():(this.logger.error(`[CronService.writeCrontab] failed exitCode=${n} stderr=${t}`),i(new ue(`crontab rejected input (exit ${n}).`,_e,{exitCode:n,stderr:t,contentLength:e.length})))}),n.on(`error`,e=>{this.logger.error(`[CronService.writeCrontab] spawn failed: ${e.message}`),i(new ue(`Failed to spawn crontab process.`,_e,{},{cause:e}))})})}async schedule(e){let t=e.cli??`claude`;if(!e.schedule&&!e.intervalMinutes)throw new ue(`Either schedule (cron expression) or intervalMinutes must be provided.`,`CRON_INVALID_INPUT`,{name:e.name});let n=e.schedule??Ce(e.intervalMinutes),r=Se(t,e.cwd,e.prompt,e.promptFile),i=new Date().toISOString(),a=ye.parse({name:e.name,cwd:e.cwd,cli:t,schedule:n,prompt:e.prompt,promptFile:e.promptFile,createdAt:i}),o=await this.readCrontab(),s=this.removeCronBlock(o,e.name),c=`${`${de}${e.name}`}\n${`# workflow-mcp:meta:${JSON.stringify(a)}`}\n${`${n} ${r}`}`,l=s.trimEnd()?`${s.trimEnd()}\n${c}\n`:`${c}\n`;return await this.writeCrontab(l),this.logger.info(`Scheduled cron "${e.name}" [${n}]`),a}async list(){let e=await this.readCrontab(),t=[],n=e.split(`
2
+ `);for(let e=0;e<n.length;e++){let r=n[e];if(r.startsWith(`# workflow-mcp:meta:`))try{let e=r.slice(20),n=ye.parse(JSON.parse(e));t.push(n)}catch(t){this.logger.warn(`[CronService.list] skipping malformed metadata at line ${e+1}: ${t.message}`)}}return t}async remove(e){let t=await this.readCrontab(),n=this.removeCronBlock(t,e);return n===t?!1:(await this.writeCrontab(n),this.logger.info(`Removed cron "${e}"`),!0)}removeCronBlock(e,t){let n=e.split(`
3
+ `),r=[],i=`${de}${t}`,a=!1;for(let e of n){if(e===i){a=!0;continue}if(a){if(e.startsWith(`# workflow-mcp:meta:`))continue;a=!1;continue}r.push(e)}return r.join(`
4
+ `)}},Ee=class e{static TOOL_NAME=`list-crons`;constructor(e=new Te){this.service=e}getInputSchema(){return r.object({})}getDefinition(){return{name:e.TOOL_NAME,description:`List all cron jobs scheduled via workflow-mcp, showing name, schedule, CLI, cwd, and prompt.`,inputSchema:r.toJSONSchema(r.object({}))}}async execute(){try{let e=await this.service.list();return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}catch(t){let n=new le(`Failed to list cron jobs.`,`LIST_CRONS_TOOL_FAILED`,{tool:e.TOOL_NAME},{cause:t});return console.error(`[${e.TOOL_NAME}] ${n.message}`,n.context),{content:[{type:`text`,text:JSON.stringify({code:n.code,context:n.context,message:n.message},null,2)}],isError:!0}}}},De=class extends Error{code=`INVALID_WORKFLOW_RUN_RECORD`;constructor(e,t,n){super(`Invalid workflow run record at "${e}": ${t}`,n),this.recordPath=e,this.name=`InvalidWorkflowRunRecordError`}},Oe=class extends Error{code=`WORKFLOW_RUN_CONFLICT`;constructor(e,t){super(`Workflow "${t}" is already running in workspace "${e}"`),this.workspace=e,this.runKey=t,this.name=`WorkflowRunConflictError`}};const ke=r.record(r.string(),r.coerce.string()),Ae=r.object({description:r.string().optional(),required:r.boolean().optional(),default:r.string().optional(),type:r.string().optional(),options:r.array(r.string()).optional()}),je=r.record(r.string(),r.string()),Me=r.record(r.string(),r.string()),Ne=r.union([r.string(),Me]),Pe=r.object({"fail-fast":r.boolean().optional(),matrix:r.looseObject({include:r.array(je).optional()}).optional()}),A=r.object({name:r.string().optional(),uses:r.string().optional(),run:Ne.optional(),interactiveRun:Ne.optional(),"timeout-minutes":r.number().optional(),env:ke.optional(),with:r.record(r.string(),r.string()).optional(),"working-directory":r.string().optional(),if:r.string().optional(),"continue-on-error":r.boolean().optional(),"timeout-retries":r.number().optional(),id:r.string().optional()}),Fe=r.object({name:r.string(),run:r.string(),env:ke.optional(),"working-directory":r.string().optional(),"ready-check":r.union([r.string(),r.boolean()]).optional(),"ready-timeout":r.number().optional(),host:r.string().optional(),port:r.number().int().min(1).max(65535).optional(),"port-range":r.object({min:r.number().int().min(1).max(65535),max:r.number().int().min(1).max(65535)}).optional(),"service-type":r.enum([`tool`,`service`]).optional()}),Ie=r.object({services:r.array(Fe).optional(),steps:r.array(A).optional()}),Le=r.object({steps:r.array(A).optional()}),Re=r.object({"runs-on":r.string().optional(),needs:r.union([r.string(),r.array(r.string())]).optional(),extends:r.union([r.string(),r.array(r.string())]).optional(),description:r.string().optional(),strategy:Pe.optional(),steps:r.array(A),preJob:Le.optional(),postJob:Le.optional(),env:ke.optional(),if:r.string().optional(),"system-prompt":r.string().optional(),context:r.string().optional()}),ze=r.object({steps:r.array(A)}),Be=r.object({steps:r.array(A)}),Ve=r.object({beforeCreate:Be.optional(),afterCreate:Be.optional(),beforeCompleted:Be.optional(),afterCompleted:Be.optional()}),He=r.object({STOP:r.string().optional()}),Ue=r.object({name:r.string().optional(),workspace:r.string().optional(),imports:r.array(r.string()).optional(),"system-prompt":r.string().optional(),"max-workflows":r.number().int().positive().optional(),"launch-command":r.string().optional(),keybindings:He.optional(),on:r.looseObject({workflow_dispatch:r.object({inputs:r.record(r.string(),Ae).optional()}).nullable().optional(),agent_decision:ze.optional()}).optional(),env:ke.optional(),pre:Ie.optional(),post:Ie.optional(),worktree:Ve.optional(),jobs:r.record(r.string(),Re)}),We=r.enum([`running`,`completed`,`error`]),Ge=r.enum([`success`,`skipped`,`failed`,`interrupted`]),Ke=r.object({displayName:r.string(),dryRun:r.boolean(),errorMessage:r.string().optional(),exitCode:r.number().optional(),finishedAt:r.string().optional(),outcome:Ge.optional(),pid:r.number().int().positive().optional(),runKey:r.string(),stale:r.boolean().optional(),staleReason:r.string().optional(),stage:We,startedAt:r.string(),workflowPath:r.string(),workspace:r.string()});r.object({changelogPath:r.string(),contextPath:r.string(),displayName:r.string(),recordPath:r.string(),runDir:r.string(),runKey:r.string(),stopRequestPath:r.string(),workspace:r.string()});const qe=`default`,j=`running`,Je=`completed`,M=`error`,Ye=`workspaces`,N=`run.json`,Xe=`stop-request.json`,Ze=`workflow-run`,Qe=`Workflow process is no longer running`;var $e=class{constructor(e=x(_(),`.workflow-mcp`)){this.homeDir=e}resolveWorkspace(e,t){return this.slugifySegment(e||t||qe,qe)}async createRun(e){let t=this.resolveWorkspace(e.workspace,e.workflowWorkspace),n=this.slugifySegment(e.displayName,Ze);await this.ensureWorkspaceStructure(t);let r=await this.findReusableRunStage(t,n);if(r===j)throw new Oe(t,n);let i=this.getRunDirectory(t,j,n);r?(await m(i,{recursive:!0,force:!0}),await p(this.getRunDirectory(t,r,n),i),await this.removeFileIfExists(x(i,`fix.md`)),await this.removeFileIfExists(x(i,Xe))):await u(i,{recursive:!0});let a={displayName:e.displayName,dryRun:e.dryRun,runKey:n,stage:j,startedAt:new Date().toISOString(),pid:e.pid??process.pid,workflowPath:e.workflowPath,workspace:t},o=x(i,N);return await this.writeRunRecord(o,a),{changelogPath:x(i,`changelog.md`),contextPath:x(i,`context.md`),displayName:e.displayName,recordPath:o,runDir:i,runKey:n,stopRequestPath:x(i,Xe),workspace:t}}async finalizeRun(e,t){await this.ensureWorkspaceStructure(e.workspace);let n=this.getRunDirectory(e.workspace,t.stage,e.runKey),r=await this.readRunRecord(e.recordPath),i={...r,errorMessage:t.errorMessage,exitCode:t.exitCode,finishedAt:new Date().toISOString(),outcome:t.outcome,pid:r.pid,stage:t.stage};e.runDir!==n&&(await m(n,{recursive:!0,force:!0}),await p(e.runDir,n));let a=x(n,N);return await this.writeRunRecord(a,i),{...e,recordPath:a,runDir:n,stopRequestPath:x(n,Xe)}}async requestStop(e,t,n){let r=this.resolveWorkspace(e),i=this.slugifySegment(t,Ze),a=this.getRunDirectory(r,j,i),o=x(a,N);if(!await this.pathExists(o))throw Error(`Running workflow not found: ${r}/${t}`);let s=await this.inspectRunningRecord(r,i);if(s.stale)throw Error(s.staleReason??`Running workflow is stale: ${r}/${t}`);let c={reason:n?.trim()?n.trim():void 0,requestedAt:new Date().toISOString()};return await g(x(a,Xe),`${JSON.stringify(c,null,2)}\n`,`utf-8`),c}async hasStopRequest(e){return await this.pathExists(e.stopRequestPath)}async readRunRecord(e){try{return this.validateRunRecord(JSON.parse(await d(e,`utf-8`)),e)}catch(t){throw t instanceof De?t:new De(e,t instanceof Error?t.message:`Unknown parse failure`,{cause:t})}}async listRuns(e){let t=e?[this.resolveWorkspace(e)]:await this.listWorkspaceNames();return(await Promise.all(t.map(async e=>this.listWorkspaceRuns(e)))).flat().sort((e,t)=>this.compareRunRecordsByLatest(e,t))}async listRunsPage(e={}){let t=e.page??1,n=e.pageSize??20,r=await this.listRuns(e.workspace),i=r.length,a=Math.ceil(i/n),o=(t-1)*n,s=r.slice(o,o+n);return{hasNextPage:t<a,hasPreviousPage:t>1&&i>0,items:s,page:t,pageSize:n,total:i,totalPages:a}}async countRunningWorkflows(e){let t=this.getRunStageDirectory(e,j);if(!await this.pathExists(t))return 0;let n=await f(t,{withFileTypes:!0}),r=0;for(let e of n){if(!e.isDirectory())continue;let n=x(t,e.name,N);if(await this.pathExists(n))try{let e=await this.readRunRecord(n);e.pid&&this.isProcessAlive(e.pid)&&r++}catch{}}return r}async ensureWorkspaceStructure(e){await u(this.getRunStageDirectory(e,j),{recursive:!0}),await u(this.getRunStageDirectory(e,Je),{recursive:!0}),await u(this.getRunStageDirectory(e,M),{recursive:!0})}async findRunStage(e,t){for(let n of[j,Je,M])if(await this.pathExists(this.getRunDirectory(e,n,t)))return n;return null}async listWorkspaceRuns(e){let t=new Map;for(let n of[j,Je,M]){let r=this.getRunStageDirectory(e,n);if(!await this.pathExists(r))continue;let i=await f(r,{withFileTypes:!0});for(let a of i){if(!a.isDirectory())continue;let i=x(r,a.name,N);if(!await this.pathExists(i))continue;let o=await this.readListableRunRecord(e,n,a.name,i);o&&t.set(o.runKey,o)}}return[...t.values()]}async findReusableRunStage(e,t){let n=await this.findRunStage(e,t);return n===j?(await this.reconcileStaleRunningRecord(e,t),this.findRunStage(e,t)):n}async inspectRunningRecord(e,t){let n=x(this.getRunDirectory(e,j,t),N),r=await this.readRunRecord(n);return!r.pid||this.isProcessAlive(r.pid)?r:{...r,stale:!0,staleReason:`${Qe}: pid ${r.pid}`}}async readListableRunRecord(e,t,n,r){try{return t===j?await this.inspectRunningRecord(e,n):await this.readRunRecord(r)}catch(e){if(e instanceof De)return null;throw e}}async reconcileStaleRunningRecord(e,t){let n=this.getRunDirectory(e,j,t),r=x(n,N),i=await this.readRunRecord(r);if(!i.pid||this.isProcessAlive(i.pid))return i;let a=this.getRunDirectory(e,M,t),o={...i,errorMessage:`${Qe}: pid ${i.pid}`,exitCode:130,finishedAt:new Date().toISOString(),outcome:`interrupted`,stage:M};return await m(a,{recursive:!0,force:!0}),await p(n,a),await this.writeRunRecord(x(a,N),o),o}async listWorkspaceNames(){let e=x(this.homeDir,Ye);return await this.pathExists(e)?(await f(e,{withFileTypes:!0})).filter(e=>e.isDirectory()).map(e=>e.name).sort((e,t)=>e.localeCompare(t)):[]}compareRunRecordsByLatest(e,t){let n=e.finishedAt??e.startedAt,r=(t.finishedAt??t.startedAt).localeCompare(n);if(r!==0)return r;let i=e.workspace.localeCompare(t.workspace);return i===0?e.runKey.localeCompare(t.runKey):i}async writeRunRecord(e,t){await g(e,`${JSON.stringify(t,null,2)}\n`,`utf-8`)}validateRunRecord(e,t){let n=Ke.safeParse(e);if(!n.success)throw new De(t,n.error.message);return n.data}getRunStageDirectory(e,t){return x(this.homeDir,Ye,e,t)}getRunDirectory(e,t,n){return x(this.getRunStageDirectory(e,t),n)}async pathExists(e){try{return await l(e),!0}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async removeFileIfExists(e){try{await h(e)}catch(e){if(e.code!==`ENOENT`)throw e}}slugifySegment(e,t){return e.trim().toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``)||t}isProcessAlive(e){try{return process.kill(e,0),!0}catch(e){let t=e.code;if(t===`ESRCH`)return!1;if(t===`EPERM`)return!0;throw e}}};const et=r.object({page:r.number().int().min(1).optional().describe(`Page number to return. Defaults to 1.`),pageSize:r.number().int().min(1).max(100).optional().describe(`Number of workflow runs per page. Defaults to 20 and is capped at 100.`),workspace:r.string().optional().describe(`Optional workspace filter. When omitted, runs from all workspaces are returned.`)});var tt=class e{static TOOL_NAME=`list_workflow_statuses`;constructor(e=new $e){this.registry=e}getInputSchema(){return et}getDefinition(){return{name:e.TOOL_NAME,description:`List tracked workflow runs from the local workflow registry, including their workspace, stage, outcome, and timestamps.`,inputSchema:r.toJSONSchema(et)}}async execute(e={}){try{let t=et.parse(e),n=await this.registry.listRunsPage({page:t.page??1,pageSize:t.pageSize??20,workspace:t.workspace});return{content:[{type:`text`,text:JSON.stringify(n,null,2)}]}}catch(t){let n=new le(`Failed to list workflow statuses.`,`LIST_WORKFLOW_STATUSES_TOOL_FAILED`,{workspace:e.workspace??`all`},{cause:t});return{content:[{type:`text`,text:JSON.stringify({code:n.code,context:n.context,message:n.message},null,2)}],isError:!0}}}};const nt=v.dirname(S(import.meta.url));var rt=class extends Error{code=`WORKFLOW_CAPACITY_EXCEEDED`;constructor(e,t,n){super(`Workspace "${e}" is at capacity: ${t}/${n} workflows running.\n → Check running workflows: list-workflow-statuses --workspace ${e}\n → Wait for a running workflow to complete, or cancel one before dispatching again.`),this.workspace=e,this.running=t,this.max=n,this.name=`WorkflowCapacityError`}},P=class extends Error{code=`WORKFLOW_INTERRUPTED`;context;constructor(e,t={}){let n=t.context?` during ${t.context.phase}`:``;super(`Workflow interrupted by ${e}${n}`,{cause:t.cause??t.context}),this.signal=e,this.name=`WorkflowInterruptedError`,this.context=t.context}};const F=`\x1B[0m`,it=`\x1B[1m`,at=`\x1B[2m`,ot=`\x1B[31m`,st=`\x1B[34m`,ct=[`pnpm-workspace.yaml`,`nx.json`,`.git`];function lt(e){let t=x(e);for(;;){for(let e of ct)if(C(b(t,e)))return t;let n=y(t);if(n===t)return e;t=n}}var ut=class{runningServices=[];processRegistry=new E(process.env.PROCESS_REGISTRY_PATH);portRegistry=new oe(process.env.PORT_REGISTRY_PATH);constructor(e){this.logger=e}resolveWorkingDirectory(e,t){return e[`working-directory`]?x(t,e[`working-directory`]):t}getRunningServices(){return this.runningServices}async startService(e,t,n,r){let i=e.run;if(r)return this.logger.info(` ${st}dry ${F} ${e.name}`),this.logger.info(` ${at}$ ${i}${F}`),null;this.logger.info(` ${st}start${F} ${e.name}`),this.logger.info(` ${at}$ ${i}${F}`);let a={...process.env,...t};if(e.env)for(let[t,n]of Object.entries(e.env))a[t]=String(n);let o=this.resolveWorkingDirectory(e,n),c=lt(o),l=a.NODE_ENV??process.env.NODE_ENV??`development`,u=e.host??`127.0.0.1`,d;if(e.port!==void 0||e[`port-range`]){let t=await this.portRegistry.reservePort({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,preferredPort:e.port,portRange:e[`port-range`],host:u,force:!0});if(!t.success||t.port===void 0)throw Error(t.error??`Failed to reserve port for background service ${e.name}`);d=t.port,a.PORT??=String(d),a.SERVICE_PORT??=String(d),a.HOST??=u,a.SERVICE_HOST??=u}let f=s(i,[],{stdio:[`ignore`,`pipe`,`pipe`],cwd:o,env:{...a,FORCE_COLOR:`1`},shell:`/bin/zsh`,detached:!0});if(f.pid===void 0)throw d!==void 0&&await this.portRegistry.releasePort({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,force:!0}),Error(`Failed to spawn background service ${e.name}`);let p=`${st}[${e.name}]${F}`;f.stdout?.on(`data`,e=>{for(let t of e.toString().split(`
5
+ `))t.trim()&&process.stdout.write(`${p} ${t}\n`)}),f.stderr?.on(`data`,e=>{for(let t of e.toString().split(`
6
+ `))t.trim()&&process.stderr.write(`${p} ${t}\n`)}),f.on(`error`,e=>{this.logger.error(`${p} Process error: ${e.message}`)});let m=async()=>{d!==void 0&&await this.portRegistry.releasePort({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,force:!0})};if(f.pid!==void 0){let t=await this.processRegistry.registerProcess({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,pid:f.pid,port:d,host:d===void 0?void 0:u,command:i,args:[],metadata:{workingDirectory:o,readyCheck:e[`ready-check`]},force:!0});if(!t.success){try{process.kill(-f.pid,`SIGTERM`)}catch{f.kill(`SIGTERM`)}throw d!==void 0&&await this.portRegistry.releasePort({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,force:!0}),Error(t.error??`Failed to register process for background service ${e.name}`)}m=async()=>{let t=await this.processRegistry.releaseProcess({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,pid:f.pid,kill:!0,releasePort:!0,force:!0});if(!t.success&&!t.error?.includes(`No matching process entry`))throw Error(t.error??`Failed to release ${e.name}`)}}let h={name:e.name,process:f,env:a,release:m};return this.runningServices.push(h),h}async waitForServiceReady(e,t,n,r){if(!e[`ready-check`]||typeof e[`ready-check`]!=`string`)return!0;let i=(e[`ready-timeout`]||30)*1e3,a=Date.now(),s=e[`ready-check`],c=this.resolveWorkingDirectory(e,t),l=this.runningServices.find(t=>t.name===e.name)?.env??{...process.env,...n};for(this.logger.info(` ${at}wait${F} Waiting for ${e.name} to be ready...`);Date.now()-a<i;){if(r?.())return!1;try{return o(s,{stdio:`ignore`,cwd:c,env:l,shell:`/bin/zsh`,timeout:5e3}),this.logger.info(` ready${F} ${e.name}`),!0}catch{if(r?.())return!1}await new Promise(e=>{setTimeout(e,1e3)})}return r?.()||this.logger.error(` ${ot}timeout${F} ${e.name} not ready after ${e[`ready-timeout`]||30}s`),!1}async stopAll(){if(this.runningServices.length!==0){this.logger.info(`\n ${st}${it}pre${F} ${it}Stopping services${F}`),this.logger.info(` ${`─`.repeat(50)}`);for(let e of this.runningServices){if(!e.process.killed&&e.process.pid&&this.logger.info(` ${at}stop${F} ${e.name} (pid ${e.process.pid})`),e.release)try{await e.release();continue}catch(t){this.logger.warn(` ${ot}warn${F} Failed registry cleanup for ${e.name}: ${t instanceof Error?t.message:String(t)}`)}if(!e.process.killed&&e.process.pid)try{process.kill(-e.process.pid,`SIGTERM`)}catch{e.process.kill(`SIGTERM`)}}this.runningServices=[]}}};const I=`\x1B[0m`,dt=`\x1B[1m`,ft=`\x1B[2m`,pt=`\x1B[33m`,mt=`WORKFLOW_JOB_ID`,ht=`WORKFLOW_JOB_NAME`,gt=`PROCESS_REGISTRY_TAG`;var _t=class{constructor(e,t,n){this.parser=e,this.stepRunner=t,this.logger=n}async runJob(e,t,n,r){let i=this.parser.expandMatrix(t),a=r.maxRetries;for(let o of i){let i=se(),s=Object.keys(o).length>0?` ${ft}(${Object.entries(o).map(([e,t])=>`${e}=${t}`).join(`, `)})${I}`:``;this.logger.info(`\n ${dt}job${I} ${dt}${e}${I}${s}`),this.logger.info(` ${`─`.repeat(50)}`);let c={...n.workflowEnv,[mt]:i,[ht]:e,[gt]:i};if(t.env)for(let[e,r]of Object.entries(t.env))c[e]=this.parser.interpolate(String(r),{inputs:n.inputs,matrix:o,secrets:n.secrets,env:c});c[mt]=i,c[ht]=e,c[gt]=i,this.logger.info(` ${ft}jobid${I} ${i}`);let l=[];if(c.WORKFLOW_RUN_DIR){let t=n.jobOrder.map(e=>{let t=n.allJobs[e]?.description;return t?` - ${e}: ${t}`:` - ${e}`}).join(`
7
+ `);l.push(`You are currently running job "${e}" in the following workflow pipeline:\n${t}\n\nIf you find bugs, missing features, or issues that need fixes from a previous job, write to ${c.WORKFLOW_RUN_DIR}/fix.md with a YAML frontmatter restart-from field specifying which job should handle the fix, followed by a detailed description. Example:\n---\nrestart-from: development\n---\nDescription of what needs to be fixed...\n\nThe workflow runner will restart from the specified job. Only create fix.md for issues that require code changes — fix minor issues yourself.`)}if(n.workflowSystemPrompt&&l.push(this.parser.interpolate(n.workflowSystemPrompt,{inputs:n.inputs,matrix:o,secrets:n.secrets,env:c})),t[`system-prompt`]&&l.push(this.parser.interpolate(t[`system-prompt`],{inputs:n.inputs,matrix:o,secrets:n.secrets,env:c})),l.length>0){let e=l.join(`
8
+
9
+ `);c.JOB_SYSTEM_PROMPT=e,this.logger.info(` ${ft}prompt${I} ${e.split(`
10
+ `)[0].slice(0,80)}${e.includes(`
11
+ `)?`...`:``}`)}let u=t.context?this.parser.interpolate(t.context,{inputs:n.inputs,matrix:o,secrets:n.secrets,env:c}):c.CONTEXT_FILE;if(u){let e=x(r.workflowDir,u);C(e)?(c.WORKFLOW_CONTEXT=e,c.WORKFLOW_CONTEXT_FILE=e,this.logger.info(` ${ft}ctx ${I} ${u}`)):r.dryRun||this.logger.warn(` ${pt}warn${I} context file not found: ${u}`)}if(c.CHANGELOG_FILE){let e=x(r.workflowDir,c.CHANGELOG_FILE),t=y(e);C(t)||w(t,{recursive:!0}),C(e)||ae(e,`# Changelog
12
+ `,`utf-8`),c.WORKFLOW_CHANGELOG=e,this.logger.info(` ${ft}log ${I} changelog at ${c.CHANGELOG_FILE}`)}let d={inputs:n.inputs,matrix:o,secrets:n.secrets,env:c},f=!1,p=t.preJob?.steps??[],m=t.steps??[],h=t.postJob?.steps??[];for(let t=1;t<=a;t++){t>1&&(this.logger.info(`\n ${pt}retry${I} ${dt}${e}${I}${s} (attempt ${t}/${a})`),this.logger.info(` ${`─`.repeat(50)}`));let n=!0;if(p.length>0&&(n=await this.runStepSequence(p,d,r)),n&&=await this.runStepSequence(m,d,r),h.length>0){let e=await this.runStepSequence(h,d,r);n&&=e}if(n){f=!0;break}t<a&&this.logger.warn(` ${pt}Job "${e}" failed, retrying...${I}`)}if(f)this.logger.info(` Job "${e}" completed${I}${s}`);else if(this.logger.error(`\n Job "${e}" failed after ${a} attempts${I}`),t.strategy?.[`fail-fast`]!==!1)return!1}return!0}async runStepSequence(e,t,n){for(let r of e)if(!await this.stepRunner.runStep(r,t,n))return!1;return!0}},vt=class extends Error{code=`WORKFLOW_STEP_SPAWN_FAILED`;constructor(e,t={}){super(`Failed to start workflow step "${e.stepName}"`,{cause:t.cause??e}),this.context=e,this.name=`WorkflowStepSpawnError`}},yt=class extends Error{code=`WORKFLOW_STEP_TIMEOUT_CONFIG_INVALID`;constructor(e,t={}){super(`Invalid ${e.configKey} for workflow step "${e.stepName}"`,{cause:t.cause??e}),this.context=e,this.name=`WorkflowStepTimeoutConfigError`}};const bt=`/backend-api`;var xt=class{codexHome;fetchFn;now;readTextFile;constructor(e={}){this.codexHome=e.codexHome??b(_(),`.codex`),this.fetchFn=e.fetchFn??fetch,this.now=e.now??(()=>Date.now()),this.readTextFile=e.readTextFile??(e=>d(e,`utf8`))}isCodexCommand(e,t){let n=e.trim();return n===`codex`||n.startsWith(`codex `)}async getQuotaStatus(){let e=await this.readAuthFile(),t=e?.tokens?.access_token?.trim(),n=e?.tokens?.account_id?.trim();if(!t||!n)return null;let r=await this.readChatgptBaseUrl(),i=await this.fetchFn(this.buildUsageUrl(r),{headers:{Authorization:`Bearer ${t}`,"ChatGPT-Account-Id":n,"User-Agent":`codex-cli`}});if(!i.ok)throw Error(`codex quota request failed with HTTP ${i.status}`);let a=await i.json();return{blockingLimit:this.findBlockingLimit(a),planType:a.plan_type??null}}async readAuthFile(){let e=await this.readOptionalTextFile(b(this.codexHome,`auth.json`));return e?JSON.parse(e):null}async readChatgptBaseUrl(){let e=(await this.readOptionalTextFile(b(this.codexHome,`config.toml`)))?.match(/^\s*chatgpt_base_url\s*=\s*"([^"]+)"/m);return this.normalizeBaseUrl(e?.[1]??`https://chatgpt.com/backend-api/`)}buildUsageUrl(e){return e.includes(bt)?`${e}/wham/usage`:`${e}/api/codex/usage`}normalizeBaseUrl(e){let t=e.trim().replace(/\/+$/,``);return(t.startsWith(`https://chatgpt.com`)||t.startsWith(`https://chat.openai.com`))&&!t.includes(bt)&&(t=`${t}${bt}`),t}async readOptionalTextFile(e){try{return await this.readTextFile(e)}catch(e){if(e.code===`ENOENT`)return null;throw e}}findBlockingLimit(e){let t=[];this.collectBlockingLimit(t,`codex`,`codex`,e.rate_limit);for(let n of e.additional_rate_limits??[])this.collectBlockingLimit(t,n.metered_feature??n.limit_name??`codex`,n.limit_name??n.metered_feature??`codex`,n.rate_limit);return t.sort((e,t)=>e.resetAt-t.resetAt||t.usedPercent-e.usedPercent),t[0]??null}collectBlockingLimit(e,t,n,r){if(!r)return;let i=[{payload:r.primary_window,window:`primary`},{payload:r.secondary_window,window:`secondary`}];for(let{payload:a,window:o}of i){let i=a?.reset_at,s=a?.used_percent??0;typeof i==`number`&&(s>=100||r.limit_reached===!0&&i*1e3>this.now()||r.allowed===!1&&i*1e3>this.now())&&e.push({limitId:t,limitName:n,resetAfterSeconds:a?.reset_after_seconds??null,resetAt:i,usedPercent:s,window:o,windowSeconds:a?.limit_window_seconds??null})}}};const L=`\x1B[0m`,R=`\x1B[2m`,St=`\x1B[36m`,Ct=`\x1B[32m`,z=`\x1B[33m`,B=`\x1B[31m`,wt=`SIGINT`,V=`SIGTERM`,Tt=`unnamed`,Et=`/bin/zsh`,Dt=`FORCE_COLOR`,H=`inherit`,Ot=`pipe`,kt=` `,At=`
13
+ ${kt}\$ `,jt=`skip`,U=`warn`,W=`error`,G=`fail`,K=`interrupted`,Mt=`already stopping`,Nt=`failed to signal`,q=`(continue-on-error)`,Pt=`ESRCH`,Ft=process.platform!==`win32`,It=`interactiveRun`,Lt=`timeout-minutes`,Rt=`timeout-retries`,zt=`retry`,Bt=`SIGKILL`,Vt=1e3,Ht=6e4,Ut=Math.floor(2147483647/Ht),Wt=`WORKFLOW_STEP_DISPLAY`,Gt=`workflow-mcp`,Kt={status:`status_completed`},qt=new Set([`ENOENT`,`EBUSY`,`EAGAIN`,`EPERM`]),Jt=1e3,Yt=`done`,Xt=`stop`,Zt=1e3,Qt=`stop_requested`,$t=r.number().positive().finite().max(Ut),en=r.number().int().min(0).max(10).default(2);var tn=class{activeStep=null;activeQuotaWait=null;constructor(e,t,n=new xt,r=ie){this.parser=e,this.logger=t,this.quotaService=n,this.watchStatusFile=r}stopActiveStep(e){if(this.activeQuotaWait){if(this.activeQuotaWait.controller.signal.aborted){this.logger.info(` ${R}${jt}${L} ${this.activeQuotaWait.stepName} ${Mt}`);return}this.activeQuotaWait.signal=e,this.activeQuotaWait.controller.abort(),this.logger.info(` ${z}${K}${L} cancelling quota wait for ${this.activeQuotaWait.stepName}`);return}if(!this.activeStep){this.logger.info(` ${R}${jt}${L} no active step to stop`);return}if(this.activeStep.process.killed){this.logger.info(` ${R}${jt}${L} ${this.activeStep.stepName} ${Mt}`);return}this.logger.info(` ${z}${K}${L} sending ${e} to ${this.activeStep.stepName}`),this.killActiveStep(e)||this.logger.warn(` ${z}${U}${L} ${Nt} ${this.activeStep.stepName}`)}async runStep(e,t,n){let r=this.resolveRunner(n),i=this.resolveStepCommand(e,t,r),a=e.name?this.parser.interpolate(e.name,t):i?.command.slice(0,60)||e.uses||Tt;if(e.uses){let n=this.parser.interpolate(e.uses,t);return this.parser.isActionSkipped(n)?(this.logger.info(` ${R}${jt}${L} ${n}`),!0):(this.logger.warn(` ${z}${U}${L} Unsupported action: ${n} (skipped)`),!0)}if(!i)return!0;if(i.missingRunner)return this.logger.error(` ${B}${W}${L} runner "${i.missingRunner.runner}" not found for step "${a}" (available runner keys: ${i.missingRunner.availableKeys.join(`, `)})`),e[`continue-on-error`]||n.continueOnError?(this.logger.warn(` ${z}${G}${L} ${a} ${q}\n`),!0):(this.logger.error(` ${B}${G}${L} ${a}\n`),!1);let{command:o,interactive:s}=i,c=this.quotaService.isCodexCommand(o,i.agentKey),l=e[`continue-on-error`]||n.continueOnError,u;try{u=this.resolveTimeoutMs(e,a)}catch(e){if(!(e instanceof yt))throw e;return this.logger.error(` ${B}${W}${L} invalid ${e.context.configKey} for "${a}": ${String(e.context.timeoutValue)}`),l?(this.logger.warn(` ${z}${G}${L} ${a} ${q}\n`),!0):(this.logger.error(` ${B}${G}${L} ${a}\n`),!1)}let d={...process.env,...t.env};if(e.env)for(let[n,r]of Object.entries(e.env))d[n]=this.parser.interpolate(String(r),t);d.WORKFLOW_STEP_NAME=a,d[Wt]=this.formatStepDisplay(d.WORKFLOW_JOB_NAME,a);let f;s&&(f=this.createStatusFile(),d.WORKFLOW_STATUS_FILE=f,this.logger.info(` ${R}status-file${L} ${f}`));let p=e[`working-directory`]?x(n.workflowDir,this.parser.interpolate(e[`working-directory`],t)):n.workflowDir;if(n.dryRun)return this.logger.info(` ${St}dry ${L} ${a}`),this.logger.info(`${kt}${R}\$ ${o.split(`
14
+ `).join(At)}${L}`),!0;let m;try{m=this.resolveTimeoutRetries(e,a)}catch(e){if(!(e instanceof yt))throw e;return this.logger.error(` ${B}${W}${L} invalid ${e.context.configKey} for "${a}": ${String(e.context.timeoutValue)}`),l?(this.logger.warn(` ${z}${G}${L} ${a} ${q}\n`),!0):(this.logger.error(` ${B}${G}${L} ${a}\n`),!1)}let h=!1;for(let e=0;e<=m;e++){if(await this.waitForCodexQuotaAvailability(a,o,c),e>0?this.logger.warn(` ${z}${zt}${L} retrying step after timeout "${a}" (attempt ${e+1}/${m+1})`):this.logger.info(` ${St}run ${L} ${a}`),this.logger.info(`${kt}${R}\$ ${o.split(`
15
+ `)[0]}${o.includes(`
16
+ `)?` ...`:``}${L}`),n.stopRequestPath&&C(n.stopRequestPath))throw this.logger.info(`\n ${z}${K}${L} ${a} (${Xt})`),new P(V,{cause:Error(`Workflow stop requested before "${a}"`),context:this.createInterruptContext(a,o)});let t=s?this.withInteractiveTerminalTitle(o,d):o,r=this.executeCommand(t,p,d,a,s,u),i=s&&f?await this.raceInteractiveCompletion(r,f,a,n.stopRequestPath):n.stopRequestPath?await this.raceStopRequest(r,a,n.stopRequestPath):await r;if(i.status===`stop_requested`)throw this.logger.info(`\n ${z}${K}${L} ${a} (${Xt})`),new P(V,{cause:Error(`Workflow stop requested during "${a}"`),context:this.createInterruptContext(a,o)});if(i.status===`signaled`)throw this.logger.info(`\n ${z}${K}${L} ${a}`),new P(i.signal,{cause:Error(`Step process ended with ${i.signal}`),context:this.createInterruptContext(a,o)});if(i.status===`completed`&&(i.exitCode===130||i.exitCode===143)){let e=i.exitCode===130?wt:V;throw this.logger.info(`\n ${z}${K}${L} ${a}`),new P(e,{cause:Error(`Step process exited with ${i.exitCode}`),context:this.createInterruptContext(a,o)})}if(i.status===`spawn_error`)return this.logger.error(` ${B}${W}${L} unable to start step "${a}" in ${i.error.context.workDir} (${this.formatSpawnError(i.error)})`),l?(this.logger.warn(` ${z}${G}${L} ${a} ${q}\n`),!0):(this.logger.error(` ${B}${G}${L} ${a}\n`),!1);if(i.status===`timed_out`){if(this.logger.error(` ${B}${W}${L} step timed out after ${this.formatTimeoutMs(i.timeoutMs)} "${a}" (${o.split(`
17
+ `)[0]})`),e<m){if(f)try{ae(f,``,`utf-8`)}catch(e){this.logger.warn(` ${z}${U}${L} status-file reset failed for ${a}: ${e instanceof Error?e.message:String(e)}`)}continue}return f&&this.cleanupStatusFile(f),l?(this.logger.warn(` ${z}${G}${L} ${a} ${q}\n`),!0):(this.logger.error(` ${B}${G}${L} ${a}\n`),!1)}if(f&&this.cleanupStatusFile(f),i.status===`status_completed`)return this.logger.info(`\n ${Ct}${Yt}${L} ${a} (status-file signaled completion)\n`),!0;if(i.status===`completed`&&i.exitCode===0)return this.logger.info(`\n ${Ct}pass${L} ${a}\n`),!0;if(i.status===`completed`&&this.logger.error(` ${B}${W}${L} step "${a}" exited with code ${i.exitCode} (${o.split(`
18
+ `)[0]})`),!h&&await this.shouldRetryForCodexQuota(a,c)){h=!0,this.logger.warn(` ${z}${zt}${L} codex quota reached after failed step, retrying after reset`),--e;continue}return l?(this.logger.warn(` ${z}${G}${L} ${a} ${q}\n`),!0):(this.logger.error(` ${B}${G}${L} ${a}\n`),!1)}return!1}async executeCommand(e,t,n,r,i,a){return i&&this.logger.info(` ${R}(interactive: output renders directly to terminal)${L}`),await new Promise(o=>{let c=i?this.captureTerminalState():null,l=i?s(e,[],{stdio:[H,H,H],cwd:t,detached:!1,env:{...n,[Dt]:`1`},shell:Et}):s(e,[],{stdio:[H,Ot,Ot],cwd:t,detached:Ft,env:{...n,[Dt]:`1`},shell:Et});this.activeStep={command:e,process:l,processGroupId:Ft&&!i&&typeof l.pid==`number`?l.pid:null,stepName:r};let u=null,d=!1,f=!1,p=e=>{f||(f=!0,m&&clearTimeout(m),u&&clearTimeout(u),this.activeStep=null,i&&this.restoreTerminalState(c),o(e))},m=a===null?null:setTimeout(()=>{if(d=!0,this.killActiveStep(V)){u=setTimeout(()=>{f||(this.logger.warn(` ${z}${U}${L} ${r} forcing SIGKILL`),this.killActiveStep(Bt))},Vt);return}p({status:`timed_out`,timeoutMs:a})},a);l.stdout?.on(`data`,e=>{for(let t of e.toString().split(`
19
+ `))t&&this.logger.info(t)}),l.stderr?.on(`data`,e=>{for(let t of e.toString().split(`
20
+ `))t&&this.logger.error(t)}),l.on(`error`,n=>{p({status:`spawn_error`,error:new vt({commandPreview:e.split(`
21
+ `)[0],source:`StepRunnerService.executeCommand`,stepName:r,workDir:t},{cause:n})})}),l.on(`exit`,(e,t)=>{if(this.cleanupProcessGroup(),d&&a!==null){p({status:`timed_out`,timeoutMs:a});return}if(t===wt){p({status:`signaled`,signal:wt});return}if(t===V){p({status:`signaled`,signal:V});return}p({status:`completed`,exitCode:e??1})})})}createInterruptContext(e,t){return{phase:`step_execution`,stepName:e,commandPreview:t.split(`
22
+ `)[0],source:`StepRunnerService.runStep`}}formatSpawnError(e){let t=e.cause;return t instanceof Error?`${`code`in t&&typeof t.code==`string`?t.code:`unknown-spawn-error`}${`path`in t&&typeof t.path==`string`?` at ${t.path}`:``}; verify command, shell, and permissions`:`check shell availability, PATH, and permissions`}resolveStepCommand(e,t,n){let r=e[It]!=null,i=r?{command:e[It],interactive:!0}:{command:e.run,interactive:!1},a=r?{command:e.run,interactive:!1}:{command:e[It],interactive:!0},o=this.resolveExactAgentCommand(i.command,t,n);if(o)return{...o,interactive:i.interactive};let s=this.resolveExactAgentCommand(a.command,t,n);if(s)return{...s,interactive:a.interactive};let c=this.resolveMissingExplicitRunner(e,n);if(c)return{command:``,interactive:!1,missingRunner:c};let l=this.resolveStringCommand(i.command,t);if(l)return{command:l,interactive:i.interactive};let u=this.resolveStringCommand(a.command,t);if(u)return{command:u,interactive:a.interactive};let d=this.resolveFirstMappedCommand(i.command,t);if(d)return{command:d,interactive:i.interactive};let f=this.resolveFirstMappedCommand(a.command,t);return f?{command:f,interactive:a.interactive}:null}resolveExactAgentCommand(e,t,n){if(!n||!e||typeof e==`string`)return null;let r=e[n];return r?{agentKey:n,command:this.parser.interpolate(r,t),interactive:!1}:null}resolveStringCommand(e,t){return!e||typeof e!=`string`?null:this.parser.interpolate(e,t)}resolveFirstMappedCommand(e,t){if(!e||typeof e==`string`)return null;let[n]=Object.values(e);return n?this.parser.interpolate(n,t):null}resolveMissingExplicitRunner(e,t){if(!t)return null;let n=this.collectMappedRunnerKeys(e);return n.length===0||n.includes(t)?null:{availableKeys:n,runner:t}}collectMappedRunnerKeys(e){let t=new Set;for(let n of[e[It],e.run])if(n&&typeof n!=`string`)for(let e of Object.keys(n))t.add(e);return[...t]}resolveRunner(e){return e.runner??e.cliAgent}formatStepDisplay(e,t){return e?`${e} > ${t}`:t}withInteractiveTerminalTitle(e,t){let n=`${Gt}: ${t[Wt]??e.split(`
23
+ `)[0]??Tt}`,r=`[ -t 1 ] && printf '\\033]0;%s\\007' ${this.escapeShellArg(n)}`,i=`[ -t 1 ] && printf '\\033]0;%s\\007' ${this.escapeShellArg(Gt)}`;return`trap ${this.escapeShellArg(i)} EXIT
24
+ ${r}
25
+ ${e}`}escapeShellArg(e){return`'${e.replace(/'/g,`'\\''`)}'`}async shouldRetryForCodexQuota(e,t){return t?(await this.readCodexQuotaStatus(e))?.blockingLimit!=null:!1}async waitForCodexQuotaAvailability(e,t,n){if(n)for(;;){let n=await this.readCodexQuotaStatus(e),r=n?.blockingLimit;if(!r)return;let i=r.resetAt*1e3+Zt,a=Math.max(i-Date.now(),Zt),o=new Date(i).toLocaleString(),s=n?.planType?` (${n.planType})`:``;this.logger.warn(` ${z}wait ${L} codex quota ${r.limitName}/${r.window} is at ${r.usedPercent}%${s}; waiting until ${o}`),await this.waitForQuotaDelay(e,t,a)}}async readCodexQuotaStatus(e){try{return await this.quotaService.getQuotaStatus()}catch(t){return this.logger.warn(` ${z}${U}${L} unable to read codex quota for "${e}" (${this.formatQuotaError(t)})`),null}}async waitForQuotaDelay(e,t,n){let r=new AbortController;this.activeQuotaWait={controller:r,signal:null,stepName:e};try{let e=n;for(;e>0;){let t=Math.min(e,3e4);await this.waitForDelay(t,r.signal),e-=t}}catch(n){throw r.signal.aborted?new P(this.activeQuotaWait?.signal??V,{cause:Error(`Interrupted while waiting for Codex quota reset`),context:{...this.createInterruptContext(e,t),phase:`StepRunnerService.waitForCodexQuota`}}):n}finally{this.activeQuotaWait=null}}async waitForDelay(e,t){await new Promise((n,r)=>{let i=setTimeout(()=>{t.removeEventListener(`abort`,a),n()},e),a=()=>{clearTimeout(i),t.removeEventListener(`abort`,a),r(Error(`aborted`))};t.addEventListener(`abort`,a,{once:!0})})}formatQuotaError(e){return e instanceof Error&&e.message.trim().length>0?e.message:`unknown quota error`}resolveTimeoutMs(e,t){let n=e[Lt];if(n===void 0)return null;try{let e=$t.parse(n);return Math.ceil(e*Ht)}catch(e){throw new yt({stepName:t,configKey:Lt,timeoutValue:n},{cause:e})}}resolveTimeoutRetries(e,t){let n=e[Rt];try{return en.parse(n)}catch(e){throw new yt({stepName:t,configKey:Rt,timeoutValue:n},{cause:e})}}formatTimeoutMs(e){return e%Ht===0?`${e/Ht} minute(s)`:`${e}ms`}cleanupProcessGroup(){if(!this.activeStep)return;let{processGroupId:e,stepName:t}=this.activeStep;if(e!==null)try{process.kill(-e,V)}catch(n){let r=n.code;r===Pt?this.logger.info(` ${R}cleanup${L} process group ${e} already exited (ESRCH)`):this.logger.warn(` ${z}${U}${L} failed to clean up process group for ${t} (pgid=${e}, error=${r})`)}}killActiveStep(e){if(!this.activeStep)return!1;let{process:t,processGroupId:n}=this.activeStep;if(n!==null)try{return process.kill(-n,e),!0}catch(e){e.code===Pt?this.logger.info(` ${R}signal${L} process group for ${this.activeStep.stepName} already exited (ESRCH)`):this.logger.warn(` ${z}${U}${L} ${Nt} process group for ${this.activeStep.stepName}`)}try{return t.kill(e)}catch(e){return this.logger.warn(` ${z}${U}${L} ${Nt} ${this.activeStep.stepName} (${e.message})`),!1}}createStatusFile(){let e=b(ee(),`workflow-step-${se()}.status`);return ae(e,``,`utf-8`),e}async raceInteractiveCompletion(e,t,n,r){return new Promise(i=>{let a=!1,o=!1,s=!1,c=null,l=null,u=null,d=null,f=null,p=e=>{a||(a=!0,l&&=(l.close(),null),u&&=(u.close(),null),d&&=(clearInterval(d),null),f&&=(clearInterval(f),null),c&&=(clearTimeout(c),null),i(e))},m=!1,h=!1,g=()=>{try{return C(t)&&T(t,`utf-8`).trim()===`YES`}catch(e){let t=e.code;return!t||!qt.has(t)?this.logger.warn(` ${z}${U}${L} status-file read failed for ${n}: ${e instanceof Error?e.message:String(e)}`):this.logger.info(` ${R}status-file${L} transient FS error (${t}) polling ${n}`),!1}},_=()=>{if(!(o||!g())){if(o=!0,m=this.killActiveStep(V),!m){this.logger.warn(` ${z}${U}${L} ${n} status-file: SIGTERM could not be delivered, process may have already exited`);return}c=setTimeout(()=>{a||(h=!0,this.logger.warn(` ${z}${U}${L} ${n} did not exit after status-file SIGTERM, forcing SIGKILL`),this.killActiveStep(Bt))},Vt)}},ee=()=>!!(r&&C(r)),v=()=>{if(!(s||!ee())){if(s=!0,m=this.killActiveStep(V),!m){this.logger.warn(` ${z}${U}${L} ${n} stop request: SIGTERM could not be delivered, process may have already exited`);return}c=setTimeout(()=>{a||(h=!0,this.logger.warn(` ${z}${U}${L} ${n} did not exit after stop-request SIGTERM, forcing SIGKILL`),this.killActiveStep(Bt))},Vt)}};try{l=this.watchStatusFile(t,()=>_())}catch(e){this.logger.warn(` ${z}${U}${L} status-file watcher setup failed for ${n}: ${e instanceof Error?e.message:String(e)}`)}if(d=setInterval(_,1e3),r){try{u=this.watchStatusFile(r,()=>v())}catch(e){e.code!==`ENOENT`&&this.logger.warn(` ${z}${U}${L} stop-request watcher setup failed for ${n}: ${e instanceof Error?e.message:String(e)}`)}f=setInterval(v,Jt)}_(),v(),e.then(e=>{if(s){h&&this.logger.warn(` ${z}${U}${L} ${n} required SIGKILL escalation after stop request`),p({status:Qt});return}if(!(o||g())){p(e);return}switch(o||(o=!0,this.logger.info(` ${Ct}${Yt}${L} ${n} status-file observed during process exit handling`)),e.status){case`spawn_error`:case`timed_out`:p(e);return;case`signaled`:h&&this.logger.warn(` ${z}${U}${L} ${n} required SIGKILL escalation (work completed via status-file)`),p(Kt);return;case`completed`:e.exitCode===0||e.exitCode>128||h||!m?(h&&this.logger.warn(` ${z}${U}${L} ${n} required SIGKILL escalation (work completed via status-file, exit code ${e.exitCode})`),p(Kt)):p(e);return;case`status_completed`:p(e);return;case Qt:p(e);return;default:p(e)}})})}async raceStopRequest(e,t,n){return new Promise(r=>{let i=!1,a=!1,o=null,s=null,c=null,l=e=>{i||(i=!0,c&&=(c.close(),null),s&&=(clearInterval(s),null),o&&=(clearTimeout(o),null),r(e))},u=()=>{if(!(a||!C(n))){if(a=!0,!this.killActiveStep(V)){this.logger.warn(` ${z}${U}${L} ${t} stop request: SIGTERM could not be delivered, process may have already exited`);return}o=setTimeout(()=>{i||(this.logger.warn(` ${z}${U}${L} ${t} did not exit after stop-request SIGTERM, forcing SIGKILL`),this.killActiveStep(Bt))},Vt)}};try{c=this.watchStatusFile(n,()=>u())}catch(e){e.code!==`ENOENT`&&this.logger.warn(` ${z}${U}${L} stop-request watcher setup failed for ${t}: ${e instanceof Error?e.message:String(e)}`)}s=setInterval(u,Jt),u(),e.then(e=>{l(a?{status:Qt}:e)})})}captureTerminalState(){if(!process.stdin.isTTY)return null;try{return a(`stty`,[`-g`],{encoding:`utf-8`,stdio:[H,Ot,`ignore`]}).trim()}catch{return null}}restoreTerminalState(e){if(e&&process.stdin.isTTY)try{a(`stty`,[e],{stdio:[H,`ignore`,`ignore`]})}catch{}process.stdout.isTTY&&process.stdout.write(`\x1B[0m\x1B[?25h\x1B[?1049l`)}cleanupStatusFile(e){try{C(e)&&re(e)}catch(e){this.logger.warn(` ${z}${U}${L} status-file cleanup failed: ${e instanceof Error?e.message:String(e)}`)}}};const J=`\x1B[0m`,nn=`\x1B[1m`,rn=`\x1B[2m`,an=`\x1B[36m`,on=`\x1B[32m`,sn=`\x1B[31m`,cn=`\x1B[34m`;var ln=class{activePrompt=null;activePromptSignal=null;constructor(e,t){this.stepRunner=e,this.logger=t}async pathExists(e){try{return await l(e),!0}catch{return!1}}abortActivePrompt(e){this.activePromptSignal=e,this.activePrompt?.close()}async handleUserPrompt(e,t,n,r,i){if(!e.on||!(`user_prompt`in e.on))return!0;this.logger.info(`\n ${cn}${nn}trigger${J} ${nn}user_prompt${J}`),this.logger.info(` ${`─`.repeat(50)}`);let a=t,o=n.CONTEXT_FILE;if(!a&&o){let e=x(r,o);await this.pathExists(e)&&(a=(await d(e,`utf-8`)).trim(),a&&this.logger.info(` ${on}found${J} Existing context.md -> ${o}`))}if(!a)if(i)this.logger.info(` ${an}dry ${J} Would prompt user for input`),a=`(dry-run: no prompt collected)`;else{this.logger.info(` ${an}input${J} Enter your prompt (press Enter twice to finish):\n`);let e=[],t=D({input:process.stdin,output:process.stdout});this.activePrompt=t,this.activePromptSignal=null,a=await new Promise((n,r)=>{let i=!1,a=e=>{i||(i=!0,this.activePrompt=null,e())};t.on(`line`,n=>{n===``&&e.length>0&&e[e.length-1]===``?t.close():e.push(n)}),t.on(`SIGINT`,()=>{this.activePromptSignal=`SIGINT`,t.close()}),t.on(`close`,()=>a(()=>{let t=this.activePromptSignal;if(this.activePromptSignal=null,t){r(new P(t));return}e.length>0&&e[e.length-1]===``&&e.pop(),n(e.join(`
26
+ `))}))})}if(!a)return this.logger.error(` ${sn}fail${J} No prompt provided. Aborting.`),!1;if(o){let e=x(r,o);await u(y(e),{recursive:!0}),await g(e,a,`utf-8`),this.logger.info(` ${on}saved${J} ${a.split(`
27
+ `).length} line(s) -> ${o}`)}return n.USER_PROMPT=a,this.logger.info(` ${rn}prompt${J} ${a.split(`
28
+ `)[0].slice(0,80)}${a.includes(`
29
+ `)?`...`:``}`),!0}async handleAgentDecision(e,t,n,r,i,a){let o=e.on?.agent_decision;if(!o?.steps)return!0;this.logger.info(`\n ${cn}${nn}trigger${J} ${nn}agent_decision${J}`),this.logger.info(` ${`─`.repeat(50)}`),a&&(t.HEARTBEAT_PROMPT=a,this.logger.info(` ${rn}prompt${J} ${a.split(`
30
+ `)[0].slice(0,80)}${a.includes(`
31
+ `)?`...`:``}`));let s=t.CONTEXT_FILE;s&&await u(y(x(i.workflowDir,s)),{recursive:!0});let c={inputs:n,matrix:{},secrets:r,env:t};for(let e of o.steps)if(!await this.stepRunner.runStep(e,c,i))return this.logger.error(`\n ${sn}Agent decision aborted workflow${J}`),!1;if(s&&!i.dryRun){let e=x(i.workflowDir,s);if(!await this.pathExists(e))return this.logger.error(` ${sn}fail${J} Context file not created: ${s}`),!1;let t=(await d(e,`utf-8`)).trim();if(!t)return this.logger.error(` ${sn}fail${J} Context file is empty: ${s}`),!1;this.logger.info(` ${on}ready${J} context has ${t.split(`
32
+ `).length} line(s)`)}return!0}};const un=[`actions/checkout`,`actions/setup-node`,`actions/cache`,`actions/upload-artifact`,`actions/download-artifact`,`pnpm/action-setup`],dn=`bold.calm.cool.dark.deep.fair.fast.free.gold.keen.kind.loud.neat.pure.rare.safe.slim.soft.tall.warm.wild.wise.blue.gray.jade.iron.zinc.ruby.opal.onyx`.split(`.`),fn=`arch.beam.bird.bolt.cape.cove.dawn.dove.echo.fern.flux.gale.hawk.haze.iris.jade.kite.lake.lynx.mesa.moth.node.palm.peak.pine.raft.reef.sage.tide.vale`.split(`.`);var pn=class{generateHumanReadableId(){return`${dn[Math.floor(Math.random()*dn.length)]}-${fn[Math.floor(Math.random()*fn.length)]}`}parseWorkflowFile(e){let t=x(e);if(!C(t))throw Error(`Workflow file not found: ${t}`);let n=T(t,`utf-8`),r=Ue.parse(O(n));if(r.imports&&this.resolveImports(r,t),!r.jobs||Object.keys(r.jobs).length===0)throw Error(`No jobs found in workflow file`);return this.resolveExtends(r),r}resolveImports(e,t,n=new Set){let r=x(t);if(n.has(r))throw Error(`Circular import detected: ${r}`);n.add(r);let i=r.replace(/\/[^/]+$/,``);for(let t of e.imports??[]){let a=x(i,t);if(!C(a))throw Error(`Imported workflow file not found: ${a} (from ${r})`);let o=O(T(a,`utf-8`));if(o.imports&&this.resolveImports(o,a,n),o.env&&(e.env={...o.env,...e.env}),o.pre&&(e.pre||={},o.pre.services&&(e.pre.services=[...o.pre.services,...e.pre.services??[]]),o.pre.steps&&(e.pre.steps=[...o.pre.steps,...e.pre.steps??[]])),o.post&&(e.post||={},o.post.services&&(e.post.services=[...o.post.services,...e.post.services??[]]),o.post.steps&&(e.post.steps=[...o.post.steps,...e.post.steps??[]])),o.worktree){e.worktree||={};for(let t of[`beforeCreate`,`afterCreate`,`beforeCompleted`,`afterCompleted`])o.worktree[t]&&!e.worktree[t]&&(e.worktree[t]=o.worktree[t])}if(o[`max-workflows`]&&!e[`max-workflows`]&&(e[`max-workflows`]=o[`max-workflows`]),o[`launch-command`]&&!e[`launch-command`]&&(e[`launch-command`]=o[`launch-command`]),o.keybindings&&(e.keybindings={...o.keybindings,...e.keybindings}),o[`system-prompt`]&&(e[`system-prompt`]?e[`system-prompt`]=`${o[`system-prompt`]}\n\n${e[`system-prompt`]}`:e[`system-prompt`]=o[`system-prompt`]),o.jobs)for(let[t,n]of Object.entries(o.jobs))t in(e.jobs??{})||(e.jobs||={},e.jobs[t]=n)}delete e.imports}resolveExtends(e){let{jobs:t}=e,n=new Map;for(let[e,r]of Object.entries(t))e.startsWith(`.`)&&n.set(e,r);for(let[e,r]of Object.entries(t)){if(e.startsWith(`.`)||!r.extends)continue;let t=Array.isArray(r.extends)?r.extends:[r.extends],i=[...r.steps??[]],a=[...r.preJob?.steps??[]],o=[...r.postJob?.steps??[]],s=[],c=[],l=[];for(let i of t){let t=n.get(i);if(!t)throw Error(`Job "${e}" extends "${i}" but template not found`);s.push(...t.steps??[]),c.push(...t.preJob?.steps??[]),l.push(...t.postJob?.steps??[]),this.applyTemplate(r,t)}r.steps=[...s,...i],this.assignJobHookSteps(r,`preJob`,[...c,...a]),this.assignJobHookSteps(r,`postJob`,[...l,...o]),delete r.extends}for(let e of n.keys())delete t[e]}applyTemplate(e,t){e.steps=[...t.steps??[],...e.steps??[]],this.mergeJobHookSteps(e,t,`preJob`),this.mergeJobHookSteps(e,t,`postJob`),(t.env||e.env)&&(e.env={...t.env,...e.env}),t[`system-prompt`]&&e[`system-prompt`]?e[`system-prompt`]=`${t[`system-prompt`]}\n\n${e[`system-prompt`]}`:t[`system-prompt`]&&(e[`system-prompt`]=t[`system-prompt`]),t[`runs-on`]&&!e[`runs-on`]&&(e[`runs-on`]=t[`runs-on`]),t.strategy&&!e.strategy&&(e.strategy=t.strategy),t.context&&!e.context&&(e.context=t.context)}mergeJobHookSteps(e,t,n){let r=t[n]?.steps??[],i=e[n]?.steps??[];this.assignJobHookSteps(e,n,[...r,...i])}assignJobHookSteps(e,t,n){if(n.length>0){e[t]={steps:n};return}delete e[t]}loadSecrets(e){let t=new Map;if(!C(e))return t;let n=T(e,`utf-8`);for(let e of n.split(`
33
+ `)){let n=e.trim();if(!n||n.startsWith(`#`))continue;let r=n.indexOf(`=`);r!==-1&&t.set(n.slice(0,r),n.slice(r+1))}return t}interpolate(e,t){return e.replace(/\$\{\{\s*(.+?)\s*\}\}/g,(e,n)=>{let r=n.trim().split(`.`);if(r[0]===`inputs`&&r[1])return t.inputs.get(r[1])||``;if(r[0]===`matrix`&&r[1])return t.matrix[r[1]]||``;if(r[0]===`secrets`&&r[1])return t.secrets.get(r[1])||process.env[r[1]]||``;if(r[0]===`env`&&r[1])return t.env[r[1]]||process.env[r[1]]||``;if(r[0]===`runner`&&r[1]===`os`)return`macOS`;if(r[0]===`github`&&r[1]===`sha`)try{return o(`git rev-parse HEAD`,{encoding:`utf-8`,stdio:[`ignore`,`pipe`,`ignore`]}).trim()}catch{return`local`}return n.includes(`hashFiles`)?`local`:`\${{ ${n} }}`})}isActionSkipped(e){return un.some(t=>e.startsWith(t))}resolveJobOrder(e,t){let n=new Set,r=[],i=t=>{if(n.has(t))return;n.add(t);let a=e[t];if(!a)throw Error(`Job not found: ${t}`);let o=a.needs?Array.isArray(a.needs)?a.needs:[a.needs]:[];for(let e of o)i(e);r.push(t)};if(t)i(t);else for(let t of Object.keys(e))i(t);return r}expandMatrix(e){if(!e.strategy?.matrix)return[{}];let{include:t,...n}=e.strategy.matrix;if(t&&t.length>0)return t;let r=Object.keys(n).filter(e=>Array.isArray(n[e]));if(r.length===0)return[{}];let i=[{}];for(let e of r){let t=n[e],r=[];for(let n of i)for(let i of t)r.push({...n,[e]:String(i)});i=r}return i}};const mn=`\x1B[0m`,hn=`\x1B[2m`;var gn=class{constructor(e,t){this.stepRunner=e,this.logger=t}createWorktree(e,t){let n=t.toLowerCase().replace(/[^a-z0-9-]+/g,`-`).replace(/^-|-$/g,``),r=x(y(e),`${te(e)}-worktree`),i=x(r,n);C(r)||w(r,{recursive:!0});let a=`worktree/${n}`;C(i)&&(this.logger.info(` ${hn}clean${mn} Removing stale worktree at ${i}`),this.cleanupWorktreePath(e,i));try{o(`git branch -D "${a}"`,{cwd:e,stdio:[`ignore`,`pipe`,`ignore`]})}catch{}return this.logger.info(` create${mn} Worktree at ${i} (branch: ${a})`),o(`git worktree add -b "${a}" "${i}" HEAD`,{cwd:e,stdio:[`ignore`,`pipe`,`ignore`]}),i}removeWorktree(e,t){if(C(t)){this.logger.info(` ${hn}remove${mn} Worktree at ${t}`);try{this.cleanupWorktreePath(e,t)}catch{this.logger.warn(` warn${mn} Failed to remove worktree (may need manual cleanup)`)}}}cleanupWorktreePath(e,t){if(this.isRegisteredWorktree(e,t)){o(`git worktree remove --force "${t}"`,{cwd:e,stdio:[`ignore`,`pipe`,`ignore`]});return}ne(t,{recursive:!0,force:!0})}isRegisteredWorktree(e,t){try{return o(`git worktree list --porcelain`,{cwd:e,encoding:`utf-8`,stdio:[`ignore`,`pipe`,`ignore`]}).split(`
34
+ `).some(e=>e.startsWith(`worktree `)&&x(e.slice(9))===t)}catch{return!1}}async runHook(e,t,n,r,i,a){let o={inputs:r,matrix:{},secrets:i,env:n};for(let n of e.steps)if(!await this.stepRunner.runStep(n,o,{...a,workflowDir:t}))return!1;return!0}};const Y=`\x1B[0m`,X=`\x1B[1m`,_n=`\x1B[2m`,vn=`\x1B[32m`,Z=`\x1B[33m`,Q=`\x1B[31m`,$=`\x1B[34m`,yn=`SIGINT`,bn=`SIGTERM`,xn=`workflow_execution`,Sn=`restart-from`,Cn=`user_prompt`,wn=`agent_decision`,Tn=`workflow_dispatch`,En=`Workflow post-cleanup failed`;var Dn=class{logger;parser;serviceManager;stepRunner;jobRunner;triggerService;worktreeService;registry;outputLines=[];interruptedSignal=null;constructor(e,t={}){let n=e||{info:e=>process.stdout.write(`${e}\n`),error:e=>console.error(e),warn:e=>console.error(e)},r=e=>{this.outputLines.push(e),this.outputLines.length>5e3&&this.outputLines.shift()};this.logger={info:e=>{r(e),n.info(e)},error:e=>{r(e),n.error(e)},warn:e=>{r(e),n.warn(e)}},this.parser=t.parser??new pn,this.serviceManager=t.serviceManager??new ut(this.logger),this.stepRunner=t.stepRunner??new tn(this.parser,this.logger),this.jobRunner=t.jobRunner??new _t(this.parser,this.stepRunner,this.logger),this.triggerService=t.triggerService??new ln(this.stepRunner,this.logger),this.worktreeService=t.worktreeService??new gn(this.stepRunner,this.logger),this.registry=t.registry??new $e}async run(e){this.outputLines=[],this.interruptedSignal=null;let t=e.dryRun??!1,n=e.continueOnError??!1,r=!1,i=e=>{r||(r=!0,this.interrupt(e))},a=()=>i(yn),o=()=>i(bn);process.on(yn,a),process.on(bn,o);try{return await this.executeWorkflow(e,t,n,e.keepWorktree??!1)}catch(e){if(e instanceof P||this.isInterrupted())return await this.serviceManager.stopAll(),this.createInterruptedResult();throw e}finally{process.off(yn,a),process.off(bn,o),await this.serviceManager.stopAll()}}interrupt(e,t={phase:xn}){if(this.interruptedSignal)return;this.interruptedSignal=e;let n=t.phase===xn?``:` (${t.phase})`;this.logger.info(`\n\n${Z}${X}Interrupted — shutting down...${Y}${n}`),this.triggerService.abortActivePrompt(e),this.stepRunner.stopActiveStep(e),this.serviceManager.stopAll()}isInterrupted(){return this.interruptedSignal===yn||this.interruptedSignal===bn}createInterruptedResult(){return{exitCode:130,output:this.outputLines.join(`
35
+ `)}}parseFixRestartFrom(e,t){if(!e.startsWith(`---`))return{};let n=e.indexOf(`---`,3);if(n<0)return{};let r=e.slice(3,n);for(let e of r.split(`
36
+ `)){let n=e.trim();if(n.startsWith(`${Sn}:`)){let e=n.slice(`${Sn}:`.length).trim();return t.includes(e)?{restartFrom:e}:{invalidTarget:e}}}return{}}async pathExists(e){try{return await l(e),!0}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async waitForServiceStartup(){await new Promise(e=>{setTimeout(e,2e3)})}resolveRunner(e){if(e.runner&&e.cliAgent&&e.runner!==e.cliAgent)throw Error(`Conflicting runner selectors: runner="${e.runner}" cliAgent="${e.cliAgent}"`);return e.runner??e.cliAgent}async executeWorkflow(e,t,n,r=!1){let i=x(e.workflowPath),a=this.parser.parseWorkflowFile(i),s=this.resolveRunner(e),c=null,l=null,u=null,f=y(i),p=f,m=null,_=!1,ee=!1,v=!!a.worktree,b=new Map,S=e.secretFile?this.parser.loadSecrets(e.secretFile):new Map,C={};try{let u=a.on?.[Tn]?.inputs||{};for(let[e,t]of Object.entries(u))t.default&&b.set(e,t.default);if(e.inputs)for(let[t,n]of Object.entries(e.inputs))b.set(t,n);if(a.env)for(let[e,t]of Object.entries(a.env))C[e]=String(t);if(e.env)for(let[t,n]of Object.entries(e.env))C[t]=n;s&&(C.WORKFLOW_RUNNER=s,C.WORKFLOW_CLI_AGENT=s);let w=f;for(;w!==`/`;){if(await this.pathExists(x(w,`.git`))){f=w;break}w=y(w)}let T=a[`max-workflows`];if(T){let t=this.registry.resolveWorkspace(e.workspace,a.workspace),n=await this.registry.countRunningWorkflows(t);if(n>=T)throw new rt(t,n,T)}let ne=a[`launch-command`];if(ne&&!e.skipLaunch){let t=this.buildLaunchCliCommand(e,i),n=e.name||a.name||te(i),r=e.name?n:`${n}-${this.parser.generateHumanReadableId()}`,s=ne.replace(`{name}`,r).replace(`{command}`,t);this.logger.info(`${_n}Delegating via launch-command: ${s}${Y}`);try{return o(s,{stdio:`inherit`,cwd:f}),{exitCode:0,output:this.outputLines.join(`
37
+ `)}}catch(e){return{exitCode:e.status??1,output:this.outputLines.join(`
38
+ `)}}}let re=e.name||a.name||te(i),ie=e.name?re:`${re}-${this.parser.generateHumanReadableId()}`;if(c=await this.registry.createRun({displayName:ie,dryRun:t,workflowPath:i,workflowWorkspace:a.workspace,workspace:e.workspace}),C.WORKFLOW_RUN_DIR=c.runDir,C.WORKFLOW_WORKSPACE=c.workspace,C.CONTEXT_FILE&&(C.CONTEXT_FILE=c.contextPath,C.CHANGELOG_FILE=c.changelogPath),this.logger.info(`${X}${`═`.repeat(60)}${Y}`),this.logger.info(` ${X}run-workflow${Y} - ${c.displayName}`),this.logger.info(`${X}${`═`.repeat(60)}${Y}`),this.logger.info(` File: ${i}`),this.logger.info(` Workspace: ${c.workspace||`default`}`),this.logger.info(` Run Dir: ${c.runDir}`),this.logger.info(` CWD: ${f}`),b.size>0&&this.logger.info(` Inputs: ${[...b.entries()].map(([e,t])=>`${e}=${t}`).join(`, `)}`),S.size>0&&this.logger.info(` Secrets: ${[...S.keys()].join(`, `)}`),e.job&&this.logger.info(` Target: ${e.job}`),s&&this.logger.info(` Runner: ${s}`),a.pre){let e=a.pre.services?.length||0,t=a.pre.steps?.length||0;this.logger.info(` Pre: ${e} service(s), ${t} setup step(s)`)}if(a.post){let e=a.post.services?.length||0,t=a.post.steps?.length||0;this.logger.info(` Post: ${e} service(s), ${t} cleanup step(s)`)}v&&this.logger.info(` Worktree: enabled${r?` (keep on completion)`:``}`);let ae=a.on&&Cn in a.on?Cn:a.on?.[wn]?wn:Tn;if(this.logger.info(` Trigger: ${ae}`),this.logger.info(` Mode: ${t?`dry-run`:`execute`}`),this.logger.info(`${X}${`═`.repeat(60)}${Y}`),!await this.triggerService.handleUserPrompt(a,e.prompt||null,C,f,t))return this.isInterrupted()?(l=this.createInterruptedResult(),l):(this.logger.error(`\n${Q}${X}Workflow aborted: no prompt provided${Y}`),l={exitCode:1,output:this.outputLines.join(`
39
+ `)},l);if(!await this.triggerService.handleAgentDecision(a,C,b,S,{runner:s,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,stopRequestPath:c.stopRequestPath,workflowDir:f},e.prompt||null))return this.isInterrupted()?(l=this.createInterruptedResult(),l):(this.logger.info(`\n${Z}${X}Workflow skipped: agent decided nothing to do${Y}`),l={exitCode:2,output:this.outputLines.join(`
40
+ `)},l);if(p=f,v&&!t)a.worktree?.beforeCreate&&(this.logger.info(`\n ${$}${X}worktree${Y} ${X}beforeCreate${Y}`),this.logger.info(` ${`─`.repeat(50)}`),await this.worktreeService.runHook(a.worktree.beforeCreate,f,C,b,S,{runner:s,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,stopRequestPath:c.stopRequestPath})||(this.isInterrupted()?l=this.createInterruptedResult():(this.logger.error(`\n${Q}${X}Workflow aborted: worktree beforeCreate hook failed${Y}`),l={exitCode:1,output:this.outputLines.join(`
41
+ `)}))),l||(this.logger.info(`\n ${$}${X}worktree${Y} ${X}Creating worktree${Y}`),this.logger.info(` ${`─`.repeat(50)}`),m=this.worktreeService.createWorktree(f,c.displayName),f=m,C.WORKTREE_PATH=m,C.WORKFLOW_NAME=c.displayName,C.ORIGINAL_REPO_PATH=p,a.worktree?.afterCreate&&(this.logger.info(`\n ${$}${X}worktree${Y} ${X}afterCreate${Y}`),this.logger.info(` ${`─`.repeat(50)}`),await this.worktreeService.runHook(a.worktree.afterCreate,m,C,b,S,{runner:s,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,stopRequestPath:c.stopRequestPath})||(this.isInterrupted()?l=this.createInterruptedResult():(this.logger.error(`\n${Q}${X}Workflow aborted: worktree afterCreate hook failed${Y}`),l={exitCode:1,output:this.outputLines.join(`
42
+ `)}),ee=!r)));else if(v&&t){let e=y(f),t=te(f),n=c.displayName.toLowerCase().replace(/[^a-z0-9-]+/g,`-`).replace(/^-|-$/g,``),r=x(e,`${t}-worktree`,n);this.logger.info(`\n ${$}${X}worktree${Y} ${X}(dry-run)${Y} Would create at ${r}`)}!l&&a.pre&&(await this.runPreBlock(a.pre,C,b,S,{runner:s,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,stopRequestPath:c.stopRequestPath,workflowDir:f})||(this.isInterrupted()?l=this.createInterruptedResult():(this.logger.error(`\n${Q}${X}Workflow aborted: pre-conditions failed${Y}`),l={exitCode:1,output:this.outputLines.join(`
43
+ `)}),ee=!!(m&&!r)));let oe=Number(C.MAX_RETRIES)||3,E=this.parser.resolveJobOrder(a.jobs,e.job||null);this.logger.info(`\n ${_n}Job order: ${E.join(` -> `)} (max retries: ${oe})${Y}`);let se=new Set,D=C.WORKFLOW_RUN_DIR?x(C.WORKFLOW_RUN_DIR,`fix.md`):null,O=0,k=0;for(;!l&&k<E.length;){let r=E[k],i=a.jobs[r],o=await this.jobRunner.runJob(r,i,{inputs:b,secrets:S,workflowEnv:C,workflowSystemPrompt:a[`system-prompt`],jobOrder:E,allJobs:a.jobs},{runner:s,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,stopRequestPath:c.stopRequestPath,workflowDir:f,maxRetries:oe});if(this.isInterrupted()&&(l=this.createInterruptedResult()),!l&&!o&&(this.logger.error(`\n${Q}${X}Workflow failed at job "${r}"${Y}`),await this.serviceManager.stopAll(),l={exitCode:1,output:this.outputLines.join(`
44
+ `)}),!l){if(se.add(r),D&&await this.pathExists(D)&&!t){if(O++,O>3){this.logger.error(`\n${Q}${X}Fix loop limit reached (3 cycles). Aborting.${Y}`),l={exitCode:1,output:this.outputLines.join(`
45
+ `)};continue}let e=(await d(D,`utf-8`)).trim();await h(D);let{restartFrom:t,invalidTarget:n}=this.parseFixRestartFrom(e,E);if(this.logger.info(`\n${Z}${X}fix.md detected after "${r}" (cycle ${O}/3)${Y}`),this.logger.info(`${_n}${e.split(`
46
+ `)[0].slice(0,100)}${e.includes(`
47
+ `)?`...`:``}${Y}`),n&&this.logger.warn(`${Z}fix.md restart-from "${n}" is not a valid job (available: ${E.join(`, `)}), falling back to default${Y}`),C.CONTEXT_FILE&&await this.pathExists(C.CONTEXT_FILE)){let t=await d(C.CONTEXT_FILE,`utf-8`);await g(C.CONTEXT_FILE,`${t}\n\n---\n## Fix Request (cycle ${O})\n${e}`,`utf-8`)}let i=t?E.indexOf(t):-1,a=E.indexOf(`development`);k=i>=0?i:a>=0?a:1,this.logger.info(`${Z}Restarting from job "${E[k]}"${Y}\n`);continue}k++}}!l&&this.isInterrupted()&&(l=this.createInterruptedResult()),l||=(_=!0,this.logger.info(`\n${X}${`═`.repeat(60)}${Y}`),this.logger.info(` ${vn}${X}Workflow completed successfully${Y}`),this.logger.info(` Jobs run: ${[...se].join(`, `)}`),m&&this.logger.info(` Worktree: ${m}`),this.logger.info(`${X}${`═`.repeat(60)}${Y}\n`),{exitCode:0,output:this.outputLines.join(`
48
+ `)})}catch(e){if(u=e,e instanceof P||this.isInterrupted())l=this.createInterruptedResult();else throw e}finally{if(_&&l?.exitCode===0&&v&&a.worktree?.beforeCompleted&&!t&&m&&(this.logger.info(`\n ${$}${X}worktree${Y} ${X}beforeCompleted${Y}`),this.logger.info(` ${`─`.repeat(50)}`),await this.worktreeService.runHook(a.worktree.beforeCompleted,m,C,b,S,{runner:s,cliAgent:e.cliAgent,dryRun:t,continueOnError:n})||(this.isInterrupted()?l=this.createInterruptedResult():this.logger.warn(`\n${Z}${X}Warning: worktree beforeCompleted hook failed${Y}`))),a.post)try{await this.runPostBlock(a.post,C,b,S,{runner:s,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,workflowDir:f})||(this.logger.error(`\n${Q}${X}${En}${Y}`),(!l||l.exitCode===0||l.exitCode===2)&&(l={exitCode:1,output:this.outputLines.join(`
49
+ `)}))}catch(e){u??=e,this.logger.error(`\n${Q}${X}${En}${Y}`),(!l||l.exitCode===0||l.exitCode===2)&&(l={exitCode:1,output:this.outputLines.join(`
50
+ `)})}_&&l?.exitCode===0&&v&&a.worktree?.afterCompleted&&!t&&!r&&(this.logger.info(`\n ${$}${X}worktree${Y} ${X}afterCompleted${Y}`),this.logger.info(` ${`─`.repeat(50)}`),await this.worktreeService.runHook(a.worktree.afterCompleted,p,C,b,S,{runner:s,cliAgent:e.cliAgent,dryRun:t,continueOnError:n})||(this.isInterrupted()?l=this.createInterruptedResult():this.logger.warn(`\n${Z}${X}Warning: worktree afterCompleted hook failed${Y}`))),ee&&m&&this.worktreeService.removeWorktree(p,m),l&&={...l,output:this.outputLines.join(`
51
+ `)},c&&await this.finalizeRegistryRun(c,l,u)}return l??{exitCode:1,output:this.outputLines.join(`
52
+ `)}}async finalizeRegistryRun(e,t,n){let r=t?.exitCode===130||n instanceof P,i=t?.exitCode??(r?130:1),a=i===0||i===2?`completed`:`error`,o=i===0?`success`:i===2?`skipped`:r||this.isInterrupted()?`interrupted`:`failed`,s=n instanceof Error?n.message:i===1?this.lastMeaningfulOutputLine():void 0;await this.registry.finalizeRun(e,{errorMessage:s,exitCode:i,outcome:o,stage:a})}resolveCliPath(){let e=nt,t=x(`/`);for(;e!==t;){let t=x(e,`cli.cjs`);if(C(t))return t;let n=x(e,`dist`,`cli.cjs`);if(C(n))return n;e=y(e)}throw Error(`Unable to locate workflow-mcp cli.cjs from `+nt)}buildLaunchCliCommand(e,t){let n=[`node`,this.resolveCliPath(),`run-workflow`,t,`--skip-launch`],r=this.resolveRunner(e);if(e.job&&n.push(`--job`,e.job),r&&n.push(`--runner`,r),e.dryRun&&n.push(`--dry-run`),e.continueOnError&&n.push(`--continue-on-error`),e.keepWorktree&&n.push(`--keep-worktree`),e.prompt&&n.push(`--prompt`,`'${e.prompt.replace(/'/g,`'\\''`)}'`),e.name&&n.push(`--name`,`'${e.name}'`),e.workspace&&n.push(`--workspace`,`'${e.workspace}'`),e.secretFile&&n.push(`--secret-file`,e.secretFile),e.inputs)for(let[t,r]of Object.entries(e.inputs))n.push(`--input`,`${t}=${r}`);if(e.env)for(let[t,r]of Object.entries(e.env))n.push(`--env`,`${t}=${r}`);return n.join(` `)}lastMeaningfulOutputLine(){return[...this.outputLines].reverse().find(e=>e.trim().length>0)}async runPreBlock(e,t,n,r,i){if(this.logger.info(`\n ${$}${X}pre${Y} ${X}Pre-conditions${Y}`),this.logger.info(` ${`─`.repeat(50)}`),t.CONTEXT_FILE&&await u(y(x(i.workflowDir,t.CONTEXT_FILE)),{recursive:!0}),e.services&&e.services.length>0){this.logger.info(`\n ${$}${X}services${Y}`);for(let n of e.services)if(await this.serviceManager.startService(n,t,i.workflowDir,i.dryRun)&&n[`ready-check`]&&!i.dryRun){let e=await this.serviceManager.waitForServiceReady(n,i.workflowDir,t,()=>this.interruptedSignal!==null);if(this.isInterrupted())return!1;if(!e&&!i.continueOnError)return await this.serviceManager.stopAll(),!1}!i.dryRun&&this.serviceManager.getRunningServices().length>0&&await this.waitForServiceStartup()}if(e.steps&&e.steps.length>0){this.logger.info(`\n ${$}${X}setup${Y}`);let a={inputs:n,matrix:{},secrets:r,env:t};for(let t of e.steps)if(!await this.stepRunner.runStep(t,a,i))return this.logger.error(`\n ${Q}Pre-condition step failed${Y}`),await this.serviceManager.stopAll(),!1}return this.logger.info(`\n ${vn}Pre-conditions ready${Y}`),!0}async runPostBlock(e,t,n,r,i){this.logger.info(`\n ${$}${X}post${Y} ${X}Cleanup${Y}`),this.logger.info(` ${`─`.repeat(50)}`),await this.serviceManager.stopAll();try{if(e.services&&e.services.length>0){this.logger.info(`\n ${$}${X}services${Y}`);for(let n of e.services)if(await this.serviceManager.startService(n,t,i.workflowDir,i.dryRun)&&n[`ready-check`]&&!i.dryRun&&!await this.serviceManager.waitForServiceReady(n,i.workflowDir,t,()=>!1)&&!i.continueOnError)return!1;!i.dryRun&&this.serviceManager.getRunningServices().length>0&&await this.waitForServiceStartup()}if(e.steps&&e.steps.length>0){this.logger.info(`\n ${$}${X}cleanup${Y}`);let a={inputs:n,matrix:{},secrets:r,env:t};for(let t of e.steps)if(!await this.stepRunner.runStep(t,a,i))return this.logger.error(`\n ${Q}Post-cleanup step failed${Y}`),!1}return this.logger.info(`\n ${vn}Cleanup complete${Y}`),!0}finally{await this.serviceManager.stopAll()}}};const On=r.object({workflowPath:r.string().describe(`Path to the workflow YAML file (e.g., .github/workflows/deploy.yml)`),runner:r.string().optional().describe(`Preferred runner key for step command maps (e.g. ollama, claude, codex)`),cliAgent:r.string().optional().describe(`Deprecated alias for runner. Preferred runner command key for step command maps.`),job:r.string().optional().describe(`Run only this job (and its dependencies)`),inputs:r.record(r.string(),r.string()).optional().describe(`Workflow dispatch inputs as key-value pairs`),env:r.record(r.string(),r.string()).optional().describe(`Extra environment variables as key-value pairs`),secretFile:r.string().optional().describe(`Path to a dotenv-style secrets file`),dryRun:r.boolean().optional().describe(`Print steps without executing`),continueOnError:r.boolean().optional().describe(`Continue past step failures`),keepWorktree:r.boolean().optional().describe(`Keep worktree on completion (skip merge and cleanup for retry)`),prompt:r.string().optional().describe(`User prompt for user_prompt trigger workflows`),name:r.string().optional().describe(`Name for the workflow run context directory`),workspace:r.string().optional().describe(`Workspace for workflow registry storage`)});var kn=class e{static TOOL_NAME=`run_workflow`;constructor(e=new Dn){this.service=e}getInputSchema(){return On}getDefinition(){return{name:e.TOOL_NAME,description:`Run a GitHub Actions workflow file locally. Parses the workflow YAML and executes run steps on the host machine, respecting job dependencies, matrix strategies, environment variables, and workflow_dispatch inputs.`,inputSchema:r.toJSONSchema(On)}}async execute(e){try{let t=On.parse(e);if(t.runner&&t.cliAgent&&t.runner!==t.cliAgent)throw Error(`Conflicting runner selectors: runner="${t.runner}" cliAgent="${t.cliAgent}"`);let n={cliAgent:t.cliAgent,runner:t.runner??t.cliAgent,workflowPath:t.workflowPath,job:t.job,inputs:t.inputs,env:t.env,secretFile:t.secretFile,dryRun:t.dryRun,continueOnError:t.continueOnError,keepWorktree:t.keepWorktree,prompt:t.prompt,name:t.name,workspace:t.workspace},r=await this.service.run(n);return r.exitCode!==0&&r.exitCode!==2?{content:[{type:`text`,text:`Workflow failed (exit code ${r.exitCode}):\n\n${r.output}`}],isError:!0}:{content:[{type:`text`,text:r.output||`Workflow completed successfully.`}]}}catch(e){return{content:[{type:`text`,text:`Error: ${e instanceof Error?e.message:`Unknown error`}`}],isError:!0}}}},An=class e{static TOOL_NAME=`schedule-cron`;constructor(e=new Te){this.service=e}getInputSchema(){return be}getDefinition(){return{name:e.TOOL_NAME,description:`Schedule a headless Claude Code or Codex CLI run as a system cron job. Specify either a cron expression or an interval in minutes.`,inputSchema:r.toJSONSchema(be)}}async execute(t){try{let e=be.parse(t),n=await this.service.schedule(e);return{content:[{type:`text`,text:JSON.stringify(n,null,2)}]}}catch(n){let r=new le(`Failed to schedule cron job.`,`SCHEDULE_CRON_TOOL_FAILED`,{tool:e.TOOL_NAME,name:t.name,cwd:t.cwd},{cause:n});return console.error(`[${e.TOOL_NAME}] ${r.message}`,r.context),{content:[{type:`text`,text:JSON.stringify({code:r.code,context:r.context,message:r.message},null,2)}],isError:!0}}}};const jn=r.object({reason:r.string().optional().describe(`Optional stop reason to record.`),runKey:r.string().min(1).describe(`Run key of the running workflow to stop.`),workspace:r.string().optional().describe(`Workspace containing the running workflow. Defaults to default.`)});var Mn=class e{static TOOL_NAME=`stop_workflow`;constructor(e=new $e){this.registry=e}getInputSchema(){return jn}getDefinition(){return{name:e.TOOL_NAME,description:`Request a running workflow to stop gracefully without taking over terminal stdin.`,inputSchema:r.toJSONSchema(jn)}}async execute(e){try{let t=jn.parse(e),n=await this.registry.requestStop(t.workspace,t.runKey,t.reason);return{content:[{type:`text`,text:JSON.stringify({reason:n.reason,requestedAt:n.requestedAt,runKey:t.runKey,workspace:this.registry.resolveWorkspace(t.workspace)},null,2)}]}}catch(e){return{content:[{type:`text`,text:`Failed to request workflow stop. ${e instanceof Error?e.message:`Unknown error`}`}],isError:!0}}}};const Nn=[kn.TOOL_NAME,tt.TOOL_NAME,An.TOOL_NAME,Ee.TOOL_NAME,Mn.TOOL_NAME];function Pn(){let r=new e({name:`workflow-mcp`,version:`0.1.0`},{capabilities:{tools:{}}}),i=new kn,a=new tt,o=new An,s=new Ee,c=new Mn;return r.setRequestHandler(n,async()=>({tools:[i.getDefinition(),a.getDefinition(),o.getDefinition(),s.getDefinition(),c.getDefinition()]})),r.setRequestHandler(t,async e=>{let{name:t,arguments:n}=e.params;if(t===kn.TOOL_NAME)return await i.execute(n);if(t===tt.TOOL_NAME)return await a.execute(n);if(t===An.TOOL_NAME)return await o.execute(n);if(t===Ee.TOOL_NAME)return await s.execute();if(t===Mn.TOOL_NAME)return await c.execute(n);throw new ce(t,Nn)}),r}var Fn=class{server;transport=null;constructor(e){this.server=e}async start(){this.transport=new k,await this.server.connect(this.transport),console.error(`workflow-mcp MCP server started on stdio`)}async stop(){this.transport&&=(await this.transport.close(),null)}};export{$e as a,be as c,xt as i,Pn as n,Te as o,Dn as r,he as s,Fn as t};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/workflow-mcp",
3
- "version": "0.1.12",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server for running GitHub Actions workflows locally",
5
5
  "keywords": [
6
6
  "mcp",
@@ -36,10 +36,10 @@
36
36
  "commander": "14.0.3",
37
37
  "js-yaml": "4.1.1",
38
38
  "zod": "4.4.1",
39
- "@agimon-ai/foundation-validator": "0.5.7",
40
- "@agimon-ai/foundation-port-registry": "0.8.7",
41
- "@agimon-ai/foundation-process-registry": "0.8.7",
42
- "@agimon-ai/log-sink-mcp": "0.8.7"
39
+ "@agimon-ai/foundation-port-registry": "0.9.0",
40
+ "@agimon-ai/log-sink-mcp": "0.9.0",
41
+ "@agimon-ai/foundation-validator": "0.6.0",
42
+ "@agimon-ai/foundation-process-registry": "0.9.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/js-yaml": "4.0.9",
@@ -1,52 +0,0 @@
1
- let e=require(`@modelcontextprotocol/sdk/server/index.js`),t=require(`@modelcontextprotocol/sdk/types.js`),n=require(`zod`),r=require(`node:child_process`),i=require(`node:util`),a=require(`node:fs/promises`),o=require(`node:os`),s=require(`node:path`),c=require(`node:fs`),l=require(`node:readline`),u=require(`@agimon-ai/foundation-port-registry`),d=require(`@agimon-ai/foundation-process-registry`),f=require(`node:crypto`),p=require(`js-yaml`),m=require(`@modelcontextprotocol/sdk/server/stdio.js`);var h=class extends Error{code=`WORKFLOW_TOOL_NOT_FOUND`;constructor(e,t){super(`Unknown tool "${e}". Available tools: ${t.join(`, `)}`),this.toolName=e,this.availableTools=t,this.name=`WorkflowToolNotFoundError`}},g=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`WorkflowToolError`}},_=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`CronError`}};const v=`# workflow-mcp:cron:`,y=`crontab`,b=`claude`,x=`codex`,S=`--cwd`,C=`CRON_WRITE_FAILED`,ee=n.z.enum([b,x]),te=n.z.object({name:n.z.string().min(1),cwd:n.z.string().min(1),cli:ee,schedule:n.z.string().min(1),prompt:n.z.string().optional(),promptFile:n.z.string().optional(),createdAt:n.z.string().min(1)}),w=n.z.object({name:n.z.string().min(1),cwd:n.z.string().min(1),cli:ee.optional(),prompt:n.z.string().optional(),promptFile:n.z.string().optional(),schedule:n.z.string().optional(),intervalMinutes:n.z.number().positive().optional()});function T(e){return`'${e.replace(/'/g,`'\\''`)}'`}function ne(e,t,n,r){let i=n?T(n):r?`"$(cat ${T(r)})"`:void 0;if(e===`codex`){let e=[x,`--approval-mode`,`full-auto`];return i&&e.push(`-q`,i),e.push(S,T(t)),e.join(` `)}let a=[b,`--dangerously-skip-permissions`];return i&&a.push(`-p`,i),a.push(S,T(t)),a.join(` `)}function E(e){if(e<=0)throw Error(`Interval must be a positive number of minutes`);if(e<60)return`*/${e} * * * *`;let t=Math.floor(e/60);return t<24?`0 */${t} * * *`:`0 0 */${Math.floor(t/24)} * *`}const re=(0,i.promisify)(r.execFile);var D=class{logger;constructor(e){this.logger=e??{info:e=>process.stdout.write(`${e}\n`),error:e=>console.error(e),warn:e=>console.error(e)}}async readCrontab(){try{let{stdout:e}=await re(y,[`-l`]);return e}catch(e){let t=e.code;if(t===`ENOENT`||(e.stderr??``).includes(`no crontab for`))return``;throw this.logger.error(`[CronService.readCrontab] failed exitCode=${t}`),new _(`Failed to read current crontab.`,`CRON_READ_FAILED`,{exitCode:t},{cause:e})}}async writeCrontab(e){let t=``,n=(0,r.execFile)(y,[`-`]);n.stderr?.on(`data`,e=>{t+=e}),n.stdin?.write(e),n.stdin?.end(),await new Promise((r,i)=>{n.on(`close`,n=>{n===0?r():(this.logger.error(`[CronService.writeCrontab] failed exitCode=${n} stderr=${t}`),i(new _(`crontab rejected input (exit ${n}).`,C,{exitCode:n,stderr:t,contentLength:e.length})))}),n.on(`error`,e=>{this.logger.error(`[CronService.writeCrontab] spawn failed: ${e.message}`),i(new _(`Failed to spawn crontab process.`,C,{},{cause:e}))})})}async schedule(e){let t=e.cli??`claude`;if(!e.schedule&&!e.intervalMinutes)throw new _(`Either schedule (cron expression) or intervalMinutes must be provided.`,`CRON_INVALID_INPUT`,{name:e.name});let n=e.schedule??E(e.intervalMinutes),r=ne(t,e.cwd,e.prompt,e.promptFile),i=new Date().toISOString(),a=te.parse({name:e.name,cwd:e.cwd,cli:t,schedule:n,prompt:e.prompt,promptFile:e.promptFile,createdAt:i}),o=await this.readCrontab(),s=this.removeCronBlock(o,e.name),c=`${`${v}${e.name}`}\n${`# workflow-mcp:meta:${JSON.stringify(a)}`}\n${`${n} ${r}`}`,l=s.trimEnd()?`${s.trimEnd()}\n${c}\n`:`${c}\n`;return await this.writeCrontab(l),this.logger.info(`Scheduled cron "${e.name}" [${n}]`),a}async list(){let e=await this.readCrontab(),t=[],n=e.split(`
2
- `);for(let e=0;e<n.length;e++){let r=n[e];if(r.startsWith(`# workflow-mcp:meta:`))try{let e=r.slice(20),n=te.parse(JSON.parse(e));t.push(n)}catch(t){this.logger.warn(`[CronService.list] skipping malformed metadata at line ${e+1}: ${t.message}`)}}return t}async remove(e){let t=await this.readCrontab(),n=this.removeCronBlock(t,e);return n===t?!1:(await this.writeCrontab(n),this.logger.info(`Removed cron "${e}"`),!0)}removeCronBlock(e,t){let n=e.split(`
3
- `),r=[],i=`${v}${t}`,a=!1;for(let e of n){if(e===i){a=!0;continue}if(a){if(e.startsWith(`# workflow-mcp:meta:`))continue;a=!1;continue}r.push(e)}return r.join(`
4
- `)}},O=class e{static TOOL_NAME=`list-crons`;constructor(e=new D){this.service=e}getInputSchema(){return n.z.object({})}getDefinition(){return{name:e.TOOL_NAME,description:`List all cron jobs scheduled via workflow-mcp, showing name, schedule, CLI, cwd, and prompt.`,inputSchema:n.z.toJSONSchema(n.z.object({}))}}async execute(){try{let e=await this.service.list();return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}catch(t){let n=new g(`Failed to list cron jobs.`,`LIST_CRONS_TOOL_FAILED`,{tool:e.TOOL_NAME},{cause:t});return console.error(`[${e.TOOL_NAME}] ${n.message}`,n.context),{content:[{type:`text`,text:JSON.stringify({code:n.code,context:n.context,message:n.message},null,2)}],isError:!0}}}},k=class extends Error{code=`INVALID_WORKFLOW_RUN_RECORD`;constructor(e,t,n){super(`Invalid workflow run record at "${e}": ${t}`,n),this.recordPath=e,this.name=`InvalidWorkflowRunRecordError`}},ie=class extends Error{code=`WORKFLOW_RUN_CONFLICT`;constructor(e,t){super(`Workflow "${t}" is already running in workspace "${e}"`),this.workspace=e,this.runKey=t,this.name=`WorkflowRunConflictError`}};const ae=n.z.record(n.z.string(),n.z.coerce.string()),oe=n.z.object({description:n.z.string().optional(),required:n.z.boolean().optional(),default:n.z.string().optional(),type:n.z.string().optional(),options:n.z.array(n.z.string()).optional()}),se=n.z.record(n.z.string(),n.z.string()),ce=n.z.record(n.z.string(),n.z.string()),le=n.z.union([n.z.string(),ce]),ue=n.z.object({"fail-fast":n.z.boolean().optional(),matrix:n.z.looseObject({include:n.z.array(se).optional()}).optional()}),A=n.z.object({name:n.z.string().optional(),uses:n.z.string().optional(),run:le.optional(),interactiveRun:le.optional(),"timeout-minutes":n.z.number().optional(),env:ae.optional(),with:n.z.record(n.z.string(),n.z.string()).optional(),"working-directory":n.z.string().optional(),if:n.z.string().optional(),"continue-on-error":n.z.boolean().optional(),"timeout-retries":n.z.number().optional(),id:n.z.string().optional()}),de=n.z.object({name:n.z.string(),run:n.z.string(),env:ae.optional(),"working-directory":n.z.string().optional(),"ready-check":n.z.union([n.z.string(),n.z.boolean()]).optional(),"ready-timeout":n.z.number().optional(),host:n.z.string().optional(),port:n.z.number().int().min(1).max(65535).optional(),"port-range":n.z.object({min:n.z.number().int().min(1).max(65535),max:n.z.number().int().min(1).max(65535)}).optional(),"service-type":n.z.enum([`tool`,`service`]).optional()}),fe=n.z.object({services:n.z.array(de).optional(),steps:n.z.array(A).optional()}),pe=n.z.object({steps:n.z.array(A).optional()}),me=n.z.object({"runs-on":n.z.string().optional(),needs:n.z.union([n.z.string(),n.z.array(n.z.string())]).optional(),extends:n.z.union([n.z.string(),n.z.array(n.z.string())]).optional(),description:n.z.string().optional(),strategy:ue.optional(),steps:n.z.array(A),preJob:pe.optional(),postJob:pe.optional(),env:ae.optional(),if:n.z.string().optional(),"system-prompt":n.z.string().optional(),context:n.z.string().optional()}),he=n.z.object({steps:n.z.array(A)}),ge=n.z.object({steps:n.z.array(A)}),_e=n.z.object({beforeCreate:ge.optional(),afterCreate:ge.optional(),beforeCompleted:ge.optional(),afterCompleted:ge.optional()}),ve=n.z.object({STOP:n.z.string().optional()}),ye=n.z.object({name:n.z.string().optional(),workspace:n.z.string().optional(),imports:n.z.array(n.z.string()).optional(),"system-prompt":n.z.string().optional(),"max-workflows":n.z.number().int().positive().optional(),"launch-command":n.z.string().optional(),keybindings:ve.optional(),on:n.z.looseObject({workflow_dispatch:n.z.object({inputs:n.z.record(n.z.string(),oe).optional()}).nullable().optional(),agent_decision:he.optional()}).optional(),env:ae.optional(),pre:fe.optional(),post:fe.optional(),worktree:_e.optional(),jobs:n.z.record(n.z.string(),me)}),be=n.z.enum([`running`,`completed`,`error`]),xe=n.z.enum([`success`,`skipped`,`failed`,`interrupted`]),Se=n.z.object({displayName:n.z.string(),dryRun:n.z.boolean(),errorMessage:n.z.string().optional(),exitCode:n.z.number().optional(),finishedAt:n.z.string().optional(),outcome:xe.optional(),pid:n.z.number().int().positive().optional(),runKey:n.z.string(),stale:n.z.boolean().optional(),staleReason:n.z.string().optional(),stage:be,startedAt:n.z.string(),workflowPath:n.z.string(),workspace:n.z.string()});n.z.object({changelogPath:n.z.string(),contextPath:n.z.string(),displayName:n.z.string(),recordPath:n.z.string(),runDir:n.z.string(),runKey:n.z.string(),workspace:n.z.string()});const Ce=`default`,j=`running`,we=`completed`,M=`error`,Te=`workspaces`,N=`run.json`,Ee=`Workflow process is no longer running`;var De=class{constructor(e=(0,s.resolve)((0,o.homedir)(),`.workflow-mcp`)){this.homeDir=e}resolveWorkspace(e,t){return this.slugifySegment(e||t||Ce,Ce)}async createRun(e){let t=this.resolveWorkspace(e.workspace,e.workflowWorkspace),n=this.slugifySegment(e.displayName,`workflow-run`);await this.ensureWorkspaceStructure(t);let r=await this.findReusableRunStage(t,n);if(r===j)throw new ie(t,n);let i=this.getRunDirectory(t,j,n);r?(await(0,a.rm)(i,{recursive:!0,force:!0}),await(0,a.rename)(this.getRunDirectory(t,r,n),i),await this.removeFileIfExists((0,s.resolve)(i,`fix.md`))):await(0,a.mkdir)(i,{recursive:!0});let o={displayName:e.displayName,dryRun:e.dryRun,runKey:n,stage:j,startedAt:new Date().toISOString(),pid:e.pid??process.pid,workflowPath:e.workflowPath,workspace:t},c=(0,s.resolve)(i,N);return await this.writeRunRecord(c,o),{changelogPath:(0,s.resolve)(i,`changelog.md`),contextPath:(0,s.resolve)(i,`context.md`),displayName:e.displayName,recordPath:c,runDir:i,runKey:n,workspace:t}}async finalizeRun(e,t){await this.ensureWorkspaceStructure(e.workspace);let n=this.getRunDirectory(e.workspace,t.stage,e.runKey),r=await this.readRunRecord(e.recordPath),i={...r,errorMessage:t.errorMessage,exitCode:t.exitCode,finishedAt:new Date().toISOString(),outcome:t.outcome,pid:r.pid,stage:t.stage};e.runDir!==n&&(await(0,a.rm)(n,{recursive:!0,force:!0}),await(0,a.rename)(e.runDir,n));let o=(0,s.resolve)(n,N);return await this.writeRunRecord(o,i),{...e,recordPath:o,runDir:n}}async readRunRecord(e){try{return this.validateRunRecord(JSON.parse(await(0,a.readFile)(e,`utf-8`)),e)}catch(t){throw t instanceof k?t:new k(e,t instanceof Error?t.message:`Unknown parse failure`,{cause:t})}}async listRuns(e){let t=e?[this.resolveWorkspace(e)]:await this.listWorkspaceNames();return(await Promise.all(t.map(async e=>this.listWorkspaceRuns(e)))).flat().sort((e,t)=>this.compareRunRecordsByLatest(e,t))}async listRunsPage(e={}){let t=e.page??1,n=e.pageSize??20,r=await this.listRuns(e.workspace),i=r.length,a=Math.ceil(i/n),o=(t-1)*n,s=r.slice(o,o+n);return{hasNextPage:t<a,hasPreviousPage:t>1&&i>0,items:s,page:t,pageSize:n,total:i,totalPages:a}}async countRunningWorkflows(e){let t=this.getRunStageDirectory(e,j);if(!await this.pathExists(t))return 0;let n=await(0,a.readdir)(t,{withFileTypes:!0}),r=0;for(let e of n){if(!e.isDirectory())continue;let n=(0,s.resolve)(t,e.name,N);if(await this.pathExists(n))try{let e=await this.readRunRecord(n);e.pid&&this.isProcessAlive(e.pid)&&r++}catch{}}return r}async ensureWorkspaceStructure(e){await(0,a.mkdir)(this.getRunStageDirectory(e,j),{recursive:!0}),await(0,a.mkdir)(this.getRunStageDirectory(e,we),{recursive:!0}),await(0,a.mkdir)(this.getRunStageDirectory(e,M),{recursive:!0})}async findRunStage(e,t){for(let n of[j,we,M])if(await this.pathExists(this.getRunDirectory(e,n,t)))return n;return null}async listWorkspaceRuns(e){let t=new Map;for(let n of[j,we,M]){let r=this.getRunStageDirectory(e,n);if(!await this.pathExists(r))continue;let i=await(0,a.readdir)(r,{withFileTypes:!0});for(let a of i){if(!a.isDirectory())continue;let i=(0,s.resolve)(r,a.name,N);if(!await this.pathExists(i))continue;let o=await this.readListableRunRecord(e,n,a.name,i);o&&t.set(o.runKey,o)}}return[...t.values()]}async findReusableRunStage(e,t){let n=await this.findRunStage(e,t);return n===j?(await this.reconcileStaleRunningRecord(e,t),this.findRunStage(e,t)):n}async inspectRunningRecord(e,t){let n=(0,s.resolve)(this.getRunDirectory(e,j,t),N),r=await this.readRunRecord(n);return!r.pid||this.isProcessAlive(r.pid)?r:{...r,stale:!0,staleReason:`${Ee}: pid ${r.pid}`}}async readListableRunRecord(e,t,n,r){try{return t===j?await this.inspectRunningRecord(e,n):await this.readRunRecord(r)}catch(e){if(e instanceof k)return null;throw e}}async reconcileStaleRunningRecord(e,t){let n=this.getRunDirectory(e,j,t),r=(0,s.resolve)(n,N),i=await this.readRunRecord(r);if(!i.pid||this.isProcessAlive(i.pid))return i;let o=this.getRunDirectory(e,M,t),c={...i,errorMessage:`${Ee}: pid ${i.pid}`,exitCode:130,finishedAt:new Date().toISOString(),outcome:`interrupted`,stage:M};return await(0,a.rm)(o,{recursive:!0,force:!0}),await(0,a.rename)(n,o),await this.writeRunRecord((0,s.resolve)(o,N),c),c}async listWorkspaceNames(){let e=(0,s.resolve)(this.homeDir,Te);return await this.pathExists(e)?(await(0,a.readdir)(e,{withFileTypes:!0})).filter(e=>e.isDirectory()).map(e=>e.name).sort((e,t)=>e.localeCompare(t)):[]}compareRunRecordsByLatest(e,t){let n=e.finishedAt??e.startedAt,r=(t.finishedAt??t.startedAt).localeCompare(n);if(r!==0)return r;let i=e.workspace.localeCompare(t.workspace);return i===0?e.runKey.localeCompare(t.runKey):i}async writeRunRecord(e,t){await(0,a.writeFile)(e,`${JSON.stringify(t,null,2)}\n`,`utf-8`)}validateRunRecord(e,t){let n=Se.safeParse(e);if(!n.success)throw new k(t,n.error.message);return n.data}getRunStageDirectory(e,t){return(0,s.resolve)(this.homeDir,Te,e,t)}getRunDirectory(e,t,n){return(0,s.resolve)(this.getRunStageDirectory(e,t),n)}async pathExists(e){try{return await(0,a.access)(e),!0}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async removeFileIfExists(e){try{await(0,a.unlink)(e)}catch(e){if(e.code!==`ENOENT`)throw e}}slugifySegment(e,t){return e.trim().toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``)||t}isProcessAlive(e){try{return process.kill(e,0),!0}catch(e){let t=e.code;if(t===`ESRCH`)return!1;if(t===`EPERM`)return!0;throw e}}};const Oe=n.z.object({page:n.z.number().int().min(1).optional().describe(`Page number to return. Defaults to 1.`),pageSize:n.z.number().int().min(1).max(100).optional().describe(`Number of workflow runs per page. Defaults to 20 and is capped at 100.`),workspace:n.z.string().optional().describe(`Optional workspace filter. When omitted, runs from all workspaces are returned.`)});var ke=class e{static TOOL_NAME=`list_workflow_statuses`;constructor(e=new De){this.registry=e}getInputSchema(){return Oe}getDefinition(){return{name:e.TOOL_NAME,description:`List tracked workflow runs from the local workflow registry, including their workspace, stage, outcome, and timestamps.`,inputSchema:n.z.toJSONSchema(Oe)}}async execute(e={}){try{let t=Oe.parse(e),n=await this.registry.listRunsPage({page:t.page??1,pageSize:t.pageSize??20,workspace:t.workspace});return{content:[{type:`text`,text:JSON.stringify(n,null,2)}]}}catch(t){let n=new g(`Failed to list workflow statuses.`,`LIST_WORKFLOW_STATUSES_TOOL_FAILED`,{workspace:e.workspace??`all`},{cause:t});return{content:[{type:`text`,text:JSON.stringify({code:n.code,context:n.context,message:n.message},null,2)}],isError:!0}}}},Ae=class extends Error{code=`WORKFLOW_CAPACITY_EXCEEDED`;constructor(e,t,n){super(`Workspace "${e}" is at capacity: ${t}/${n} workflows running.\n → Check running workflows: list-workflow-statuses --workspace ${e}\n → Wait for a running workflow to complete, or cancel one before dispatching again.`),this.workspace=e,this.running=t,this.max=n,this.name=`WorkflowCapacityError`}},P=class extends Error{code=`WORKFLOW_INTERRUPTED`;context;constructor(e,t={}){let n=t.context?` during ${t.context.phase}`:``;super(`Workflow interrupted by ${e}${n}`,{cause:t.cause??t.context}),this.signal=e,this.name=`WorkflowInterruptedError`,this.context=t.context}};const F=`\x1B[0m`,je=`\x1B[1m`,Me=`\x1B[2m`,Ne=`\x1B[31m`,Pe=`\x1B[34m`,Fe=[`pnpm-workspace.yaml`,`nx.json`,`.git`];function Ie(e){let t=(0,s.resolve)(e);for(;;){for(let e of Fe)if((0,c.existsSync)((0,s.join)(t,e)))return t;let n=(0,s.dirname)(t);if(n===t)return e;t=n}}var Le=class{runningServices=[];processRegistry=new d.ProcessRegistryService(process.env.PROCESS_REGISTRY_PATH);portRegistry=new u.PortRegistryService(process.env.PORT_REGISTRY_PATH);constructor(e){this.logger=e}resolveWorkingDirectory(e,t){return e[`working-directory`]?(0,s.resolve)(t,e[`working-directory`]):t}getRunningServices(){return this.runningServices}async startService(e,t,n,i){let a=e.run;if(i)return this.logger.info(` ${Pe}dry ${F} ${e.name}`),this.logger.info(` ${Me}$ ${a}${F}`),null;this.logger.info(` ${Pe}start${F} ${e.name}`),this.logger.info(` ${Me}$ ${a}${F}`);let o={...process.env,...t};if(e.env)for(let[t,n]of Object.entries(e.env))o[t]=String(n);let s=this.resolveWorkingDirectory(e,n),c=Ie(s),l=o.NODE_ENV??process.env.NODE_ENV??`development`,u=e.host??`127.0.0.1`,d;if(e.port!==void 0||e[`port-range`]){let t=await this.portRegistry.reservePort({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,preferredPort:e.port,portRange:e[`port-range`],host:u,force:!0});if(!t.success||t.port===void 0)throw Error(t.error??`Failed to reserve port for background service ${e.name}`);d=t.port,o.PORT??=String(d),o.SERVICE_PORT??=String(d),o.HOST??=u,o.SERVICE_HOST??=u}let f=(0,r.spawn)(a,[],{stdio:[`ignore`,`pipe`,`pipe`],cwd:s,env:{...o,FORCE_COLOR:`1`},shell:`/bin/zsh`,detached:!0});if(f.pid===void 0)throw d!==void 0&&await this.portRegistry.releasePort({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,force:!0}),Error(`Failed to spawn background service ${e.name}`);let p=`${Pe}[${e.name}]${F}`;f.stdout?.on(`data`,e=>{for(let t of e.toString().split(`
5
- `))t.trim()&&process.stdout.write(`${p} ${t}\n`)}),f.stderr?.on(`data`,e=>{for(let t of e.toString().split(`
6
- `))t.trim()&&process.stderr.write(`${p} ${t}\n`)}),f.on(`error`,e=>{this.logger.error(`${p} Process error: ${e.message}`)});let m=async()=>{d!==void 0&&await this.portRegistry.releasePort({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,force:!0})};if(f.pid!==void 0){let t=await this.processRegistry.registerProcess({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,pid:f.pid,port:d,host:d===void 0?void 0:u,command:a,args:[],metadata:{workingDirectory:s,readyCheck:e[`ready-check`]},force:!0});if(!t.success){try{process.kill(-f.pid,`SIGTERM`)}catch{f.kill(`SIGTERM`)}throw d!==void 0&&await this.portRegistry.releasePort({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,force:!0}),Error(t.error??`Failed to register process for background service ${e.name}`)}m=async()=>{let t=await this.processRegistry.releaseProcess({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,pid:f.pid,kill:!0,releasePort:!0,force:!0});if(!t.success&&!t.error?.includes(`No matching process entry`))throw Error(t.error??`Failed to release ${e.name}`)}}let h={name:e.name,process:f,env:o,release:m};return this.runningServices.push(h),h}async waitForServiceReady(e,t,n,i){if(!e[`ready-check`]||typeof e[`ready-check`]!=`string`)return!0;let a=(e[`ready-timeout`]||30)*1e3,o=Date.now(),s=e[`ready-check`],c=this.resolveWorkingDirectory(e,t),l=this.runningServices.find(t=>t.name===e.name)?.env??{...process.env,...n};for(this.logger.info(` ${Me}wait${F} Waiting for ${e.name} to be ready...`);Date.now()-o<a;){if(i?.())return!1;try{return(0,r.execSync)(s,{stdio:`ignore`,cwd:c,env:l,shell:`/bin/zsh`,timeout:5e3}),this.logger.info(` ready${F} ${e.name}`),!0}catch{if(i?.())return!1}await new Promise(e=>{setTimeout(e,1e3)})}return i?.()||this.logger.error(` ${Ne}timeout${F} ${e.name} not ready after ${e[`ready-timeout`]||30}s`),!1}async stopAll(){if(this.runningServices.length!==0){this.logger.info(`\n ${Pe}${je}pre${F} ${je}Stopping services${F}`),this.logger.info(` ${`─`.repeat(50)}`);for(let e of this.runningServices){if(!e.process.killed&&e.process.pid&&this.logger.info(` ${Me}stop${F} ${e.name} (pid ${e.process.pid})`),e.release)try{await e.release();continue}catch(t){this.logger.warn(` ${Ne}warn${F} Failed registry cleanup for ${e.name}: ${t instanceof Error?t.message:String(t)}`)}if(!e.process.killed&&e.process.pid)try{process.kill(-e.process.pid,`SIGTERM`)}catch{e.process.kill(`SIGTERM`)}}this.runningServices=[]}}};const I=`\x1B[0m`,Re=`\x1B[1m`,L=`\x1B[2m`,ze=`\x1B[33m`,Be=`WORKFLOW_JOB_ID`,Ve=`WORKFLOW_JOB_NAME`,He=`PROCESS_REGISTRY_TAG`;var Ue=class{constructor(e,t,n){this.parser=e,this.stepRunner=t,this.logger=n}async runJob(e,t,n,r){let i=this.parser.expandMatrix(t),a=r.maxRetries;for(let o of i){let i=(0,f.randomUUID)(),l=Object.keys(o).length>0?` ${L}(${Object.entries(o).map(([e,t])=>`${e}=${t}`).join(`, `)})${I}`:``;this.logger.info(`\n ${Re}job${I} ${Re}${e}${I}${l}`),this.logger.info(` ${`─`.repeat(50)}`);let u={...n.workflowEnv,[Be]:i,[Ve]:e,[He]:i};if(t.env)for(let[e,r]of Object.entries(t.env))u[e]=this.parser.interpolate(String(r),{inputs:n.inputs,matrix:o,secrets:n.secrets,env:u});u[Be]=i,u[Ve]=e,u[He]=i,this.logger.info(` ${L}jobid${I} ${i}`);let d=[];if(u.WORKFLOW_RUN_DIR){let t=n.jobOrder.map(e=>{let t=n.allJobs[e]?.description;return t?` - ${e}: ${t}`:` - ${e}`}).join(`
7
- `);d.push(`You are currently running job "${e}" in the following workflow pipeline:\n${t}\n\nIf you find bugs, missing features, or issues that need fixes from a previous job, write to ${u.WORKFLOW_RUN_DIR}/fix.md with a YAML frontmatter restart-from field specifying which job should handle the fix, followed by a detailed description. Example:\n---\nrestart-from: development\n---\nDescription of what needs to be fixed...\n\nThe workflow runner will restart from the specified job. Only create fix.md for issues that require code changes — fix minor issues yourself.`)}if(n.workflowSystemPrompt&&d.push(this.parser.interpolate(n.workflowSystemPrompt,{inputs:n.inputs,matrix:o,secrets:n.secrets,env:u})),t[`system-prompt`]&&d.push(this.parser.interpolate(t[`system-prompt`],{inputs:n.inputs,matrix:o,secrets:n.secrets,env:u})),d.length>0){let e=d.join(`
8
-
9
- `);u.JOB_SYSTEM_PROMPT=e,this.logger.info(` ${L}prompt${I} ${e.split(`
10
- `)[0].slice(0,80)}${e.includes(`
11
- `)?`...`:``}`)}let p=t.context?this.parser.interpolate(t.context,{inputs:n.inputs,matrix:o,secrets:n.secrets,env:u}):u.CONTEXT_FILE;if(p){let e=(0,s.resolve)(r.workflowDir,p);(0,c.existsSync)(e)?(u.WORKFLOW_CONTEXT=e,u.WORKFLOW_CONTEXT_FILE=e,this.logger.info(` ${L}ctx ${I} ${p}`)):r.dryRun||this.logger.warn(` ${ze}warn${I} context file not found: ${p}`)}if(u.CHANGELOG_FILE){let e=(0,s.resolve)(r.workflowDir,u.CHANGELOG_FILE),t=(0,s.dirname)(e);(0,c.existsSync)(t)||(0,c.mkdirSync)(t,{recursive:!0}),(0,c.existsSync)(e)||(0,c.writeFileSync)(e,`# Changelog
12
- `,`utf-8`),u.WORKFLOW_CHANGELOG=e,this.logger.info(` ${L}log ${I} changelog at ${u.CHANGELOG_FILE}`)}let m={inputs:n.inputs,matrix:o,secrets:n.secrets,env:u},h=!1,g=t.preJob?.steps??[],_=t.steps??[],v=t.postJob?.steps??[];for(let t=1;t<=a;t++){t>1&&(this.logger.info(`\n ${ze}retry${I} ${Re}${e}${I}${l} (attempt ${t}/${a})`),this.logger.info(` ${`─`.repeat(50)}`));let n=!0;if(g.length>0&&(n=await this.runStepSequence(g,m,r)),n&&=await this.runStepSequence(_,m,r),v.length>0){let e=await this.runStepSequence(v,m,r);n&&=e}if(n){h=!0;break}t<a&&this.logger.warn(` ${ze}Job "${e}" failed, retrying...${I}`)}if(h)this.logger.info(` Job "${e}" completed${I}${l}`);else if(this.logger.error(`\n Job "${e}" failed after ${a} attempts${I}`),t.strategy?.[`fail-fast`]!==!1)return!1}return!0}async runStepSequence(e,t,n){for(let r of e)if(!await this.stepRunner.runStep(r,t,n))return!1;return!0}},We=class extends Error{code=`WORKFLOW_STEP_SPAWN_FAILED`;constructor(e,t={}){super(`Failed to start workflow step "${e.stepName}"`,{cause:t.cause??e}),this.context=e,this.name=`WorkflowStepSpawnError`}},Ge=class extends Error{code=`WORKFLOW_STEP_TIMEOUT_CONFIG_INVALID`;constructor(e,t={}){super(`Invalid ${e.configKey} for workflow step "${e.stepName}"`,{cause:t.cause??e}),this.context=e,this.name=`WorkflowStepTimeoutConfigError`}};const Ke=`/backend-api`;var qe=class{codexHome;fetchFn;now;readTextFile;constructor(e={}){this.codexHome=e.codexHome??(0,s.join)((0,o.homedir)(),`.codex`),this.fetchFn=e.fetchFn??fetch,this.now=e.now??(()=>Date.now()),this.readTextFile=e.readTextFile??(e=>(0,a.readFile)(e,`utf8`))}isCodexCommand(e,t){let n=e.trim();return n===`codex`||n.startsWith(`codex `)}async getQuotaStatus(){let e=await this.readAuthFile(),t=e?.tokens?.access_token?.trim(),n=e?.tokens?.account_id?.trim();if(!t||!n)return null;let r=await this.readChatgptBaseUrl(),i=await this.fetchFn(this.buildUsageUrl(r),{headers:{Authorization:`Bearer ${t}`,"ChatGPT-Account-Id":n,"User-Agent":`codex-cli`}});if(!i.ok)throw Error(`codex quota request failed with HTTP ${i.status}`);let a=await i.json();return{blockingLimit:this.findBlockingLimit(a),planType:a.plan_type??null}}async readAuthFile(){let e=await this.readOptionalTextFile((0,s.join)(this.codexHome,`auth.json`));return e?JSON.parse(e):null}async readChatgptBaseUrl(){let e=(await this.readOptionalTextFile((0,s.join)(this.codexHome,`config.toml`)))?.match(/^\s*chatgpt_base_url\s*=\s*"([^"]+)"/m);return this.normalizeBaseUrl(e?.[1]??`https://chatgpt.com/backend-api/`)}buildUsageUrl(e){return e.includes(Ke)?`${e}/wham/usage`:`${e}/api/codex/usage`}normalizeBaseUrl(e){let t=e.trim().replace(/\/+$/,``);return(t.startsWith(`https://chatgpt.com`)||t.startsWith(`https://chat.openai.com`))&&!t.includes(Ke)&&(t=`${t}${Ke}`),t}async readOptionalTextFile(e){try{return await this.readTextFile(e)}catch(e){if(e.code===`ENOENT`)return null;throw e}}findBlockingLimit(e){let t=[];this.collectBlockingLimit(t,`codex`,`codex`,e.rate_limit);for(let n of e.additional_rate_limits??[])this.collectBlockingLimit(t,n.metered_feature??n.limit_name??`codex`,n.limit_name??n.metered_feature??`codex`,n.rate_limit);return t.sort((e,t)=>e.resetAt-t.resetAt||t.usedPercent-e.usedPercent),t[0]??null}collectBlockingLimit(e,t,n,r){if(!r)return;let i=[{payload:r.primary_window,window:`primary`},{payload:r.secondary_window,window:`secondary`}];for(let{payload:a,window:o}of i){let i=a?.reset_at,s=a?.used_percent??0;typeof i==`number`&&(s>=100||r.limit_reached===!0&&i*1e3>this.now()||r.allowed===!1&&i*1e3>this.now())&&e.push({limitId:t,limitName:n,resetAfterSeconds:a?.reset_after_seconds??null,resetAt:i,usedPercent:s,window:o,windowSeconds:a?.limit_window_seconds??null})}}};const R=`\x1B[0m`,z=`\x1B[2m`,Je=`\x1B[36m`,Ye=`\x1B[32m`,B=`\x1B[33m`,V=`\x1B[31m`,Xe=`SIGINT`,H=`SIGTERM`,Ze=`unnamed`,Qe=`/bin/zsh`,$e=`FORCE_COLOR`,et=`inherit`,tt=`pipe`,nt=` `,rt=`
13
- ${nt}\$ `,it=`skip`,U=`warn`,W=`error`,G=`fail`,at=`interrupted`,ot=`already stopping`,st=`failed to signal`,K=`(continue-on-error)`,ct=`ESRCH`,lt=process.platform!==`win32`,ut=`interactiveRun`,dt=`timeout-minutes`,ft=`timeout-retries`,pt=`retry`,mt=`SIGKILL`,ht=1e3,gt=6e4,_t=Math.floor(2147483647/gt),vt=`WORKFLOW_STEP_DISPLAY`,yt=`workflow-mcp`,bt={status:`status_completed`},xt=new Set([`ENOENT`,`EBUSY`,`EAGAIN`,`EPERM`]),St=`done`,Ct=1e3,wt=n.z.number().positive().finite().max(_t),Tt=n.z.number().int().min(0).max(10).default(2);var Et=class{activeStep=null;activeQuotaWait=null;constructor(e,t,n=new qe,r=c.watch){this.parser=e,this.logger=t,this.quotaService=n,this.watchStatusFile=r}stopActiveStep(e){if(this.activeQuotaWait){if(this.activeQuotaWait.controller.signal.aborted){this.logger.info(` ${z}${it}${R} ${this.activeQuotaWait.stepName} ${ot}`);return}this.activeQuotaWait.signal=e,this.activeQuotaWait.controller.abort(),this.logger.info(` ${B}${at}${R} cancelling quota wait for ${this.activeQuotaWait.stepName}`);return}if(!this.activeStep){this.logger.info(` ${z}${it}${R} no active step to stop`);return}if(this.activeStep.process.killed){this.logger.info(` ${z}${it}${R} ${this.activeStep.stepName} ${ot}`);return}this.logger.info(` ${B}${at}${R} sending ${e} to ${this.activeStep.stepName}`),this.killActiveStep(e)||this.logger.warn(` ${B}${U}${R} ${st} ${this.activeStep.stepName}`)}async runStep(e,t,n){let r=this.resolveRunner(n),i=this.resolveStepCommand(e,t,r),a=e.name?this.parser.interpolate(e.name,t):i?.command.slice(0,60)||e.uses||Ze;if(e.uses){let n=this.parser.interpolate(e.uses,t);return this.parser.isActionSkipped(n)?(this.logger.info(` ${z}${it}${R} ${n}`),!0):(this.logger.warn(` ${B}${U}${R} Unsupported action: ${n} (skipped)`),!0)}if(!i)return!0;if(i.missingRunner)return this.logger.error(` ${V}${W}${R} runner "${i.missingRunner.runner}" not found for step "${a}" (available runner keys: ${i.missingRunner.availableKeys.join(`, `)})`),e[`continue-on-error`]||n.continueOnError?(this.logger.warn(` ${B}${G}${R} ${a} ${K}\n`),!0):(this.logger.error(` ${V}${G}${R} ${a}\n`),!1);let{command:o,interactive:l}=i,u=this.quotaService.isCodexCommand(o,i.agentKey),d=e[`continue-on-error`]||n.continueOnError,f;try{f=this.resolveTimeoutMs(e,a)}catch(e){if(!(e instanceof Ge))throw e;return this.logger.error(` ${V}${W}${R} invalid ${e.context.configKey} for "${a}": ${String(e.context.timeoutValue)}`),d?(this.logger.warn(` ${B}${G}${R} ${a} ${K}\n`),!0):(this.logger.error(` ${V}${G}${R} ${a}\n`),!1)}let p={...process.env,...t.env};if(e.env)for(let[n,r]of Object.entries(e.env))p[n]=this.parser.interpolate(String(r),t);p.WORKFLOW_STEP_NAME=a,p[vt]=this.formatStepDisplay(p.WORKFLOW_JOB_NAME,a);let m;l&&(m=this.createStatusFile(),p.WORKFLOW_STATUS_FILE=m,this.logger.info(` ${z}status-file${R} ${m}`));let h=e[`working-directory`]?(0,s.resolve)(n.workflowDir,this.parser.interpolate(e[`working-directory`],t)):n.workflowDir;if(n.dryRun)return this.logger.info(` ${Je}dry ${R} ${a}`),this.logger.info(`${nt}${z}\$ ${o.split(`
14
- `).join(rt)}${R}`),!0;let g;try{g=this.resolveTimeoutRetries(e,a)}catch(e){if(!(e instanceof Ge))throw e;return this.logger.error(` ${V}${W}${R} invalid ${e.context.configKey} for "${a}": ${String(e.context.timeoutValue)}`),d?(this.logger.warn(` ${B}${G}${R} ${a} ${K}\n`),!0):(this.logger.error(` ${V}${G}${R} ${a}\n`),!1)}let _=!1;for(let e=0;e<=g;e++){await this.waitForCodexQuotaAvailability(a,o,u),e>0?this.logger.warn(` ${B}${pt}${R} retrying step after timeout "${a}" (attempt ${e+1}/${g+1})`):this.logger.info(` ${Je}run ${R} ${a}`),this.logger.info(`${nt}${z}\$ ${o.split(`
15
- `)[0]}${o.includes(`
16
- `)?` ...`:``}${R}`);let t=l?this.withInteractiveTerminalTitle(o,p):o,n=this.executeCommand(t,h,p,a,l,f),r=l&&m?await this.raceStatusFile(n,m,a):await n;if(r.status===`signaled`)throw this.logger.info(`\n ${B}${at}${R} ${a}`),new P(r.signal,{cause:Error(`Step process ended with ${r.signal}`),context:this.createInterruptContext(a,o)});if(r.status===`completed`&&(r.exitCode===130||r.exitCode===143)){let e=r.exitCode===130?Xe:H;throw this.logger.info(`\n ${B}${at}${R} ${a}`),new P(e,{cause:Error(`Step process exited with ${r.exitCode}`),context:this.createInterruptContext(a,o)})}if(r.status===`spawn_error`)return this.logger.error(` ${V}${W}${R} unable to start step "${a}" in ${r.error.context.workDir} (${this.formatSpawnError(r.error)})`),d?(this.logger.warn(` ${B}${G}${R} ${a} ${K}\n`),!0):(this.logger.error(` ${V}${G}${R} ${a}\n`),!1);if(r.status===`timed_out`){if(this.logger.error(` ${V}${W}${R} step timed out after ${this.formatTimeoutMs(r.timeoutMs)} "${a}" (${o.split(`
17
- `)[0]})`),e<g){if(m)try{(0,c.writeFileSync)(m,``,`utf-8`)}catch(e){this.logger.warn(` ${B}${U}${R} status-file reset failed for ${a}: ${e instanceof Error?e.message:String(e)}`)}continue}return m&&this.cleanupStatusFile(m),d?(this.logger.warn(` ${B}${G}${R} ${a} ${K}\n`),!0):(this.logger.error(` ${V}${G}${R} ${a}\n`),!1)}if(m&&this.cleanupStatusFile(m),r.status===`status_completed`)return this.logger.info(`\n ${Ye}${St}${R} ${a} (status-file signaled completion)\n`),!0;if(r.status===`completed`&&r.exitCode===0)return this.logger.info(`\n ${Ye}pass${R} ${a}\n`),!0;if(r.status===`completed`&&this.logger.error(` ${V}${W}${R} step "${a}" exited with code ${r.exitCode} (${o.split(`
18
- `)[0]})`),!_&&await this.shouldRetryForCodexQuota(a,u)){_=!0,this.logger.warn(` ${B}${pt}${R} codex quota reached after failed step, retrying after reset`),--e;continue}return d?(this.logger.warn(` ${B}${G}${R} ${a} ${K}\n`),!0):(this.logger.error(` ${V}${G}${R} ${a}\n`),!1)}return!1}async executeCommand(e,t,n,i,a,o){return a&&this.logger.info(` ${z}(interactive: output renders directly to terminal)${R}`),await new Promise(s=>{let c=a?(0,r.spawn)(e,[],{stdio:[et,et,et],cwd:t,detached:!1,env:{...n,[$e]:`1`},shell:Qe}):(0,r.spawn)(e,[],{stdio:[et,tt,tt],cwd:t,detached:lt,env:{...n,[$e]:`1`},shell:Qe});this.activeStep={command:e,process:c,processGroupId:lt&&!a&&typeof c.pid==`number`?c.pid:null,stepName:i};let l=null,u=!1,d=!1,f=e=>{d||(d=!0,p&&clearTimeout(p),l&&clearTimeout(l),this.activeStep=null,s(e))},p=o===null?null:setTimeout(()=>{if(u=!0,this.killActiveStep(H)){l=setTimeout(()=>{d||(this.logger.warn(` ${B}${U}${R} ${i} forcing SIGKILL`),this.killActiveStep(mt))},ht);return}f({status:`timed_out`,timeoutMs:o})},o);c.stdout?.on(`data`,e=>{for(let t of e.toString().split(`
19
- `))t&&this.logger.info(t)}),c.stderr?.on(`data`,e=>{for(let t of e.toString().split(`
20
- `))t&&this.logger.error(t)}),c.on(`error`,n=>{f({status:`spawn_error`,error:new We({commandPreview:e.split(`
21
- `)[0],source:`StepRunnerService.executeCommand`,stepName:i,workDir:t},{cause:n})})}),c.on(`exit`,(e,t)=>{if(this.cleanupProcessGroup(),u&&o!==null){f({status:`timed_out`,timeoutMs:o});return}if(t===Xe){f({status:`signaled`,signal:Xe});return}if(t===H){f({status:`signaled`,signal:H});return}f({status:`completed`,exitCode:e??1})})})}createInterruptContext(e,t){return{phase:`step_execution`,stepName:e,commandPreview:t.split(`
22
- `)[0],source:`StepRunnerService.runStep`}}formatSpawnError(e){let t=e.cause;return t instanceof Error?`${`code`in t&&typeof t.code==`string`?t.code:`unknown-spawn-error`}${`path`in t&&typeof t.path==`string`?` at ${t.path}`:``}; verify command, shell, and permissions`:`check shell availability, PATH, and permissions`}resolveStepCommand(e,t,n){let r=e[ut]!=null,i=r?{command:e[ut],interactive:!0}:{command:e.run,interactive:!1},a=r?{command:e.run,interactive:!1}:{command:e[ut],interactive:!0},o=this.resolveExactAgentCommand(i.command,t,n);if(o)return{...o,interactive:i.interactive};let s=this.resolveExactAgentCommand(a.command,t,n);if(s)return{...s,interactive:a.interactive};let c=this.resolveMissingExplicitRunner(e,n);if(c)return{command:``,interactive:!1,missingRunner:c};let l=this.resolveStringCommand(i.command,t);if(l)return{command:l,interactive:i.interactive};let u=this.resolveStringCommand(a.command,t);if(u)return{command:u,interactive:a.interactive};let d=this.resolveFirstMappedCommand(i.command,t);if(d)return{command:d,interactive:i.interactive};let f=this.resolveFirstMappedCommand(a.command,t);return f?{command:f,interactive:a.interactive}:null}resolveExactAgentCommand(e,t,n){if(!n||!e||typeof e==`string`)return null;let r=e[n];return r?{agentKey:n,command:this.parser.interpolate(r,t),interactive:!1}:null}resolveStringCommand(e,t){return!e||typeof e!=`string`?null:this.parser.interpolate(e,t)}resolveFirstMappedCommand(e,t){if(!e||typeof e==`string`)return null;let[n]=Object.values(e);return n?this.parser.interpolate(n,t):null}resolveMissingExplicitRunner(e,t){if(!t)return null;let n=this.collectMappedRunnerKeys(e);return n.length===0||n.includes(t)?null:{availableKeys:n,runner:t}}collectMappedRunnerKeys(e){let t=new Set;for(let n of[e[ut],e.run])if(n&&typeof n!=`string`)for(let e of Object.keys(n))t.add(e);return[...t]}resolveRunner(e){return e.runner??e.cliAgent}formatStepDisplay(e,t){return e?`${e} > ${t}`:t}withInteractiveTerminalTitle(e,t){let n=`${yt}: ${t[vt]??e.split(`
23
- `)[0]??Ze}`,r=`[ -t 1 ] && printf '\\033]0;%s\\007' ${this.escapeShellArg(n)}`,i=`[ -t 1 ] && printf '\\033]0;%s\\007' ${this.escapeShellArg(yt)}`;return`trap ${this.escapeShellArg(i)} EXIT
24
- ${r}
25
- ${e}`}escapeShellArg(e){return`'${e.replace(/'/g,`'\\''`)}'`}async shouldRetryForCodexQuota(e,t){return t?(await this.readCodexQuotaStatus(e))?.blockingLimit!=null:!1}async waitForCodexQuotaAvailability(e,t,n){if(n)for(;;){let n=await this.readCodexQuotaStatus(e),r=n?.blockingLimit;if(!r)return;let i=r.resetAt*1e3+Ct,a=Math.max(i-Date.now(),Ct),o=new Date(i).toLocaleString(),s=n?.planType?` (${n.planType})`:``;this.logger.warn(` ${B}wait ${R} codex quota ${r.limitName}/${r.window} is at ${r.usedPercent}%${s}; waiting until ${o}`),await this.waitForQuotaDelay(e,t,a)}}async readCodexQuotaStatus(e){try{return await this.quotaService.getQuotaStatus()}catch(t){return this.logger.warn(` ${B}${U}${R} unable to read codex quota for "${e}" (${this.formatQuotaError(t)})`),null}}async waitForQuotaDelay(e,t,n){let r=new AbortController;this.activeQuotaWait={controller:r,signal:null,stepName:e};try{let e=n;for(;e>0;){let t=Math.min(e,3e4);await this.waitForDelay(t,r.signal),e-=t}}catch(n){throw r.signal.aborted?new P(this.activeQuotaWait?.signal??H,{cause:Error(`Interrupted while waiting for Codex quota reset`),context:{...this.createInterruptContext(e,t),phase:`StepRunnerService.waitForCodexQuota`}}):n}finally{this.activeQuotaWait=null}}async waitForDelay(e,t){await new Promise((n,r)=>{let i=setTimeout(()=>{t.removeEventListener(`abort`,a),n()},e),a=()=>{clearTimeout(i),t.removeEventListener(`abort`,a),r(Error(`aborted`))};t.addEventListener(`abort`,a,{once:!0})})}formatQuotaError(e){return e instanceof Error&&e.message.trim().length>0?e.message:`unknown quota error`}resolveTimeoutMs(e,t){let n=e[dt];if(n===void 0)return null;try{let e=wt.parse(n);return Math.ceil(e*gt)}catch(e){throw new Ge({stepName:t,configKey:dt,timeoutValue:n},{cause:e})}}resolveTimeoutRetries(e,t){let n=e[ft];try{return Tt.parse(n)}catch(e){throw new Ge({stepName:t,configKey:ft,timeoutValue:n},{cause:e})}}formatTimeoutMs(e){return e%gt===0?`${e/gt} minute(s)`:`${e}ms`}cleanupProcessGroup(){if(!this.activeStep)return;let{processGroupId:e,stepName:t}=this.activeStep;if(e!==null)try{process.kill(-e,H)}catch(n){let r=n.code;r===ct?this.logger.info(` ${z}cleanup${R} process group ${e} already exited (ESRCH)`):this.logger.warn(` ${B}${U}${R} failed to clean up process group for ${t} (pgid=${e}, error=${r})`)}}killActiveStep(e){if(!this.activeStep)return!1;let{process:t,processGroupId:n}=this.activeStep;if(n!==null)try{return process.kill(-n,e),!0}catch(e){e.code===ct?this.logger.info(` ${z}signal${R} process group for ${this.activeStep.stepName} already exited (ESRCH)`):this.logger.warn(` ${B}${U}${R} ${st} process group for ${this.activeStep.stepName}`)}try{return t.kill(e)}catch(e){return this.logger.warn(` ${B}${U}${R} ${st} ${this.activeStep.stepName} (${e.message})`),!1}}createStatusFile(){let e=(0,s.join)((0,o.tmpdir)(),`workflow-step-${(0,f.randomUUID)()}.status`);return(0,c.writeFileSync)(e,``,`utf-8`),e}async raceStatusFile(e,t,n){return new Promise(r=>{let i=!1,a=!1,o=null,s=null,l=null,u=e=>{i||(i=!0,s&&=(s.close(),null),l&&=(clearInterval(l),null),o&&=(clearTimeout(o),null),r(e))},d=!1,f=!1,p=()=>{try{return(0,c.existsSync)(t)&&(0,c.readFileSync)(t,`utf-8`).trim()===`YES`}catch(e){let t=e.code;return!t||!xt.has(t)?this.logger.warn(` ${B}${U}${R} status-file read failed for ${n}: ${e instanceof Error?e.message:String(e)}`):this.logger.info(` ${z}status-file${R} transient FS error (${t}) polling ${n}`),!1}},m=()=>{if(!(a||!p())){if(a=!0,d=this.killActiveStep(H),!d){this.logger.warn(` ${B}${U}${R} ${n} status-file: SIGTERM could not be delivered, process may have already exited`);return}o=setTimeout(()=>{i||(f=!0,this.logger.warn(` ${B}${U}${R} ${n} did not exit after status-file SIGTERM, forcing SIGKILL`),this.killActiveStep(mt))},ht)}};try{s=this.watchStatusFile(t,()=>m())}catch(e){this.logger.warn(` ${B}${U}${R} status-file watcher setup failed for ${n}: ${e instanceof Error?e.message:String(e)}`)}l=setInterval(m,1e3),m(),e.then(e=>{if(!(a||p())){u(e);return}switch(a||(a=!0,this.logger.info(` ${Ye}${St}${R} ${n} status-file observed during process exit handling`)),e.status){case`spawn_error`:case`timed_out`:u(e);return;case`signaled`:f&&this.logger.warn(` ${B}${U}${R} ${n} required SIGKILL escalation (work completed via status-file)`),u(bt);return;case`completed`:e.exitCode===0||e.exitCode>128||f||!d?(f&&this.logger.warn(` ${B}${U}${R} ${n} required SIGKILL escalation (work completed via status-file, exit code ${e.exitCode})`),u(bt)):u(e);return;case`status_completed`:u(e);return;default:u(e)}})})}cleanupStatusFile(e){try{(0,c.existsSync)(e)&&(0,c.unlinkSync)(e)}catch(e){this.logger.warn(` ${B}${U}${R} status-file cleanup failed: ${e instanceof Error?e.message:String(e)}`)}}};const q=`\x1B[0m`,Dt=`\x1B[1m`,Ot=`\x1B[2m`,kt=`\x1B[36m`,At=`\x1B[32m`,jt=`\x1B[31m`,Mt=`\x1B[34m`;var Nt=class{activePrompt=null;activePromptSignal=null;constructor(e,t){this.stepRunner=e,this.logger=t}async pathExists(e){try{return await(0,a.access)(e),!0}catch{return!1}}abortActivePrompt(e){this.activePromptSignal=e,this.activePrompt?.close()}async handleUserPrompt(e,t,n,r,i){if(!e.on||!(`user_prompt`in e.on))return!0;this.logger.info(`\n ${Mt}${Dt}trigger${q} ${Dt}user_prompt${q}`),this.logger.info(` ${`─`.repeat(50)}`);let o=t,c=n.CONTEXT_FILE;if(!o&&c){let e=(0,s.resolve)(r,c);await this.pathExists(e)&&(o=(await(0,a.readFile)(e,`utf-8`)).trim(),o&&this.logger.info(` ${At}found${q} Existing context.md -> ${c}`))}if(!o)if(i)this.logger.info(` ${kt}dry ${q} Would prompt user for input`),o=`(dry-run: no prompt collected)`;else{this.logger.info(` ${kt}input${q} Enter your prompt (press Enter twice to finish):\n`);let e=[],t=(0,l.createInterface)({input:process.stdin,output:process.stdout});this.activePrompt=t,this.activePromptSignal=null,o=await new Promise((n,r)=>{let i=!1,a=e=>{i||(i=!0,this.activePrompt=null,e())};t.on(`line`,n=>{n===``&&e.length>0&&e[e.length-1]===``?t.close():e.push(n)}),t.on(`SIGINT`,()=>{this.activePromptSignal=`SIGINT`,t.close()}),t.on(`close`,()=>a(()=>{let t=this.activePromptSignal;if(this.activePromptSignal=null,t){r(new P(t));return}e.length>0&&e[e.length-1]===``&&e.pop(),n(e.join(`
26
- `))}))})}if(!o)return this.logger.error(` ${jt}fail${q} No prompt provided. Aborting.`),!1;if(c){let e=(0,s.resolve)(r,c);await(0,a.mkdir)((0,s.dirname)(e),{recursive:!0}),await(0,a.writeFile)(e,o,`utf-8`),this.logger.info(` ${At}saved${q} ${o.split(`
27
- `).length} line(s) -> ${c}`)}return n.USER_PROMPT=o,this.logger.info(` ${Ot}prompt${q} ${o.split(`
28
- `)[0].slice(0,80)}${o.includes(`
29
- `)?`...`:``}`),!0}async handleAgentDecision(e,t,n,r,i,o){let c=e.on?.agent_decision;if(!c?.steps)return!0;this.logger.info(`\n ${Mt}${Dt}trigger${q} ${Dt}agent_decision${q}`),this.logger.info(` ${`─`.repeat(50)}`),o&&(t.HEARTBEAT_PROMPT=o,this.logger.info(` ${Ot}prompt${q} ${o.split(`
30
- `)[0].slice(0,80)}${o.includes(`
31
- `)?`...`:``}`));let l=t.CONTEXT_FILE;l&&await(0,a.mkdir)((0,s.dirname)((0,s.resolve)(i.workflowDir,l)),{recursive:!0});let u={inputs:n,matrix:{},secrets:r,env:t};for(let e of c.steps)if(!await this.stepRunner.runStep(e,u,i))return this.logger.error(`\n ${jt}Agent decision aborted workflow${q}`),!1;if(l&&!i.dryRun){let e=(0,s.resolve)(i.workflowDir,l);if(!await this.pathExists(e))return this.logger.error(` ${jt}fail${q} Context file not created: ${l}`),!1;let t=(await(0,a.readFile)(e,`utf-8`)).trim();if(!t)return this.logger.error(` ${jt}fail${q} Context file is empty: ${l}`),!1;this.logger.info(` ${At}ready${q} context has ${t.split(`
32
- `).length} line(s)`)}return!0}};const Pt=[`actions/checkout`,`actions/setup-node`,`actions/cache`,`actions/upload-artifact`,`actions/download-artifact`,`pnpm/action-setup`],Ft=`bold.calm.cool.dark.deep.fair.fast.free.gold.keen.kind.loud.neat.pure.rare.safe.slim.soft.tall.warm.wild.wise.blue.gray.jade.iron.zinc.ruby.opal.onyx`.split(`.`),It=`arch.beam.bird.bolt.cape.cove.dawn.dove.echo.fern.flux.gale.hawk.haze.iris.jade.kite.lake.lynx.mesa.moth.node.palm.peak.pine.raft.reef.sage.tide.vale`.split(`.`);var Lt=class{generateHumanReadableId(){return`${Ft[Math.floor(Math.random()*Ft.length)]}-${It[Math.floor(Math.random()*It.length)]}`}parseWorkflowFile(e){let t=(0,s.resolve)(e);if(!(0,c.existsSync)(t))throw Error(`Workflow file not found: ${t}`);let n=(0,c.readFileSync)(t,`utf-8`),r=ye.parse((0,p.load)(n));if(r.imports&&this.resolveImports(r,t),!r.jobs||Object.keys(r.jobs).length===0)throw Error(`No jobs found in workflow file`);return this.resolveExtends(r),r}resolveImports(e,t,n=new Set){let r=(0,s.resolve)(t);if(n.has(r))throw Error(`Circular import detected: ${r}`);n.add(r);let i=r.replace(/\/[^/]+$/,``);for(let t of e.imports??[]){let a=(0,s.resolve)(i,t);if(!(0,c.existsSync)(a))throw Error(`Imported workflow file not found: ${a} (from ${r})`);let o=(0,p.load)((0,c.readFileSync)(a,`utf-8`));if(o.imports&&this.resolveImports(o,a,n),o.env&&(e.env={...o.env,...e.env}),o.pre&&(e.pre||={},o.pre.services&&(e.pre.services=[...o.pre.services,...e.pre.services??[]]),o.pre.steps&&(e.pre.steps=[...o.pre.steps,...e.pre.steps??[]])),o.post&&(e.post||={},o.post.services&&(e.post.services=[...o.post.services,...e.post.services??[]]),o.post.steps&&(e.post.steps=[...o.post.steps,...e.post.steps??[]])),o.worktree){e.worktree||={};for(let t of[`beforeCreate`,`afterCreate`,`beforeCompleted`,`afterCompleted`])o.worktree[t]&&!e.worktree[t]&&(e.worktree[t]=o.worktree[t])}if(o[`max-workflows`]&&!e[`max-workflows`]&&(e[`max-workflows`]=o[`max-workflows`]),o[`launch-command`]&&!e[`launch-command`]&&(e[`launch-command`]=o[`launch-command`]),o.keybindings&&(e.keybindings={...o.keybindings,...e.keybindings}),o[`system-prompt`]&&(e[`system-prompt`]?e[`system-prompt`]=`${o[`system-prompt`]}\n\n${e[`system-prompt`]}`:e[`system-prompt`]=o[`system-prompt`]),o.jobs)for(let[t,n]of Object.entries(o.jobs))t in(e.jobs??{})||(e.jobs||={},e.jobs[t]=n)}delete e.imports}resolveExtends(e){let{jobs:t}=e,n=new Map;for(let[e,r]of Object.entries(t))e.startsWith(`.`)&&n.set(e,r);for(let[e,r]of Object.entries(t)){if(e.startsWith(`.`)||!r.extends)continue;let t=Array.isArray(r.extends)?r.extends:[r.extends],i=[...r.steps??[]],a=[...r.preJob?.steps??[]],o=[...r.postJob?.steps??[]],s=[],c=[],l=[];for(let i of t){let t=n.get(i);if(!t)throw Error(`Job "${e}" extends "${i}" but template not found`);s.push(...t.steps??[]),c.push(...t.preJob?.steps??[]),l.push(...t.postJob?.steps??[]),this.applyTemplate(r,t)}r.steps=[...s,...i],this.assignJobHookSteps(r,`preJob`,[...c,...a]),this.assignJobHookSteps(r,`postJob`,[...l,...o]),delete r.extends}for(let e of n.keys())delete t[e]}applyTemplate(e,t){e.steps=[...t.steps??[],...e.steps??[]],this.mergeJobHookSteps(e,t,`preJob`),this.mergeJobHookSteps(e,t,`postJob`),(t.env||e.env)&&(e.env={...t.env,...e.env}),t[`system-prompt`]&&e[`system-prompt`]?e[`system-prompt`]=`${t[`system-prompt`]}\n\n${e[`system-prompt`]}`:t[`system-prompt`]&&(e[`system-prompt`]=t[`system-prompt`]),t[`runs-on`]&&!e[`runs-on`]&&(e[`runs-on`]=t[`runs-on`]),t.strategy&&!e.strategy&&(e.strategy=t.strategy),t.context&&!e.context&&(e.context=t.context)}mergeJobHookSteps(e,t,n){let r=t[n]?.steps??[],i=e[n]?.steps??[];this.assignJobHookSteps(e,n,[...r,...i])}assignJobHookSteps(e,t,n){if(n.length>0){e[t]={steps:n};return}delete e[t]}loadSecrets(e){let t=new Map;if(!(0,c.existsSync)(e))return t;let n=(0,c.readFileSync)(e,`utf-8`);for(let e of n.split(`
33
- `)){let n=e.trim();if(!n||n.startsWith(`#`))continue;let r=n.indexOf(`=`);r!==-1&&t.set(n.slice(0,r),n.slice(r+1))}return t}interpolate(e,t){return e.replace(/\$\{\{\s*(.+?)\s*\}\}/g,(e,n)=>{let i=n.trim().split(`.`);if(i[0]===`inputs`&&i[1])return t.inputs.get(i[1])||``;if(i[0]===`matrix`&&i[1])return t.matrix[i[1]]||``;if(i[0]===`secrets`&&i[1])return t.secrets.get(i[1])||process.env[i[1]]||``;if(i[0]===`env`&&i[1])return t.env[i[1]]||process.env[i[1]]||``;if(i[0]===`runner`&&i[1]===`os`)return`macOS`;if(i[0]===`github`&&i[1]===`sha`)try{return(0,r.execSync)(`git rev-parse HEAD`,{encoding:`utf-8`,stdio:[`ignore`,`pipe`,`ignore`]}).trim()}catch{return`local`}return n.includes(`hashFiles`)?`local`:`\${{ ${n} }}`})}isActionSkipped(e){return Pt.some(t=>e.startsWith(t))}resolveJobOrder(e,t){let n=new Set,r=[],i=t=>{if(n.has(t))return;n.add(t);let a=e[t];if(!a)throw Error(`Job not found: ${t}`);let o=a.needs?Array.isArray(a.needs)?a.needs:[a.needs]:[];for(let e of o)i(e);r.push(t)};if(t)i(t);else for(let t of Object.keys(e))i(t);return r}expandMatrix(e){if(!e.strategy?.matrix)return[{}];let{include:t,...n}=e.strategy.matrix;if(t&&t.length>0)return t;let r=Object.keys(n).filter(e=>Array.isArray(n[e]));if(r.length===0)return[{}];let i=[{}];for(let e of r){let t=n[e],r=[];for(let n of i)for(let i of t)r.push({...n,[e]:String(i)});i=r}return i}};const Rt=`\x1B[0m`,zt=`\x1B[2m`;var Bt=class{constructor(e,t){this.stepRunner=e,this.logger=t}createWorktree(e,t){let n=t.toLowerCase().replace(/[^a-z0-9-]+/g,`-`).replace(/^-|-$/g,``),i=(0,s.resolve)((0,s.dirname)(e),`${(0,s.basename)(e)}-worktree`),a=(0,s.resolve)(i,n);(0,c.existsSync)(i)||(0,c.mkdirSync)(i,{recursive:!0});let o=`worktree/${n}`;(0,c.existsSync)(a)&&(this.logger.info(` ${zt}clean${Rt} Removing stale worktree at ${a}`),this.cleanupWorktreePath(e,a));try{(0,r.execSync)(`git branch -D "${o}"`,{cwd:e,stdio:[`ignore`,`pipe`,`ignore`]})}catch{}return this.logger.info(` create${Rt} Worktree at ${a} (branch: ${o})`),(0,r.execSync)(`git worktree add -b "${o}" "${a}" HEAD`,{cwd:e,stdio:[`ignore`,`pipe`,`ignore`]}),a}removeWorktree(e,t){if((0,c.existsSync)(t)){this.logger.info(` ${zt}remove${Rt} Worktree at ${t}`);try{this.cleanupWorktreePath(e,t)}catch{this.logger.warn(` warn${Rt} Failed to remove worktree (may need manual cleanup)`)}}}cleanupWorktreePath(e,t){if(this.isRegisteredWorktree(e,t)){(0,r.execSync)(`git worktree remove --force "${t}"`,{cwd:e,stdio:[`ignore`,`pipe`,`ignore`]});return}(0,c.rmSync)(t,{recursive:!0,force:!0})}isRegisteredWorktree(e,t){try{return(0,r.execSync)(`git worktree list --porcelain`,{cwd:e,encoding:`utf-8`,stdio:[`ignore`,`pipe`,`ignore`]}).split(`
34
- `).some(e=>e.startsWith(`worktree `)&&(0,s.resolve)(e.slice(9))===t)}catch{return!1}}async runHook(e,t,n,r,i,a){let o={inputs:r,matrix:{},secrets:i,env:n};for(let n of e.steps)if(!await this.stepRunner.runStep(n,o,{...a,workflowDir:t}))return!1;return!0}};const J=`\x1B[0m`,Y=`\x1B[1m`,Vt=`\x1B[2m`,Ht=`\x1B[32m`,X=`\x1B[33m`,Z=`\x1B[31m`,Q=`\x1B[34m`,$=`SIGINT`,Ut=`SIGTERM`,Wt=`workflow_execution`,Gt=`restart-from`,Kt=`user_prompt`,qt=`agent_decision`,Jt=`workflow_dispatch`,Yt=`Workflow post-cleanup failed`,Xt=`STOP`,Zt=`shift`,Qt=`ctrl`,$t=`meta`;var en=class{logger;parser;serviceManager;stepRunner;jobRunner;triggerService;worktreeService;registry;outputLines=[];interruptedSignal=null;constructor(e,t={}){let n=e||{info:e=>process.stdout.write(`${e}\n`),error:e=>console.error(e),warn:e=>console.error(e)},r=e=>{this.outputLines.push(e),this.outputLines.length>5e3&&this.outputLines.shift()};this.logger={info:e=>{r(e),n.info(e)},error:e=>{r(e),n.error(e)},warn:e=>{r(e),n.warn(e)}},this.parser=t.parser??new Lt,this.serviceManager=t.serviceManager??new Le(this.logger),this.stepRunner=t.stepRunner??new Et(this.parser,this.logger),this.jobRunner=t.jobRunner??new Ue(this.parser,this.stepRunner,this.logger),this.triggerService=t.triggerService??new Nt(this.stepRunner,this.logger),this.worktreeService=t.worktreeService??new Bt(this.stepRunner,this.logger),this.registry=t.registry??new De}async run(e){this.outputLines=[],this.interruptedSignal=null;let t=e.dryRun??!1,n=e.continueOnError??!1,r=!1,i=e=>{r||(r=!0,this.interrupt(e))},a=()=>i($),o=()=>i(Ut);process.on($,a),process.on(Ut,o);try{return await this.executeWorkflow(e,t,n,e.keepWorktree??!1)}catch(e){if(e instanceof P||this.isInterrupted())return await this.serviceManager.stopAll(),this.createInterruptedResult();throw e}finally{process.off($,a),process.off(Ut,o),await this.serviceManager.stopAll()}}interrupt(e,t={phase:Wt}){if(this.interruptedSignal)return;this.interruptedSignal=e;let n=t.phase===Wt?``:` (${t.phase})`;this.logger.info(`\n\n${X}${Y}Interrupted — shutting down...${J}${n}`),this.triggerService.abortActivePrompt(e),this.stepRunner.stopActiveStep(e),this.serviceManager.stopAll()}isInterrupted(){return this.interruptedSignal===$||this.interruptedSignal===Ut}createInterruptedResult(){return{exitCode:130,output:this.outputLines.join(`
35
- `)}}parseFixRestartFrom(e,t){if(!e.startsWith(`---`))return{};let n=e.indexOf(`---`,3);if(n<0)return{};let r=e.slice(3,n);for(let e of r.split(`
36
- `)){let n=e.trim();if(n.startsWith(`${Gt}:`)){let e=n.slice(`${Gt}:`.length).trim();return t.includes(e)?{restartFrom:e}:{invalidTarget:e}}}return{}}async pathExists(e){try{return await(0,a.access)(e),!0}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async waitForServiceStartup(){await new Promise(e=>{setTimeout(e,2e3)})}resolveRunner(e){if(e.runner&&e.cliAgent&&e.runner!==e.cliAgent)throw Error(`Conflicting runner selectors: runner="${e.runner}" cliAgent="${e.cliAgent}"`);return e.runner??e.cliAgent}async executeWorkflow(e,t,n,i=!1){let o=(0,s.resolve)(e.workflowPath),c=this.parser.parseWorkflowFile(o),l=this.resolveRunner(e),u=this.installStopKeybinding(c.keybindings?.[Xt]),d=null,f=null,p=null,m=(0,s.dirname)(o),h=m,g=null,_=!1,v=!1,y=!!c.worktree,b=new Map,x=e.secretFile?this.parser.loadSecrets(e.secretFile):new Map,S={};try{let u=c.on?.[Jt]?.inputs||{};for(let[e,t]of Object.entries(u))t.default&&b.set(e,t.default);if(e.inputs)for(let[t,n]of Object.entries(e.inputs))b.set(t,n);if(c.env)for(let[e,t]of Object.entries(c.env))S[e]=String(t);if(e.env)for(let[t,n]of Object.entries(e.env))S[t]=n;l&&(S.WORKFLOW_RUNNER=l,S.WORKFLOW_CLI_AGENT=l);let p=m;for(;p!==`/`;){if(await this.pathExists((0,s.resolve)(p,`.git`))){m=p;break}p=(0,s.dirname)(p)}let C=c[`max-workflows`];if(C){let t=this.registry.resolveWorkspace(e.workspace,c.workspace),n=await this.registry.countRunningWorkflows(t);if(n>=C)throw new Ae(t,n,C)}let ee=c[`launch-command`];if(ee&&!e.skipLaunch){let t=this.buildLaunchCliCommand(e,o),n=e.name||c.name||(0,s.basename)(o),i=e.name?n:`${n}-${this.parser.generateHumanReadableId()}`,a=ee.replace(`{name}`,i).replace(`{command}`,t);this.logger.info(`${Vt}Delegating via launch-command: ${a}${J}`);try{return(0,r.execSync)(a,{stdio:`inherit`,cwd:m}),{exitCode:0,output:this.outputLines.join(`
37
- `)}}catch(e){return{exitCode:e.status??1,output:this.outputLines.join(`
38
- `)}}}let te=e.name||c.name||(0,s.basename)(o),w=e.name?te:`${te}-${this.parser.generateHumanReadableId()}`;if(d=await this.registry.createRun({displayName:w,dryRun:t,workflowPath:o,workflowWorkspace:c.workspace,workspace:e.workspace}),S.WORKFLOW_RUN_DIR=d.runDir,S.WORKFLOW_WORKSPACE=d.workspace,S.CONTEXT_FILE&&(S.CONTEXT_FILE=d.contextPath,S.CHANGELOG_FILE=d.changelogPath),this.logger.info(`${Y}${`═`.repeat(60)}${J}`),this.logger.info(` ${Y}run-workflow${J} - ${d.displayName}`),this.logger.info(`${Y}${`═`.repeat(60)}${J}`),this.logger.info(` File: ${o}`),this.logger.info(` Workspace: ${d.workspace||`default`}`),this.logger.info(` Run Dir: ${d.runDir}`),this.logger.info(` CWD: ${m}`),b.size>0&&this.logger.info(` Inputs: ${[...b.entries()].map(([e,t])=>`${e}=${t}`).join(`, `)}`),x.size>0&&this.logger.info(` Secrets: ${[...x.keys()].join(`, `)}`),e.job&&this.logger.info(` Target: ${e.job}`),l&&this.logger.info(` Runner: ${l}`),c.keybindings?.[Xt]&&this.logger.info(` Stop Key: ${c.keybindings[Xt]}`),c.pre){let e=c.pre.services?.length||0,t=c.pre.steps?.length||0;this.logger.info(` Pre: ${e} service(s), ${t} setup step(s)`)}if(c.post){let e=c.post.services?.length||0,t=c.post.steps?.length||0;this.logger.info(` Post: ${e} service(s), ${t} cleanup step(s)`)}y&&this.logger.info(` Worktree: enabled${i?` (keep on completion)`:``}`);let T=c.on&&Kt in c.on?Kt:c.on?.[qt]?qt:Jt;if(this.logger.info(` Trigger: ${T}`),this.logger.info(` Mode: ${t?`dry-run`:`execute`}`),this.logger.info(`${Y}${`═`.repeat(60)}${J}`),!await this.triggerService.handleUserPrompt(c,e.prompt||null,S,m,t))return this.isInterrupted()?(f=this.createInterruptedResult(),f):(this.logger.error(`\n${Z}${Y}Workflow aborted: no prompt provided${J}`),f={exitCode:1,output:this.outputLines.join(`
39
- `)},f);if(!await this.triggerService.handleAgentDecision(c,S,b,x,{runner:l,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,workflowDir:m},e.prompt||null))return this.isInterrupted()?(f=this.createInterruptedResult(),f):(this.logger.info(`\n${X}${Y}Workflow skipped: agent decided nothing to do${J}`),f={exitCode:2,output:this.outputLines.join(`
40
- `)},f);if(h=m,y&&!t)c.worktree?.beforeCreate&&(this.logger.info(`\n ${Q}${Y}worktree${J} ${Y}beforeCreate${J}`),this.logger.info(` ${`─`.repeat(50)}`),await this.worktreeService.runHook(c.worktree.beforeCreate,m,S,b,x,{runner:l,cliAgent:e.cliAgent,dryRun:t,continueOnError:n})||(this.isInterrupted()?f=this.createInterruptedResult():(this.logger.error(`\n${Z}${Y}Workflow aborted: worktree beforeCreate hook failed${J}`),f={exitCode:1,output:this.outputLines.join(`
41
- `)}))),f||(this.logger.info(`\n ${Q}${Y}worktree${J} ${Y}Creating worktree${J}`),this.logger.info(` ${`─`.repeat(50)}`),g=this.worktreeService.createWorktree(m,d.displayName),m=g,S.WORKTREE_PATH=g,S.WORKFLOW_NAME=d.displayName,S.ORIGINAL_REPO_PATH=h,c.worktree?.afterCreate&&(this.logger.info(`\n ${Q}${Y}worktree${J} ${Y}afterCreate${J}`),this.logger.info(` ${`─`.repeat(50)}`),await this.worktreeService.runHook(c.worktree.afterCreate,g,S,b,x,{runner:l,cliAgent:e.cliAgent,dryRun:t,continueOnError:n})||(this.isInterrupted()?f=this.createInterruptedResult():(this.logger.error(`\n${Z}${Y}Workflow aborted: worktree afterCreate hook failed${J}`),f={exitCode:1,output:this.outputLines.join(`
42
- `)}),v=!i)));else if(y&&t){let e=(0,s.dirname)(m),t=(0,s.basename)(m),n=d.displayName.toLowerCase().replace(/[^a-z0-9-]+/g,`-`).replace(/^-|-$/g,``),r=(0,s.resolve)(e,`${t}-worktree`,n);this.logger.info(`\n ${Q}${Y}worktree${J} ${Y}(dry-run)${J} Would create at ${r}`)}!f&&c.pre&&(await this.runPreBlock(c.pre,S,b,x,{runner:l,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,workflowDir:m})||(this.isInterrupted()?f=this.createInterruptedResult():(this.logger.error(`\n${Z}${Y}Workflow aborted: pre-conditions failed${J}`),f={exitCode:1,output:this.outputLines.join(`
43
- `)}),v=!!(g&&!i)));let ne=Number(S.MAX_RETRIES)||3,E=this.parser.resolveJobOrder(c.jobs,e.job||null);this.logger.info(`\n ${Vt}Job order: ${E.join(` -> `)} (max retries: ${ne})${J}`);let re=new Set,D=S.WORKFLOW_RUN_DIR?(0,s.resolve)(S.WORKFLOW_RUN_DIR,`fix.md`):null,O=0,k=0;for(;!f&&k<E.length;){let r=E[k],i=c.jobs[r],o=await this.jobRunner.runJob(r,i,{inputs:b,secrets:x,workflowEnv:S,workflowSystemPrompt:c[`system-prompt`],jobOrder:E,allJobs:c.jobs},{runner:l,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,workflowDir:m,maxRetries:ne});if(this.isInterrupted()&&(f=this.createInterruptedResult()),!f&&!o&&(this.logger.error(`\n${Z}${Y}Workflow failed at job "${r}"${J}`),await this.serviceManager.stopAll(),f={exitCode:1,output:this.outputLines.join(`
44
- `)}),!f){if(re.add(r),D&&await this.pathExists(D)&&!t){if(O++,O>3){this.logger.error(`\n${Z}${Y}Fix loop limit reached (3 cycles). Aborting.${J}`),f={exitCode:1,output:this.outputLines.join(`
45
- `)};continue}let e=(await(0,a.readFile)(D,`utf-8`)).trim();await(0,a.unlink)(D);let{restartFrom:t,invalidTarget:n}=this.parseFixRestartFrom(e,E);if(this.logger.info(`\n${X}${Y}fix.md detected after "${r}" (cycle ${O}/3)${J}`),this.logger.info(`${Vt}${e.split(`
46
- `)[0].slice(0,100)}${e.includes(`
47
- `)?`...`:``}${J}`),n&&this.logger.warn(`${X}fix.md restart-from "${n}" is not a valid job (available: ${E.join(`, `)}), falling back to default${J}`),S.CONTEXT_FILE&&await this.pathExists(S.CONTEXT_FILE)){let t=await(0,a.readFile)(S.CONTEXT_FILE,`utf-8`);await(0,a.writeFile)(S.CONTEXT_FILE,`${t}\n\n---\n## Fix Request (cycle ${O})\n${e}`,`utf-8`)}let i=t?E.indexOf(t):-1,o=E.indexOf(`development`);k=i>=0?i:o>=0?o:1,this.logger.info(`${X}Restarting from job "${E[k]}"${J}\n`);continue}k++}}!f&&this.isInterrupted()&&(f=this.createInterruptedResult()),f||=(_=!0,this.logger.info(`\n${Y}${`═`.repeat(60)}${J}`),this.logger.info(` ${Ht}${Y}Workflow completed successfully${J}`),this.logger.info(` Jobs run: ${[...re].join(`, `)}`),g&&this.logger.info(` Worktree: ${g}`),this.logger.info(`${Y}${`═`.repeat(60)}${J}\n`),{exitCode:0,output:this.outputLines.join(`
48
- `)})}catch(e){if(p=e,e instanceof P||this.isInterrupted())f=this.createInterruptedResult();else throw e}finally{if(u?.(),_&&f?.exitCode===0&&y&&c.worktree?.beforeCompleted&&!t&&g&&(this.logger.info(`\n ${Q}${Y}worktree${J} ${Y}beforeCompleted${J}`),this.logger.info(` ${`─`.repeat(50)}`),await this.worktreeService.runHook(c.worktree.beforeCompleted,g,S,b,x,{runner:l,cliAgent:e.cliAgent,dryRun:t,continueOnError:n})||(this.isInterrupted()?f=this.createInterruptedResult():this.logger.warn(`\n${X}${Y}Warning: worktree beforeCompleted hook failed${J}`))),c.post)try{await this.runPostBlock(c.post,S,b,x,{runner:l,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,workflowDir:m})||(this.logger.error(`\n${Z}${Y}${Yt}${J}`),(!f||f.exitCode===0||f.exitCode===2)&&(f={exitCode:1,output:this.outputLines.join(`
49
- `)}))}catch(e){p??=e,this.logger.error(`\n${Z}${Y}${Yt}${J}`),(!f||f.exitCode===0||f.exitCode===2)&&(f={exitCode:1,output:this.outputLines.join(`
50
- `)})}_&&f?.exitCode===0&&y&&c.worktree?.afterCompleted&&!t&&!i&&(this.logger.info(`\n ${Q}${Y}worktree${J} ${Y}afterCompleted${J}`),this.logger.info(` ${`─`.repeat(50)}`),await this.worktreeService.runHook(c.worktree.afterCompleted,h,S,b,x,{runner:l,cliAgent:e.cliAgent,dryRun:t,continueOnError:n})||(this.isInterrupted()?f=this.createInterruptedResult():this.logger.warn(`\n${X}${Y}Warning: worktree afterCompleted hook failed${J}`))),v&&g&&this.worktreeService.removeWorktree(h,g),f&&={...f,output:this.outputLines.join(`
51
- `)},d&&await this.finalizeRegistryRun(d,f,p)}return f??{exitCode:1,output:this.outputLines.join(`
52
- `)}}installStopKeybinding(e){if(!e)return null;let t=this.parseKeybinding(e);if(!t)return this.logger.warn(` ${X}Keybinding ${Xt} ignored: unsupported value "${e}"${J}`),null;if(!process.stdin.isTTY||typeof process.stdin.setRawMode!=`function`)return null;let n=process.stdin,r=n.isRaw;(0,l.emitKeypressEvents)(n);let i=(e,n={})=>{if(this.keyMatchesBinding(n,t)){this.interrupt($,{phase:Wt,source:`keybinding`});return}this.isCtrlC(n)&&this.interrupt($,{phase:Wt,source:`keypress`})};return n.on(`keypress`,i),n.setRawMode(!0),n.resume(),()=>{n.off(`keypress`,i),!r&&typeof n.setRawMode==`function`&&n.setRawMode(!1)}}parseKeybinding(e){let t=e.toLowerCase().split(`+`).map(e=>e.trim()).filter(Boolean),n=t.at(-1);if(!n)return null;let r=new Set(t.slice(0,-1));return[...r].find(e=>e!==Qt&&e!==Zt&&e!==$t&&e!==`cmd`)?null:{ctrl:r.has(Qt),key:n,meta:r.has($t)||r.has(`cmd`),shift:r.has(Zt)}}keyMatchesBinding(e,t){let n=e.name?.toLowerCase();if(!n||n!==t.key)return!1;let r=!!e.ctrl===t.ctrl,i=!!e.meta===t.meta,a=t.shift?!0:!e.shift;return r&&i&&a}isCtrlC(e){return e.ctrl===!0&&e.name?.toLowerCase()===`c`||e.sequence===``}async finalizeRegistryRun(e,t,n){let r=t?.exitCode===130||n instanceof P,i=t?.exitCode??(r?130:1),a=i===0||i===2?`completed`:`error`,o=i===0?`success`:i===2?`skipped`:r||this.isInterrupted()?`interrupted`:`failed`,s=n instanceof Error?n.message:i===1?this.lastMeaningfulOutputLine():void 0;await this.registry.finalizeRun(e,{errorMessage:s,exitCode:i,outcome:o,stage:a})}resolveCliPath(){let e=__dirname,t=(0,s.resolve)(`/`);for(;e!==t;){let t=(0,s.resolve)(e,`cli.cjs`);if((0,c.existsSync)(t))return t;let n=(0,s.resolve)(e,`dist`,`cli.cjs`);if((0,c.existsSync)(n))return n;e=(0,s.dirname)(e)}throw Error(`Unable to locate workflow-mcp cli.cjs from `+__dirname)}buildLaunchCliCommand(e,t){let n=[`node`,this.resolveCliPath(),`run-workflow`,t,`--skip-launch`],r=this.resolveRunner(e);if(e.job&&n.push(`--job`,e.job),r&&n.push(`--runner`,r),e.dryRun&&n.push(`--dry-run`),e.continueOnError&&n.push(`--continue-on-error`),e.keepWorktree&&n.push(`--keep-worktree`),e.prompt&&n.push(`--prompt`,`'${e.prompt.replace(/'/g,`'\\''`)}'`),e.name&&n.push(`--name`,`'${e.name}'`),e.workspace&&n.push(`--workspace`,`'${e.workspace}'`),e.secretFile&&n.push(`--secret-file`,e.secretFile),e.inputs)for(let[t,r]of Object.entries(e.inputs))n.push(`--input`,`${t}=${r}`);if(e.env)for(let[t,r]of Object.entries(e.env))n.push(`--env`,`${t}=${r}`);return n.join(` `)}lastMeaningfulOutputLine(){return[...this.outputLines].reverse().find(e=>e.trim().length>0)}async runPreBlock(e,t,n,r,i){if(this.logger.info(`\n ${Q}${Y}pre${J} ${Y}Pre-conditions${J}`),this.logger.info(` ${`─`.repeat(50)}`),t.CONTEXT_FILE&&await(0,a.mkdir)((0,s.dirname)((0,s.resolve)(i.workflowDir,t.CONTEXT_FILE)),{recursive:!0}),e.services&&e.services.length>0){this.logger.info(`\n ${Q}${Y}services${J}`);for(let n of e.services)if(await this.serviceManager.startService(n,t,i.workflowDir,i.dryRun)&&n[`ready-check`]&&!i.dryRun){let e=await this.serviceManager.waitForServiceReady(n,i.workflowDir,t,()=>this.interruptedSignal!==null);if(this.isInterrupted())return!1;if(!e&&!i.continueOnError)return await this.serviceManager.stopAll(),!1}!i.dryRun&&this.serviceManager.getRunningServices().length>0&&await this.waitForServiceStartup()}if(e.steps&&e.steps.length>0){this.logger.info(`\n ${Q}${Y}setup${J}`);let a={inputs:n,matrix:{},secrets:r,env:t};for(let t of e.steps)if(!await this.stepRunner.runStep(t,a,i))return this.logger.error(`\n ${Z}Pre-condition step failed${J}`),await this.serviceManager.stopAll(),!1}return this.logger.info(`\n ${Ht}Pre-conditions ready${J}`),!0}async runPostBlock(e,t,n,r,i){this.logger.info(`\n ${Q}${Y}post${J} ${Y}Cleanup${J}`),this.logger.info(` ${`─`.repeat(50)}`),await this.serviceManager.stopAll();try{if(e.services&&e.services.length>0){this.logger.info(`\n ${Q}${Y}services${J}`);for(let n of e.services)if(await this.serviceManager.startService(n,t,i.workflowDir,i.dryRun)&&n[`ready-check`]&&!i.dryRun&&!await this.serviceManager.waitForServiceReady(n,i.workflowDir,t,()=>!1)&&!i.continueOnError)return!1;!i.dryRun&&this.serviceManager.getRunningServices().length>0&&await this.waitForServiceStartup()}if(e.steps&&e.steps.length>0){this.logger.info(`\n ${Q}${Y}cleanup${J}`);let a={inputs:n,matrix:{},secrets:r,env:t};for(let t of e.steps)if(!await this.stepRunner.runStep(t,a,i))return this.logger.error(`\n ${Z}Post-cleanup step failed${J}`),!1}return this.logger.info(`\n ${Ht}Cleanup complete${J}`),!0}finally{await this.serviceManager.stopAll()}}};const tn=n.z.object({workflowPath:n.z.string().describe(`Path to the workflow YAML file (e.g., .github/workflows/deploy.yml)`),runner:n.z.string().optional().describe(`Preferred runner key for step command maps (e.g. ollama, claude, codex)`),cliAgent:n.z.string().optional().describe(`Deprecated alias for runner. Preferred runner command key for step command maps.`),job:n.z.string().optional().describe(`Run only this job (and its dependencies)`),inputs:n.z.record(n.z.string(),n.z.string()).optional().describe(`Workflow dispatch inputs as key-value pairs`),env:n.z.record(n.z.string(),n.z.string()).optional().describe(`Extra environment variables as key-value pairs`),secretFile:n.z.string().optional().describe(`Path to a dotenv-style secrets file`),dryRun:n.z.boolean().optional().describe(`Print steps without executing`),continueOnError:n.z.boolean().optional().describe(`Continue past step failures`),keepWorktree:n.z.boolean().optional().describe(`Keep worktree on completion (skip merge and cleanup for retry)`),prompt:n.z.string().optional().describe(`User prompt for user_prompt trigger workflows`),name:n.z.string().optional().describe(`Name for the workflow run context directory`),workspace:n.z.string().optional().describe(`Workspace for workflow registry storage`)});var nn=class e{static TOOL_NAME=`run_workflow`;constructor(e=new en){this.service=e}getInputSchema(){return tn}getDefinition(){return{name:e.TOOL_NAME,description:`Run a GitHub Actions workflow file locally. Parses the workflow YAML and executes run steps on the host machine, respecting job dependencies, matrix strategies, environment variables, and workflow_dispatch inputs.`,inputSchema:n.z.toJSONSchema(tn)}}async execute(e){try{let t=tn.parse(e);if(t.runner&&t.cliAgent&&t.runner!==t.cliAgent)throw Error(`Conflicting runner selectors: runner="${t.runner}" cliAgent="${t.cliAgent}"`);let n={cliAgent:t.cliAgent,runner:t.runner??t.cliAgent,workflowPath:t.workflowPath,job:t.job,inputs:t.inputs,env:t.env,secretFile:t.secretFile,dryRun:t.dryRun,continueOnError:t.continueOnError,keepWorktree:t.keepWorktree,prompt:t.prompt,name:t.name,workspace:t.workspace},r=await this.service.run(n);return r.exitCode!==0&&r.exitCode!==2?{content:[{type:`text`,text:`Workflow failed (exit code ${r.exitCode}):\n\n${r.output}`}],isError:!0}:{content:[{type:`text`,text:r.output||`Workflow completed successfully.`}]}}catch(e){return{content:[{type:`text`,text:`Error: ${e instanceof Error?e.message:`Unknown error`}`}],isError:!0}}}},rn=class e{static TOOL_NAME=`schedule-cron`;constructor(e=new D){this.service=e}getInputSchema(){return w}getDefinition(){return{name:e.TOOL_NAME,description:`Schedule a headless Claude Code or Codex CLI run as a system cron job. Specify either a cron expression or an interval in minutes.`,inputSchema:n.z.toJSONSchema(w)}}async execute(t){try{let e=w.parse(t),n=await this.service.schedule(e);return{content:[{type:`text`,text:JSON.stringify(n,null,2)}]}}catch(n){let r=new g(`Failed to schedule cron job.`,`SCHEDULE_CRON_TOOL_FAILED`,{tool:e.TOOL_NAME,name:t.name,cwd:t.cwd},{cause:n});return console.error(`[${e.TOOL_NAME}] ${r.message}`,r.context),{content:[{type:`text`,text:JSON.stringify({code:r.code,context:r.context,message:r.message},null,2)}],isError:!0}}}};const an=[nn.TOOL_NAME,ke.TOOL_NAME,rn.TOOL_NAME,O.TOOL_NAME];function on(){let n=new e.Server({name:`workflow-mcp`,version:`0.1.0`},{capabilities:{tools:{}}}),r=new nn,i=new ke,a=new rn,o=new O;return n.setRequestHandler(t.ListToolsRequestSchema,async()=>({tools:[r.getDefinition(),i.getDefinition(),a.getDefinition(),o.getDefinition()]})),n.setRequestHandler(t.CallToolRequestSchema,async e=>{let{name:t,arguments:n}=e.params;if(t===nn.TOOL_NAME)return await r.execute(n);if(t===ke.TOOL_NAME)return await i.execute(n);if(t===rn.TOOL_NAME)return await a.execute(n);if(t===O.TOOL_NAME)return await o.execute();throw new h(t,an)}),n}var sn=class{server;transport=null;constructor(e){this.server=e}async start(){this.transport=new m.StdioServerTransport,await this.server.connect(this.transport),console.error(`workflow-mcp MCP server started on stdio`)}async stop(){this.transport&&=(await this.transport.close(),null)}};Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return De}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return w}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return qe}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return on}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return D}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return en}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return`claude`}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return sn}});
@@ -1,52 +0,0 @@
1
- import{Server as e}from"@modelcontextprotocol/sdk/server/index.js";import{CallToolRequestSchema as t,ListToolsRequestSchema as n}from"@modelcontextprotocol/sdk/types.js";import{z as r}from"zod";import{execFile as i,execSync as a,spawn as o}from"node:child_process";import{promisify as s}from"node:util";import{access as c,mkdir as l,readFile as u,readdir as d,rename as f,rm as p,unlink as m,writeFile as h}from"node:fs/promises";import{homedir as g,tmpdir as _}from"node:os";import ee,{basename as te,dirname as v,join as y,resolve as b}from"node:path";import{fileURLToPath as x}from"node:url";import{existsSync as S,mkdirSync as C,readFileSync as w,rmSync as ne,unlinkSync as re,watch as ie,writeFileSync as T}from"node:fs";import{createInterface as ae,emitKeypressEvents as oe}from"node:readline";import{PortRegistryService as E}from"@agimon-ai/foundation-port-registry";import{ProcessRegistryService as se}from"@agimon-ai/foundation-process-registry";import{randomUUID as D}from"node:crypto";import{load as O}from"js-yaml";import{StdioServerTransport as k}from"@modelcontextprotocol/sdk/server/stdio.js";var ce=class extends Error{code=`WORKFLOW_TOOL_NOT_FOUND`;constructor(e,t){super(`Unknown tool "${e}". Available tools: ${t.join(`, `)}`),this.toolName=e,this.availableTools=t,this.name=`WorkflowToolNotFoundError`}},le=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`WorkflowToolError`}},ue=class extends Error{constructor(e,t,n={},r){super(e,r),this.code=t,this.context=n,this.name=`CronError`}};const de=`# workflow-mcp:cron:`,fe=`crontab`,pe=`claude`,me=`codex`,he=pe,ge=`--cwd`,_e=`CRON_WRITE_FAILED`,ve=r.enum([pe,me]),ye=r.object({name:r.string().min(1),cwd:r.string().min(1),cli:ve,schedule:r.string().min(1),prompt:r.string().optional(),promptFile:r.string().optional(),createdAt:r.string().min(1)}),be=r.object({name:r.string().min(1),cwd:r.string().min(1),cli:ve.optional(),prompt:r.string().optional(),promptFile:r.string().optional(),schedule:r.string().optional(),intervalMinutes:r.number().positive().optional()});function xe(e){return`'${e.replace(/'/g,`'\\''`)}'`}function Se(e,t,n,r){let i=n?xe(n):r?`"$(cat ${xe(r)})"`:void 0;if(e===`codex`){let e=[me,`--approval-mode`,`full-auto`];return i&&e.push(`-q`,i),e.push(ge,xe(t)),e.join(` `)}let a=[pe,`--dangerously-skip-permissions`];return i&&a.push(`-p`,i),a.push(ge,xe(t)),a.join(` `)}function Ce(e){if(e<=0)throw Error(`Interval must be a positive number of minutes`);if(e<60)return`*/${e} * * * *`;let t=Math.floor(e/60);return t<24?`0 */${t} * * *`:`0 0 */${Math.floor(t/24)} * *`}const we=s(i);var Te=class{logger;constructor(e){this.logger=e??{info:e=>process.stdout.write(`${e}\n`),error:e=>console.error(e),warn:e=>console.error(e)}}async readCrontab(){try{let{stdout:e}=await we(fe,[`-l`]);return e}catch(e){let t=e.code;if(t===`ENOENT`||(e.stderr??``).includes(`no crontab for`))return``;throw this.logger.error(`[CronService.readCrontab] failed exitCode=${t}`),new ue(`Failed to read current crontab.`,`CRON_READ_FAILED`,{exitCode:t},{cause:e})}}async writeCrontab(e){let t=``,n=i(fe,[`-`]);n.stderr?.on(`data`,e=>{t+=e}),n.stdin?.write(e),n.stdin?.end(),await new Promise((r,i)=>{n.on(`close`,n=>{n===0?r():(this.logger.error(`[CronService.writeCrontab] failed exitCode=${n} stderr=${t}`),i(new ue(`crontab rejected input (exit ${n}).`,_e,{exitCode:n,stderr:t,contentLength:e.length})))}),n.on(`error`,e=>{this.logger.error(`[CronService.writeCrontab] spawn failed: ${e.message}`),i(new ue(`Failed to spawn crontab process.`,_e,{},{cause:e}))})})}async schedule(e){let t=e.cli??`claude`;if(!e.schedule&&!e.intervalMinutes)throw new ue(`Either schedule (cron expression) or intervalMinutes must be provided.`,`CRON_INVALID_INPUT`,{name:e.name});let n=e.schedule??Ce(e.intervalMinutes),r=Se(t,e.cwd,e.prompt,e.promptFile),i=new Date().toISOString(),a=ye.parse({name:e.name,cwd:e.cwd,cli:t,schedule:n,prompt:e.prompt,promptFile:e.promptFile,createdAt:i}),o=await this.readCrontab(),s=this.removeCronBlock(o,e.name),c=`${`${de}${e.name}`}\n${`# workflow-mcp:meta:${JSON.stringify(a)}`}\n${`${n} ${r}`}`,l=s.trimEnd()?`${s.trimEnd()}\n${c}\n`:`${c}\n`;return await this.writeCrontab(l),this.logger.info(`Scheduled cron "${e.name}" [${n}]`),a}async list(){let e=await this.readCrontab(),t=[],n=e.split(`
2
- `);for(let e=0;e<n.length;e++){let r=n[e];if(r.startsWith(`# workflow-mcp:meta:`))try{let e=r.slice(20),n=ye.parse(JSON.parse(e));t.push(n)}catch(t){this.logger.warn(`[CronService.list] skipping malformed metadata at line ${e+1}: ${t.message}`)}}return t}async remove(e){let t=await this.readCrontab(),n=this.removeCronBlock(t,e);return n===t?!1:(await this.writeCrontab(n),this.logger.info(`Removed cron "${e}"`),!0)}removeCronBlock(e,t){let n=e.split(`
3
- `),r=[],i=`${de}${t}`,a=!1;for(let e of n){if(e===i){a=!0;continue}if(a){if(e.startsWith(`# workflow-mcp:meta:`))continue;a=!1;continue}r.push(e)}return r.join(`
4
- `)}},Ee=class e{static TOOL_NAME=`list-crons`;constructor(e=new Te){this.service=e}getInputSchema(){return r.object({})}getDefinition(){return{name:e.TOOL_NAME,description:`List all cron jobs scheduled via workflow-mcp, showing name, schedule, CLI, cwd, and prompt.`,inputSchema:r.toJSONSchema(r.object({}))}}async execute(){try{let e=await this.service.list();return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}catch(t){let n=new le(`Failed to list cron jobs.`,`LIST_CRONS_TOOL_FAILED`,{tool:e.TOOL_NAME},{cause:t});return console.error(`[${e.TOOL_NAME}] ${n.message}`,n.context),{content:[{type:`text`,text:JSON.stringify({code:n.code,context:n.context,message:n.message},null,2)}],isError:!0}}}},De=class extends Error{code=`INVALID_WORKFLOW_RUN_RECORD`;constructor(e,t,n){super(`Invalid workflow run record at "${e}": ${t}`,n),this.recordPath=e,this.name=`InvalidWorkflowRunRecordError`}},Oe=class extends Error{code=`WORKFLOW_RUN_CONFLICT`;constructor(e,t){super(`Workflow "${t}" is already running in workspace "${e}"`),this.workspace=e,this.runKey=t,this.name=`WorkflowRunConflictError`}};const ke=r.record(r.string(),r.coerce.string()),Ae=r.object({description:r.string().optional(),required:r.boolean().optional(),default:r.string().optional(),type:r.string().optional(),options:r.array(r.string()).optional()}),je=r.record(r.string(),r.string()),Me=r.record(r.string(),r.string()),Ne=r.union([r.string(),Me]),Pe=r.object({"fail-fast":r.boolean().optional(),matrix:r.looseObject({include:r.array(je).optional()}).optional()}),A=r.object({name:r.string().optional(),uses:r.string().optional(),run:Ne.optional(),interactiveRun:Ne.optional(),"timeout-minutes":r.number().optional(),env:ke.optional(),with:r.record(r.string(),r.string()).optional(),"working-directory":r.string().optional(),if:r.string().optional(),"continue-on-error":r.boolean().optional(),"timeout-retries":r.number().optional(),id:r.string().optional()}),Fe=r.object({name:r.string(),run:r.string(),env:ke.optional(),"working-directory":r.string().optional(),"ready-check":r.union([r.string(),r.boolean()]).optional(),"ready-timeout":r.number().optional(),host:r.string().optional(),port:r.number().int().min(1).max(65535).optional(),"port-range":r.object({min:r.number().int().min(1).max(65535),max:r.number().int().min(1).max(65535)}).optional(),"service-type":r.enum([`tool`,`service`]).optional()}),Ie=r.object({services:r.array(Fe).optional(),steps:r.array(A).optional()}),Le=r.object({steps:r.array(A).optional()}),Re=r.object({"runs-on":r.string().optional(),needs:r.union([r.string(),r.array(r.string())]).optional(),extends:r.union([r.string(),r.array(r.string())]).optional(),description:r.string().optional(),strategy:Pe.optional(),steps:r.array(A),preJob:Le.optional(),postJob:Le.optional(),env:ke.optional(),if:r.string().optional(),"system-prompt":r.string().optional(),context:r.string().optional()}),ze=r.object({steps:r.array(A)}),Be=r.object({steps:r.array(A)}),Ve=r.object({beforeCreate:Be.optional(),afterCreate:Be.optional(),beforeCompleted:Be.optional(),afterCompleted:Be.optional()}),He=r.object({STOP:r.string().optional()}),Ue=r.object({name:r.string().optional(),workspace:r.string().optional(),imports:r.array(r.string()).optional(),"system-prompt":r.string().optional(),"max-workflows":r.number().int().positive().optional(),"launch-command":r.string().optional(),keybindings:He.optional(),on:r.looseObject({workflow_dispatch:r.object({inputs:r.record(r.string(),Ae).optional()}).nullable().optional(),agent_decision:ze.optional()}).optional(),env:ke.optional(),pre:Ie.optional(),post:Ie.optional(),worktree:Ve.optional(),jobs:r.record(r.string(),Re)}),We=r.enum([`running`,`completed`,`error`]),Ge=r.enum([`success`,`skipped`,`failed`,`interrupted`]),Ke=r.object({displayName:r.string(),dryRun:r.boolean(),errorMessage:r.string().optional(),exitCode:r.number().optional(),finishedAt:r.string().optional(),outcome:Ge.optional(),pid:r.number().int().positive().optional(),runKey:r.string(),stale:r.boolean().optional(),staleReason:r.string().optional(),stage:We,startedAt:r.string(),workflowPath:r.string(),workspace:r.string()});r.object({changelogPath:r.string(),contextPath:r.string(),displayName:r.string(),recordPath:r.string(),runDir:r.string(),runKey:r.string(),workspace:r.string()});const qe=`default`,j=`running`,Je=`completed`,M=`error`,Ye=`workspaces`,N=`run.json`,Xe=`Workflow process is no longer running`;var Ze=class{constructor(e=b(g(),`.workflow-mcp`)){this.homeDir=e}resolveWorkspace(e,t){return this.slugifySegment(e||t||qe,qe)}async createRun(e){let t=this.resolveWorkspace(e.workspace,e.workflowWorkspace),n=this.slugifySegment(e.displayName,`workflow-run`);await this.ensureWorkspaceStructure(t);let r=await this.findReusableRunStage(t,n);if(r===j)throw new Oe(t,n);let i=this.getRunDirectory(t,j,n);r?(await p(i,{recursive:!0,force:!0}),await f(this.getRunDirectory(t,r,n),i),await this.removeFileIfExists(b(i,`fix.md`))):await l(i,{recursive:!0});let a={displayName:e.displayName,dryRun:e.dryRun,runKey:n,stage:j,startedAt:new Date().toISOString(),pid:e.pid??process.pid,workflowPath:e.workflowPath,workspace:t},o=b(i,N);return await this.writeRunRecord(o,a),{changelogPath:b(i,`changelog.md`),contextPath:b(i,`context.md`),displayName:e.displayName,recordPath:o,runDir:i,runKey:n,workspace:t}}async finalizeRun(e,t){await this.ensureWorkspaceStructure(e.workspace);let n=this.getRunDirectory(e.workspace,t.stage,e.runKey),r=await this.readRunRecord(e.recordPath),i={...r,errorMessage:t.errorMessage,exitCode:t.exitCode,finishedAt:new Date().toISOString(),outcome:t.outcome,pid:r.pid,stage:t.stage};e.runDir!==n&&(await p(n,{recursive:!0,force:!0}),await f(e.runDir,n));let a=b(n,N);return await this.writeRunRecord(a,i),{...e,recordPath:a,runDir:n}}async readRunRecord(e){try{return this.validateRunRecord(JSON.parse(await u(e,`utf-8`)),e)}catch(t){throw t instanceof De?t:new De(e,t instanceof Error?t.message:`Unknown parse failure`,{cause:t})}}async listRuns(e){let t=e?[this.resolveWorkspace(e)]:await this.listWorkspaceNames();return(await Promise.all(t.map(async e=>this.listWorkspaceRuns(e)))).flat().sort((e,t)=>this.compareRunRecordsByLatest(e,t))}async listRunsPage(e={}){let t=e.page??1,n=e.pageSize??20,r=await this.listRuns(e.workspace),i=r.length,a=Math.ceil(i/n),o=(t-1)*n,s=r.slice(o,o+n);return{hasNextPage:t<a,hasPreviousPage:t>1&&i>0,items:s,page:t,pageSize:n,total:i,totalPages:a}}async countRunningWorkflows(e){let t=this.getRunStageDirectory(e,j);if(!await this.pathExists(t))return 0;let n=await d(t,{withFileTypes:!0}),r=0;for(let e of n){if(!e.isDirectory())continue;let n=b(t,e.name,N);if(await this.pathExists(n))try{let e=await this.readRunRecord(n);e.pid&&this.isProcessAlive(e.pid)&&r++}catch{}}return r}async ensureWorkspaceStructure(e){await l(this.getRunStageDirectory(e,j),{recursive:!0}),await l(this.getRunStageDirectory(e,Je),{recursive:!0}),await l(this.getRunStageDirectory(e,M),{recursive:!0})}async findRunStage(e,t){for(let n of[j,Je,M])if(await this.pathExists(this.getRunDirectory(e,n,t)))return n;return null}async listWorkspaceRuns(e){let t=new Map;for(let n of[j,Je,M]){let r=this.getRunStageDirectory(e,n);if(!await this.pathExists(r))continue;let i=await d(r,{withFileTypes:!0});for(let a of i){if(!a.isDirectory())continue;let i=b(r,a.name,N);if(!await this.pathExists(i))continue;let o=await this.readListableRunRecord(e,n,a.name,i);o&&t.set(o.runKey,o)}}return[...t.values()]}async findReusableRunStage(e,t){let n=await this.findRunStage(e,t);return n===j?(await this.reconcileStaleRunningRecord(e,t),this.findRunStage(e,t)):n}async inspectRunningRecord(e,t){let n=b(this.getRunDirectory(e,j,t),N),r=await this.readRunRecord(n);return!r.pid||this.isProcessAlive(r.pid)?r:{...r,stale:!0,staleReason:`${Xe}: pid ${r.pid}`}}async readListableRunRecord(e,t,n,r){try{return t===j?await this.inspectRunningRecord(e,n):await this.readRunRecord(r)}catch(e){if(e instanceof De)return null;throw e}}async reconcileStaleRunningRecord(e,t){let n=this.getRunDirectory(e,j,t),r=b(n,N),i=await this.readRunRecord(r);if(!i.pid||this.isProcessAlive(i.pid))return i;let a=this.getRunDirectory(e,M,t),o={...i,errorMessage:`${Xe}: pid ${i.pid}`,exitCode:130,finishedAt:new Date().toISOString(),outcome:`interrupted`,stage:M};return await p(a,{recursive:!0,force:!0}),await f(n,a),await this.writeRunRecord(b(a,N),o),o}async listWorkspaceNames(){let e=b(this.homeDir,Ye);return await this.pathExists(e)?(await d(e,{withFileTypes:!0})).filter(e=>e.isDirectory()).map(e=>e.name).sort((e,t)=>e.localeCompare(t)):[]}compareRunRecordsByLatest(e,t){let n=e.finishedAt??e.startedAt,r=(t.finishedAt??t.startedAt).localeCompare(n);if(r!==0)return r;let i=e.workspace.localeCompare(t.workspace);return i===0?e.runKey.localeCompare(t.runKey):i}async writeRunRecord(e,t){await h(e,`${JSON.stringify(t,null,2)}\n`,`utf-8`)}validateRunRecord(e,t){let n=Ke.safeParse(e);if(!n.success)throw new De(t,n.error.message);return n.data}getRunStageDirectory(e,t){return b(this.homeDir,Ye,e,t)}getRunDirectory(e,t,n){return b(this.getRunStageDirectory(e,t),n)}async pathExists(e){try{return await c(e),!0}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async removeFileIfExists(e){try{await m(e)}catch(e){if(e.code!==`ENOENT`)throw e}}slugifySegment(e,t){return e.trim().toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``)||t}isProcessAlive(e){try{return process.kill(e,0),!0}catch(e){let t=e.code;if(t===`ESRCH`)return!1;if(t===`EPERM`)return!0;throw e}}};const Qe=r.object({page:r.number().int().min(1).optional().describe(`Page number to return. Defaults to 1.`),pageSize:r.number().int().min(1).max(100).optional().describe(`Number of workflow runs per page. Defaults to 20 and is capped at 100.`),workspace:r.string().optional().describe(`Optional workspace filter. When omitted, runs from all workspaces are returned.`)});var $e=class e{static TOOL_NAME=`list_workflow_statuses`;constructor(e=new Ze){this.registry=e}getInputSchema(){return Qe}getDefinition(){return{name:e.TOOL_NAME,description:`List tracked workflow runs from the local workflow registry, including their workspace, stage, outcome, and timestamps.`,inputSchema:r.toJSONSchema(Qe)}}async execute(e={}){try{let t=Qe.parse(e),n=await this.registry.listRunsPage({page:t.page??1,pageSize:t.pageSize??20,workspace:t.workspace});return{content:[{type:`text`,text:JSON.stringify(n,null,2)}]}}catch(t){let n=new le(`Failed to list workflow statuses.`,`LIST_WORKFLOW_STATUSES_TOOL_FAILED`,{workspace:e.workspace??`all`},{cause:t});return{content:[{type:`text`,text:JSON.stringify({code:n.code,context:n.context,message:n.message},null,2)}],isError:!0}}}};const et=ee.dirname(x(import.meta.url));var tt=class extends Error{code=`WORKFLOW_CAPACITY_EXCEEDED`;constructor(e,t,n){super(`Workspace "${e}" is at capacity: ${t}/${n} workflows running.\n → Check running workflows: list-workflow-statuses --workspace ${e}\n → Wait for a running workflow to complete, or cancel one before dispatching again.`),this.workspace=e,this.running=t,this.max=n,this.name=`WorkflowCapacityError`}},P=class extends Error{code=`WORKFLOW_INTERRUPTED`;context;constructor(e,t={}){let n=t.context?` during ${t.context.phase}`:``;super(`Workflow interrupted by ${e}${n}`,{cause:t.cause??t.context}),this.signal=e,this.name=`WorkflowInterruptedError`,this.context=t.context}};const F=`\x1B[0m`,nt=`\x1B[1m`,rt=`\x1B[2m`,it=`\x1B[31m`,at=`\x1B[34m`,ot=[`pnpm-workspace.yaml`,`nx.json`,`.git`];function st(e){let t=b(e);for(;;){for(let e of ot)if(S(y(t,e)))return t;let n=v(t);if(n===t)return e;t=n}}var ct=class{runningServices=[];processRegistry=new se(process.env.PROCESS_REGISTRY_PATH);portRegistry=new E(process.env.PORT_REGISTRY_PATH);constructor(e){this.logger=e}resolveWorkingDirectory(e,t){return e[`working-directory`]?b(t,e[`working-directory`]):t}getRunningServices(){return this.runningServices}async startService(e,t,n,r){let i=e.run;if(r)return this.logger.info(` ${at}dry ${F} ${e.name}`),this.logger.info(` ${rt}$ ${i}${F}`),null;this.logger.info(` ${at}start${F} ${e.name}`),this.logger.info(` ${rt}$ ${i}${F}`);let a={...process.env,...t};if(e.env)for(let[t,n]of Object.entries(e.env))a[t]=String(n);let s=this.resolveWorkingDirectory(e,n),c=st(s),l=a.NODE_ENV??process.env.NODE_ENV??`development`,u=e.host??`127.0.0.1`,d;if(e.port!==void 0||e[`port-range`]){let t=await this.portRegistry.reservePort({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,preferredPort:e.port,portRange:e[`port-range`],host:u,force:!0});if(!t.success||t.port===void 0)throw Error(t.error??`Failed to reserve port for background service ${e.name}`);d=t.port,a.PORT??=String(d),a.SERVICE_PORT??=String(d),a.HOST??=u,a.SERVICE_HOST??=u}let f=o(i,[],{stdio:[`ignore`,`pipe`,`pipe`],cwd:s,env:{...a,FORCE_COLOR:`1`},shell:`/bin/zsh`,detached:!0});if(f.pid===void 0)throw d!==void 0&&await this.portRegistry.releasePort({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,force:!0}),Error(`Failed to spawn background service ${e.name}`);let p=`${at}[${e.name}]${F}`;f.stdout?.on(`data`,e=>{for(let t of e.toString().split(`
5
- `))t.trim()&&process.stdout.write(`${p} ${t}\n`)}),f.stderr?.on(`data`,e=>{for(let t of e.toString().split(`
6
- `))t.trim()&&process.stderr.write(`${p} ${t}\n`)}),f.on(`error`,e=>{this.logger.error(`${p} Process error: ${e.message}`)});let m=async()=>{d!==void 0&&await this.portRegistry.releasePort({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,force:!0})};if(f.pid!==void 0){let t=await this.processRegistry.registerProcess({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,pid:f.pid,port:d,host:d===void 0?void 0:u,command:i,args:[],metadata:{workingDirectory:s,readyCheck:e[`ready-check`]},force:!0});if(!t.success){try{process.kill(-f.pid,`SIGTERM`)}catch{f.kill(`SIGTERM`)}throw d!==void 0&&await this.portRegistry.releasePort({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,force:!0}),Error(t.error??`Failed to register process for background service ${e.name}`)}m=async()=>{let t=await this.processRegistry.releaseProcess({repositoryPath:c,serviceName:e.name,serviceType:e[`service-type`]??`tool`,environment:l,pid:f.pid,kill:!0,releasePort:!0,force:!0});if(!t.success&&!t.error?.includes(`No matching process entry`))throw Error(t.error??`Failed to release ${e.name}`)}}let h={name:e.name,process:f,env:a,release:m};return this.runningServices.push(h),h}async waitForServiceReady(e,t,n,r){if(!e[`ready-check`]||typeof e[`ready-check`]!=`string`)return!0;let i=(e[`ready-timeout`]||30)*1e3,o=Date.now(),s=e[`ready-check`],c=this.resolveWorkingDirectory(e,t),l=this.runningServices.find(t=>t.name===e.name)?.env??{...process.env,...n};for(this.logger.info(` ${rt}wait${F} Waiting for ${e.name} to be ready...`);Date.now()-o<i;){if(r?.())return!1;try{return a(s,{stdio:`ignore`,cwd:c,env:l,shell:`/bin/zsh`,timeout:5e3}),this.logger.info(` ready${F} ${e.name}`),!0}catch{if(r?.())return!1}await new Promise(e=>{setTimeout(e,1e3)})}return r?.()||this.logger.error(` ${it}timeout${F} ${e.name} not ready after ${e[`ready-timeout`]||30}s`),!1}async stopAll(){if(this.runningServices.length!==0){this.logger.info(`\n ${at}${nt}pre${F} ${nt}Stopping services${F}`),this.logger.info(` ${`─`.repeat(50)}`);for(let e of this.runningServices){if(!e.process.killed&&e.process.pid&&this.logger.info(` ${rt}stop${F} ${e.name} (pid ${e.process.pid})`),e.release)try{await e.release();continue}catch(t){this.logger.warn(` ${it}warn${F} Failed registry cleanup for ${e.name}: ${t instanceof Error?t.message:String(t)}`)}if(!e.process.killed&&e.process.pid)try{process.kill(-e.process.pid,`SIGTERM`)}catch{e.process.kill(`SIGTERM`)}}this.runningServices=[]}}};const I=`\x1B[0m`,lt=`\x1B[1m`,L=`\x1B[2m`,ut=`\x1B[33m`,dt=`WORKFLOW_JOB_ID`,ft=`WORKFLOW_JOB_NAME`,pt=`PROCESS_REGISTRY_TAG`;var mt=class{constructor(e,t,n){this.parser=e,this.stepRunner=t,this.logger=n}async runJob(e,t,n,r){let i=this.parser.expandMatrix(t),a=r.maxRetries;for(let o of i){let i=D(),s=Object.keys(o).length>0?` ${L}(${Object.entries(o).map(([e,t])=>`${e}=${t}`).join(`, `)})${I}`:``;this.logger.info(`\n ${lt}job${I} ${lt}${e}${I}${s}`),this.logger.info(` ${`─`.repeat(50)}`);let c={...n.workflowEnv,[dt]:i,[ft]:e,[pt]:i};if(t.env)for(let[e,r]of Object.entries(t.env))c[e]=this.parser.interpolate(String(r),{inputs:n.inputs,matrix:o,secrets:n.secrets,env:c});c[dt]=i,c[ft]=e,c[pt]=i,this.logger.info(` ${L}jobid${I} ${i}`);let l=[];if(c.WORKFLOW_RUN_DIR){let t=n.jobOrder.map(e=>{let t=n.allJobs[e]?.description;return t?` - ${e}: ${t}`:` - ${e}`}).join(`
7
- `);l.push(`You are currently running job "${e}" in the following workflow pipeline:\n${t}\n\nIf you find bugs, missing features, or issues that need fixes from a previous job, write to ${c.WORKFLOW_RUN_DIR}/fix.md with a YAML frontmatter restart-from field specifying which job should handle the fix, followed by a detailed description. Example:\n---\nrestart-from: development\n---\nDescription of what needs to be fixed...\n\nThe workflow runner will restart from the specified job. Only create fix.md for issues that require code changes — fix minor issues yourself.`)}if(n.workflowSystemPrompt&&l.push(this.parser.interpolate(n.workflowSystemPrompt,{inputs:n.inputs,matrix:o,secrets:n.secrets,env:c})),t[`system-prompt`]&&l.push(this.parser.interpolate(t[`system-prompt`],{inputs:n.inputs,matrix:o,secrets:n.secrets,env:c})),l.length>0){let e=l.join(`
8
-
9
- `);c.JOB_SYSTEM_PROMPT=e,this.logger.info(` ${L}prompt${I} ${e.split(`
10
- `)[0].slice(0,80)}${e.includes(`
11
- `)?`...`:``}`)}let u=t.context?this.parser.interpolate(t.context,{inputs:n.inputs,matrix:o,secrets:n.secrets,env:c}):c.CONTEXT_FILE;if(u){let e=b(r.workflowDir,u);S(e)?(c.WORKFLOW_CONTEXT=e,c.WORKFLOW_CONTEXT_FILE=e,this.logger.info(` ${L}ctx ${I} ${u}`)):r.dryRun||this.logger.warn(` ${ut}warn${I} context file not found: ${u}`)}if(c.CHANGELOG_FILE){let e=b(r.workflowDir,c.CHANGELOG_FILE),t=v(e);S(t)||C(t,{recursive:!0}),S(e)||T(e,`# Changelog
12
- `,`utf-8`),c.WORKFLOW_CHANGELOG=e,this.logger.info(` ${L}log ${I} changelog at ${c.CHANGELOG_FILE}`)}let d={inputs:n.inputs,matrix:o,secrets:n.secrets,env:c},f=!1,p=t.preJob?.steps??[],m=t.steps??[],h=t.postJob?.steps??[];for(let t=1;t<=a;t++){t>1&&(this.logger.info(`\n ${ut}retry${I} ${lt}${e}${I}${s} (attempt ${t}/${a})`),this.logger.info(` ${`─`.repeat(50)}`));let n=!0;if(p.length>0&&(n=await this.runStepSequence(p,d,r)),n&&=await this.runStepSequence(m,d,r),h.length>0){let e=await this.runStepSequence(h,d,r);n&&=e}if(n){f=!0;break}t<a&&this.logger.warn(` ${ut}Job "${e}" failed, retrying...${I}`)}if(f)this.logger.info(` Job "${e}" completed${I}${s}`);else if(this.logger.error(`\n Job "${e}" failed after ${a} attempts${I}`),t.strategy?.[`fail-fast`]!==!1)return!1}return!0}async runStepSequence(e,t,n){for(let r of e)if(!await this.stepRunner.runStep(r,t,n))return!1;return!0}},ht=class extends Error{code=`WORKFLOW_STEP_SPAWN_FAILED`;constructor(e,t={}){super(`Failed to start workflow step "${e.stepName}"`,{cause:t.cause??e}),this.context=e,this.name=`WorkflowStepSpawnError`}},gt=class extends Error{code=`WORKFLOW_STEP_TIMEOUT_CONFIG_INVALID`;constructor(e,t={}){super(`Invalid ${e.configKey} for workflow step "${e.stepName}"`,{cause:t.cause??e}),this.context=e,this.name=`WorkflowStepTimeoutConfigError`}};const _t=`/backend-api`;var vt=class{codexHome;fetchFn;now;readTextFile;constructor(e={}){this.codexHome=e.codexHome??y(g(),`.codex`),this.fetchFn=e.fetchFn??fetch,this.now=e.now??(()=>Date.now()),this.readTextFile=e.readTextFile??(e=>u(e,`utf8`))}isCodexCommand(e,t){let n=e.trim();return n===`codex`||n.startsWith(`codex `)}async getQuotaStatus(){let e=await this.readAuthFile(),t=e?.tokens?.access_token?.trim(),n=e?.tokens?.account_id?.trim();if(!t||!n)return null;let r=await this.readChatgptBaseUrl(),i=await this.fetchFn(this.buildUsageUrl(r),{headers:{Authorization:`Bearer ${t}`,"ChatGPT-Account-Id":n,"User-Agent":`codex-cli`}});if(!i.ok)throw Error(`codex quota request failed with HTTP ${i.status}`);let a=await i.json();return{blockingLimit:this.findBlockingLimit(a),planType:a.plan_type??null}}async readAuthFile(){let e=await this.readOptionalTextFile(y(this.codexHome,`auth.json`));return e?JSON.parse(e):null}async readChatgptBaseUrl(){let e=(await this.readOptionalTextFile(y(this.codexHome,`config.toml`)))?.match(/^\s*chatgpt_base_url\s*=\s*"([^"]+)"/m);return this.normalizeBaseUrl(e?.[1]??`https://chatgpt.com/backend-api/`)}buildUsageUrl(e){return e.includes(_t)?`${e}/wham/usage`:`${e}/api/codex/usage`}normalizeBaseUrl(e){let t=e.trim().replace(/\/+$/,``);return(t.startsWith(`https://chatgpt.com`)||t.startsWith(`https://chat.openai.com`))&&!t.includes(_t)&&(t=`${t}${_t}`),t}async readOptionalTextFile(e){try{return await this.readTextFile(e)}catch(e){if(e.code===`ENOENT`)return null;throw e}}findBlockingLimit(e){let t=[];this.collectBlockingLimit(t,`codex`,`codex`,e.rate_limit);for(let n of e.additional_rate_limits??[])this.collectBlockingLimit(t,n.metered_feature??n.limit_name??`codex`,n.limit_name??n.metered_feature??`codex`,n.rate_limit);return t.sort((e,t)=>e.resetAt-t.resetAt||t.usedPercent-e.usedPercent),t[0]??null}collectBlockingLimit(e,t,n,r){if(!r)return;let i=[{payload:r.primary_window,window:`primary`},{payload:r.secondary_window,window:`secondary`}];for(let{payload:a,window:o}of i){let i=a?.reset_at,s=a?.used_percent??0;typeof i==`number`&&(s>=100||r.limit_reached===!0&&i*1e3>this.now()||r.allowed===!1&&i*1e3>this.now())&&e.push({limitId:t,limitName:n,resetAfterSeconds:a?.reset_after_seconds??null,resetAt:i,usedPercent:s,window:o,windowSeconds:a?.limit_window_seconds??null})}}};const R=`\x1B[0m`,z=`\x1B[2m`,yt=`\x1B[36m`,bt=`\x1B[32m`,B=`\x1B[33m`,V=`\x1B[31m`,xt=`SIGINT`,H=`SIGTERM`,St=`unnamed`,Ct=`/bin/zsh`,wt=`FORCE_COLOR`,Tt=`inherit`,Et=`pipe`,Dt=` `,Ot=`
13
- ${Dt}\$ `,kt=`skip`,U=`warn`,W=`error`,G=`fail`,At=`interrupted`,jt=`already stopping`,Mt=`failed to signal`,K=`(continue-on-error)`,Nt=`ESRCH`,Pt=process.platform!==`win32`,Ft=`interactiveRun`,It=`timeout-minutes`,Lt=`timeout-retries`,Rt=`retry`,zt=`SIGKILL`,Bt=1e3,Vt=6e4,Ht=Math.floor(2147483647/Vt),Ut=`WORKFLOW_STEP_DISPLAY`,Wt=`workflow-mcp`,Gt={status:`status_completed`},Kt=new Set([`ENOENT`,`EBUSY`,`EAGAIN`,`EPERM`]),qt=`done`,Jt=1e3,Yt=r.number().positive().finite().max(Ht),Xt=r.number().int().min(0).max(10).default(2);var Zt=class{activeStep=null;activeQuotaWait=null;constructor(e,t,n=new vt,r=ie){this.parser=e,this.logger=t,this.quotaService=n,this.watchStatusFile=r}stopActiveStep(e){if(this.activeQuotaWait){if(this.activeQuotaWait.controller.signal.aborted){this.logger.info(` ${z}${kt}${R} ${this.activeQuotaWait.stepName} ${jt}`);return}this.activeQuotaWait.signal=e,this.activeQuotaWait.controller.abort(),this.logger.info(` ${B}${At}${R} cancelling quota wait for ${this.activeQuotaWait.stepName}`);return}if(!this.activeStep){this.logger.info(` ${z}${kt}${R} no active step to stop`);return}if(this.activeStep.process.killed){this.logger.info(` ${z}${kt}${R} ${this.activeStep.stepName} ${jt}`);return}this.logger.info(` ${B}${At}${R} sending ${e} to ${this.activeStep.stepName}`),this.killActiveStep(e)||this.logger.warn(` ${B}${U}${R} ${Mt} ${this.activeStep.stepName}`)}async runStep(e,t,n){let r=this.resolveRunner(n),i=this.resolveStepCommand(e,t,r),a=e.name?this.parser.interpolate(e.name,t):i?.command.slice(0,60)||e.uses||St;if(e.uses){let n=this.parser.interpolate(e.uses,t);return this.parser.isActionSkipped(n)?(this.logger.info(` ${z}${kt}${R} ${n}`),!0):(this.logger.warn(` ${B}${U}${R} Unsupported action: ${n} (skipped)`),!0)}if(!i)return!0;if(i.missingRunner)return this.logger.error(` ${V}${W}${R} runner "${i.missingRunner.runner}" not found for step "${a}" (available runner keys: ${i.missingRunner.availableKeys.join(`, `)})`),e[`continue-on-error`]||n.continueOnError?(this.logger.warn(` ${B}${G}${R} ${a} ${K}\n`),!0):(this.logger.error(` ${V}${G}${R} ${a}\n`),!1);let{command:o,interactive:s}=i,c=this.quotaService.isCodexCommand(o,i.agentKey),l=e[`continue-on-error`]||n.continueOnError,u;try{u=this.resolveTimeoutMs(e,a)}catch(e){if(!(e instanceof gt))throw e;return this.logger.error(` ${V}${W}${R} invalid ${e.context.configKey} for "${a}": ${String(e.context.timeoutValue)}`),l?(this.logger.warn(` ${B}${G}${R} ${a} ${K}\n`),!0):(this.logger.error(` ${V}${G}${R} ${a}\n`),!1)}let d={...process.env,...t.env};if(e.env)for(let[n,r]of Object.entries(e.env))d[n]=this.parser.interpolate(String(r),t);d.WORKFLOW_STEP_NAME=a,d[Ut]=this.formatStepDisplay(d.WORKFLOW_JOB_NAME,a);let f;s&&(f=this.createStatusFile(),d.WORKFLOW_STATUS_FILE=f,this.logger.info(` ${z}status-file${R} ${f}`));let p=e[`working-directory`]?b(n.workflowDir,this.parser.interpolate(e[`working-directory`],t)):n.workflowDir;if(n.dryRun)return this.logger.info(` ${yt}dry ${R} ${a}`),this.logger.info(`${Dt}${z}\$ ${o.split(`
14
- `).join(Ot)}${R}`),!0;let m;try{m=this.resolveTimeoutRetries(e,a)}catch(e){if(!(e instanceof gt))throw e;return this.logger.error(` ${V}${W}${R} invalid ${e.context.configKey} for "${a}": ${String(e.context.timeoutValue)}`),l?(this.logger.warn(` ${B}${G}${R} ${a} ${K}\n`),!0):(this.logger.error(` ${V}${G}${R} ${a}\n`),!1)}let h=!1;for(let e=0;e<=m;e++){await this.waitForCodexQuotaAvailability(a,o,c),e>0?this.logger.warn(` ${B}${Rt}${R} retrying step after timeout "${a}" (attempt ${e+1}/${m+1})`):this.logger.info(` ${yt}run ${R} ${a}`),this.logger.info(`${Dt}${z}\$ ${o.split(`
15
- `)[0]}${o.includes(`
16
- `)?` ...`:``}${R}`);let t=s?this.withInteractiveTerminalTitle(o,d):o,n=this.executeCommand(t,p,d,a,s,u),r=s&&f?await this.raceStatusFile(n,f,a):await n;if(r.status===`signaled`)throw this.logger.info(`\n ${B}${At}${R} ${a}`),new P(r.signal,{cause:Error(`Step process ended with ${r.signal}`),context:this.createInterruptContext(a,o)});if(r.status===`completed`&&(r.exitCode===130||r.exitCode===143)){let e=r.exitCode===130?xt:H;throw this.logger.info(`\n ${B}${At}${R} ${a}`),new P(e,{cause:Error(`Step process exited with ${r.exitCode}`),context:this.createInterruptContext(a,o)})}if(r.status===`spawn_error`)return this.logger.error(` ${V}${W}${R} unable to start step "${a}" in ${r.error.context.workDir} (${this.formatSpawnError(r.error)})`),l?(this.logger.warn(` ${B}${G}${R} ${a} ${K}\n`),!0):(this.logger.error(` ${V}${G}${R} ${a}\n`),!1);if(r.status===`timed_out`){if(this.logger.error(` ${V}${W}${R} step timed out after ${this.formatTimeoutMs(r.timeoutMs)} "${a}" (${o.split(`
17
- `)[0]})`),e<m){if(f)try{T(f,``,`utf-8`)}catch(e){this.logger.warn(` ${B}${U}${R} status-file reset failed for ${a}: ${e instanceof Error?e.message:String(e)}`)}continue}return f&&this.cleanupStatusFile(f),l?(this.logger.warn(` ${B}${G}${R} ${a} ${K}\n`),!0):(this.logger.error(` ${V}${G}${R} ${a}\n`),!1)}if(f&&this.cleanupStatusFile(f),r.status===`status_completed`)return this.logger.info(`\n ${bt}${qt}${R} ${a} (status-file signaled completion)\n`),!0;if(r.status===`completed`&&r.exitCode===0)return this.logger.info(`\n ${bt}pass${R} ${a}\n`),!0;if(r.status===`completed`&&this.logger.error(` ${V}${W}${R} step "${a}" exited with code ${r.exitCode} (${o.split(`
18
- `)[0]})`),!h&&await this.shouldRetryForCodexQuota(a,c)){h=!0,this.logger.warn(` ${B}${Rt}${R} codex quota reached after failed step, retrying after reset`),--e;continue}return l?(this.logger.warn(` ${B}${G}${R} ${a} ${K}\n`),!0):(this.logger.error(` ${V}${G}${R} ${a}\n`),!1)}return!1}async executeCommand(e,t,n,r,i,a){return i&&this.logger.info(` ${z}(interactive: output renders directly to terminal)${R}`),await new Promise(s=>{let c=i?o(e,[],{stdio:[Tt,Tt,Tt],cwd:t,detached:!1,env:{...n,[wt]:`1`},shell:Ct}):o(e,[],{stdio:[Tt,Et,Et],cwd:t,detached:Pt,env:{...n,[wt]:`1`},shell:Ct});this.activeStep={command:e,process:c,processGroupId:Pt&&!i&&typeof c.pid==`number`?c.pid:null,stepName:r};let l=null,u=!1,d=!1,f=e=>{d||(d=!0,p&&clearTimeout(p),l&&clearTimeout(l),this.activeStep=null,s(e))},p=a===null?null:setTimeout(()=>{if(u=!0,this.killActiveStep(H)){l=setTimeout(()=>{d||(this.logger.warn(` ${B}${U}${R} ${r} forcing SIGKILL`),this.killActiveStep(zt))},Bt);return}f({status:`timed_out`,timeoutMs:a})},a);c.stdout?.on(`data`,e=>{for(let t of e.toString().split(`
19
- `))t&&this.logger.info(t)}),c.stderr?.on(`data`,e=>{for(let t of e.toString().split(`
20
- `))t&&this.logger.error(t)}),c.on(`error`,n=>{f({status:`spawn_error`,error:new ht({commandPreview:e.split(`
21
- `)[0],source:`StepRunnerService.executeCommand`,stepName:r,workDir:t},{cause:n})})}),c.on(`exit`,(e,t)=>{if(this.cleanupProcessGroup(),u&&a!==null){f({status:`timed_out`,timeoutMs:a});return}if(t===xt){f({status:`signaled`,signal:xt});return}if(t===H){f({status:`signaled`,signal:H});return}f({status:`completed`,exitCode:e??1})})})}createInterruptContext(e,t){return{phase:`step_execution`,stepName:e,commandPreview:t.split(`
22
- `)[0],source:`StepRunnerService.runStep`}}formatSpawnError(e){let t=e.cause;return t instanceof Error?`${`code`in t&&typeof t.code==`string`?t.code:`unknown-spawn-error`}${`path`in t&&typeof t.path==`string`?` at ${t.path}`:``}; verify command, shell, and permissions`:`check shell availability, PATH, and permissions`}resolveStepCommand(e,t,n){let r=e[Ft]!=null,i=r?{command:e[Ft],interactive:!0}:{command:e.run,interactive:!1},a=r?{command:e.run,interactive:!1}:{command:e[Ft],interactive:!0},o=this.resolveExactAgentCommand(i.command,t,n);if(o)return{...o,interactive:i.interactive};let s=this.resolveExactAgentCommand(a.command,t,n);if(s)return{...s,interactive:a.interactive};let c=this.resolveMissingExplicitRunner(e,n);if(c)return{command:``,interactive:!1,missingRunner:c};let l=this.resolveStringCommand(i.command,t);if(l)return{command:l,interactive:i.interactive};let u=this.resolveStringCommand(a.command,t);if(u)return{command:u,interactive:a.interactive};let d=this.resolveFirstMappedCommand(i.command,t);if(d)return{command:d,interactive:i.interactive};let f=this.resolveFirstMappedCommand(a.command,t);return f?{command:f,interactive:a.interactive}:null}resolveExactAgentCommand(e,t,n){if(!n||!e||typeof e==`string`)return null;let r=e[n];return r?{agentKey:n,command:this.parser.interpolate(r,t),interactive:!1}:null}resolveStringCommand(e,t){return!e||typeof e!=`string`?null:this.parser.interpolate(e,t)}resolveFirstMappedCommand(e,t){if(!e||typeof e==`string`)return null;let[n]=Object.values(e);return n?this.parser.interpolate(n,t):null}resolveMissingExplicitRunner(e,t){if(!t)return null;let n=this.collectMappedRunnerKeys(e);return n.length===0||n.includes(t)?null:{availableKeys:n,runner:t}}collectMappedRunnerKeys(e){let t=new Set;for(let n of[e[Ft],e.run])if(n&&typeof n!=`string`)for(let e of Object.keys(n))t.add(e);return[...t]}resolveRunner(e){return e.runner??e.cliAgent}formatStepDisplay(e,t){return e?`${e} > ${t}`:t}withInteractiveTerminalTitle(e,t){let n=`${Wt}: ${t[Ut]??e.split(`
23
- `)[0]??St}`,r=`[ -t 1 ] && printf '\\033]0;%s\\007' ${this.escapeShellArg(n)}`,i=`[ -t 1 ] && printf '\\033]0;%s\\007' ${this.escapeShellArg(Wt)}`;return`trap ${this.escapeShellArg(i)} EXIT
24
- ${r}
25
- ${e}`}escapeShellArg(e){return`'${e.replace(/'/g,`'\\''`)}'`}async shouldRetryForCodexQuota(e,t){return t?(await this.readCodexQuotaStatus(e))?.blockingLimit!=null:!1}async waitForCodexQuotaAvailability(e,t,n){if(n)for(;;){let n=await this.readCodexQuotaStatus(e),r=n?.blockingLimit;if(!r)return;let i=r.resetAt*1e3+Jt,a=Math.max(i-Date.now(),Jt),o=new Date(i).toLocaleString(),s=n?.planType?` (${n.planType})`:``;this.logger.warn(` ${B}wait ${R} codex quota ${r.limitName}/${r.window} is at ${r.usedPercent}%${s}; waiting until ${o}`),await this.waitForQuotaDelay(e,t,a)}}async readCodexQuotaStatus(e){try{return await this.quotaService.getQuotaStatus()}catch(t){return this.logger.warn(` ${B}${U}${R} unable to read codex quota for "${e}" (${this.formatQuotaError(t)})`),null}}async waitForQuotaDelay(e,t,n){let r=new AbortController;this.activeQuotaWait={controller:r,signal:null,stepName:e};try{let e=n;for(;e>0;){let t=Math.min(e,3e4);await this.waitForDelay(t,r.signal),e-=t}}catch(n){throw r.signal.aborted?new P(this.activeQuotaWait?.signal??H,{cause:Error(`Interrupted while waiting for Codex quota reset`),context:{...this.createInterruptContext(e,t),phase:`StepRunnerService.waitForCodexQuota`}}):n}finally{this.activeQuotaWait=null}}async waitForDelay(e,t){await new Promise((n,r)=>{let i=setTimeout(()=>{t.removeEventListener(`abort`,a),n()},e),a=()=>{clearTimeout(i),t.removeEventListener(`abort`,a),r(Error(`aborted`))};t.addEventListener(`abort`,a,{once:!0})})}formatQuotaError(e){return e instanceof Error&&e.message.trim().length>0?e.message:`unknown quota error`}resolveTimeoutMs(e,t){let n=e[It];if(n===void 0)return null;try{let e=Yt.parse(n);return Math.ceil(e*Vt)}catch(e){throw new gt({stepName:t,configKey:It,timeoutValue:n},{cause:e})}}resolveTimeoutRetries(e,t){let n=e[Lt];try{return Xt.parse(n)}catch(e){throw new gt({stepName:t,configKey:Lt,timeoutValue:n},{cause:e})}}formatTimeoutMs(e){return e%Vt===0?`${e/Vt} minute(s)`:`${e}ms`}cleanupProcessGroup(){if(!this.activeStep)return;let{processGroupId:e,stepName:t}=this.activeStep;if(e!==null)try{process.kill(-e,H)}catch(n){let r=n.code;r===Nt?this.logger.info(` ${z}cleanup${R} process group ${e} already exited (ESRCH)`):this.logger.warn(` ${B}${U}${R} failed to clean up process group for ${t} (pgid=${e}, error=${r})`)}}killActiveStep(e){if(!this.activeStep)return!1;let{process:t,processGroupId:n}=this.activeStep;if(n!==null)try{return process.kill(-n,e),!0}catch(e){e.code===Nt?this.logger.info(` ${z}signal${R} process group for ${this.activeStep.stepName} already exited (ESRCH)`):this.logger.warn(` ${B}${U}${R} ${Mt} process group for ${this.activeStep.stepName}`)}try{return t.kill(e)}catch(e){return this.logger.warn(` ${B}${U}${R} ${Mt} ${this.activeStep.stepName} (${e.message})`),!1}}createStatusFile(){let e=y(_(),`workflow-step-${D()}.status`);return T(e,``,`utf-8`),e}async raceStatusFile(e,t,n){return new Promise(r=>{let i=!1,a=!1,o=null,s=null,c=null,l=e=>{i||(i=!0,s&&=(s.close(),null),c&&=(clearInterval(c),null),o&&=(clearTimeout(o),null),r(e))},u=!1,d=!1,f=()=>{try{return S(t)&&w(t,`utf-8`).trim()===`YES`}catch(e){let t=e.code;return!t||!Kt.has(t)?this.logger.warn(` ${B}${U}${R} status-file read failed for ${n}: ${e instanceof Error?e.message:String(e)}`):this.logger.info(` ${z}status-file${R} transient FS error (${t}) polling ${n}`),!1}},p=()=>{if(!(a||!f())){if(a=!0,u=this.killActiveStep(H),!u){this.logger.warn(` ${B}${U}${R} ${n} status-file: SIGTERM could not be delivered, process may have already exited`);return}o=setTimeout(()=>{i||(d=!0,this.logger.warn(` ${B}${U}${R} ${n} did not exit after status-file SIGTERM, forcing SIGKILL`),this.killActiveStep(zt))},Bt)}};try{s=this.watchStatusFile(t,()=>p())}catch(e){this.logger.warn(` ${B}${U}${R} status-file watcher setup failed for ${n}: ${e instanceof Error?e.message:String(e)}`)}c=setInterval(p,1e3),p(),e.then(e=>{if(!(a||f())){l(e);return}switch(a||(a=!0,this.logger.info(` ${bt}${qt}${R} ${n} status-file observed during process exit handling`)),e.status){case`spawn_error`:case`timed_out`:l(e);return;case`signaled`:d&&this.logger.warn(` ${B}${U}${R} ${n} required SIGKILL escalation (work completed via status-file)`),l(Gt);return;case`completed`:e.exitCode===0||e.exitCode>128||d||!u?(d&&this.logger.warn(` ${B}${U}${R} ${n} required SIGKILL escalation (work completed via status-file, exit code ${e.exitCode})`),l(Gt)):l(e);return;case`status_completed`:l(e);return;default:l(e)}})})}cleanupStatusFile(e){try{S(e)&&re(e)}catch(e){this.logger.warn(` ${B}${U}${R} status-file cleanup failed: ${e instanceof Error?e.message:String(e)}`)}}};const q=`\x1B[0m`,Qt=`\x1B[1m`,$t=`\x1B[2m`,en=`\x1B[36m`,tn=`\x1B[32m`,nn=`\x1B[31m`,rn=`\x1B[34m`;var an=class{activePrompt=null;activePromptSignal=null;constructor(e,t){this.stepRunner=e,this.logger=t}async pathExists(e){try{return await c(e),!0}catch{return!1}}abortActivePrompt(e){this.activePromptSignal=e,this.activePrompt?.close()}async handleUserPrompt(e,t,n,r,i){if(!e.on||!(`user_prompt`in e.on))return!0;this.logger.info(`\n ${rn}${Qt}trigger${q} ${Qt}user_prompt${q}`),this.logger.info(` ${`─`.repeat(50)}`);let a=t,o=n.CONTEXT_FILE;if(!a&&o){let e=b(r,o);await this.pathExists(e)&&(a=(await u(e,`utf-8`)).trim(),a&&this.logger.info(` ${tn}found${q} Existing context.md -> ${o}`))}if(!a)if(i)this.logger.info(` ${en}dry ${q} Would prompt user for input`),a=`(dry-run: no prompt collected)`;else{this.logger.info(` ${en}input${q} Enter your prompt (press Enter twice to finish):\n`);let e=[],t=ae({input:process.stdin,output:process.stdout});this.activePrompt=t,this.activePromptSignal=null,a=await new Promise((n,r)=>{let i=!1,a=e=>{i||(i=!0,this.activePrompt=null,e())};t.on(`line`,n=>{n===``&&e.length>0&&e[e.length-1]===``?t.close():e.push(n)}),t.on(`SIGINT`,()=>{this.activePromptSignal=`SIGINT`,t.close()}),t.on(`close`,()=>a(()=>{let t=this.activePromptSignal;if(this.activePromptSignal=null,t){r(new P(t));return}e.length>0&&e[e.length-1]===``&&e.pop(),n(e.join(`
26
- `))}))})}if(!a)return this.logger.error(` ${nn}fail${q} No prompt provided. Aborting.`),!1;if(o){let e=b(r,o);await l(v(e),{recursive:!0}),await h(e,a,`utf-8`),this.logger.info(` ${tn}saved${q} ${a.split(`
27
- `).length} line(s) -> ${o}`)}return n.USER_PROMPT=a,this.logger.info(` ${$t}prompt${q} ${a.split(`
28
- `)[0].slice(0,80)}${a.includes(`
29
- `)?`...`:``}`),!0}async handleAgentDecision(e,t,n,r,i,a){let o=e.on?.agent_decision;if(!o?.steps)return!0;this.logger.info(`\n ${rn}${Qt}trigger${q} ${Qt}agent_decision${q}`),this.logger.info(` ${`─`.repeat(50)}`),a&&(t.HEARTBEAT_PROMPT=a,this.logger.info(` ${$t}prompt${q} ${a.split(`
30
- `)[0].slice(0,80)}${a.includes(`
31
- `)?`...`:``}`));let s=t.CONTEXT_FILE;s&&await l(v(b(i.workflowDir,s)),{recursive:!0});let c={inputs:n,matrix:{},secrets:r,env:t};for(let e of o.steps)if(!await this.stepRunner.runStep(e,c,i))return this.logger.error(`\n ${nn}Agent decision aborted workflow${q}`),!1;if(s&&!i.dryRun){let e=b(i.workflowDir,s);if(!await this.pathExists(e))return this.logger.error(` ${nn}fail${q} Context file not created: ${s}`),!1;let t=(await u(e,`utf-8`)).trim();if(!t)return this.logger.error(` ${nn}fail${q} Context file is empty: ${s}`),!1;this.logger.info(` ${tn}ready${q} context has ${t.split(`
32
- `).length} line(s)`)}return!0}};const on=[`actions/checkout`,`actions/setup-node`,`actions/cache`,`actions/upload-artifact`,`actions/download-artifact`,`pnpm/action-setup`],sn=`bold.calm.cool.dark.deep.fair.fast.free.gold.keen.kind.loud.neat.pure.rare.safe.slim.soft.tall.warm.wild.wise.blue.gray.jade.iron.zinc.ruby.opal.onyx`.split(`.`),cn=`arch.beam.bird.bolt.cape.cove.dawn.dove.echo.fern.flux.gale.hawk.haze.iris.jade.kite.lake.lynx.mesa.moth.node.palm.peak.pine.raft.reef.sage.tide.vale`.split(`.`);var ln=class{generateHumanReadableId(){return`${sn[Math.floor(Math.random()*sn.length)]}-${cn[Math.floor(Math.random()*cn.length)]}`}parseWorkflowFile(e){let t=b(e);if(!S(t))throw Error(`Workflow file not found: ${t}`);let n=w(t,`utf-8`),r=Ue.parse(O(n));if(r.imports&&this.resolveImports(r,t),!r.jobs||Object.keys(r.jobs).length===0)throw Error(`No jobs found in workflow file`);return this.resolveExtends(r),r}resolveImports(e,t,n=new Set){let r=b(t);if(n.has(r))throw Error(`Circular import detected: ${r}`);n.add(r);let i=r.replace(/\/[^/]+$/,``);for(let t of e.imports??[]){let a=b(i,t);if(!S(a))throw Error(`Imported workflow file not found: ${a} (from ${r})`);let o=O(w(a,`utf-8`));if(o.imports&&this.resolveImports(o,a,n),o.env&&(e.env={...o.env,...e.env}),o.pre&&(e.pre||={},o.pre.services&&(e.pre.services=[...o.pre.services,...e.pre.services??[]]),o.pre.steps&&(e.pre.steps=[...o.pre.steps,...e.pre.steps??[]])),o.post&&(e.post||={},o.post.services&&(e.post.services=[...o.post.services,...e.post.services??[]]),o.post.steps&&(e.post.steps=[...o.post.steps,...e.post.steps??[]])),o.worktree){e.worktree||={};for(let t of[`beforeCreate`,`afterCreate`,`beforeCompleted`,`afterCompleted`])o.worktree[t]&&!e.worktree[t]&&(e.worktree[t]=o.worktree[t])}if(o[`max-workflows`]&&!e[`max-workflows`]&&(e[`max-workflows`]=o[`max-workflows`]),o[`launch-command`]&&!e[`launch-command`]&&(e[`launch-command`]=o[`launch-command`]),o.keybindings&&(e.keybindings={...o.keybindings,...e.keybindings}),o[`system-prompt`]&&(e[`system-prompt`]?e[`system-prompt`]=`${o[`system-prompt`]}\n\n${e[`system-prompt`]}`:e[`system-prompt`]=o[`system-prompt`]),o.jobs)for(let[t,n]of Object.entries(o.jobs))t in(e.jobs??{})||(e.jobs||={},e.jobs[t]=n)}delete e.imports}resolveExtends(e){let{jobs:t}=e,n=new Map;for(let[e,r]of Object.entries(t))e.startsWith(`.`)&&n.set(e,r);for(let[e,r]of Object.entries(t)){if(e.startsWith(`.`)||!r.extends)continue;let t=Array.isArray(r.extends)?r.extends:[r.extends],i=[...r.steps??[]],a=[...r.preJob?.steps??[]],o=[...r.postJob?.steps??[]],s=[],c=[],l=[];for(let i of t){let t=n.get(i);if(!t)throw Error(`Job "${e}" extends "${i}" but template not found`);s.push(...t.steps??[]),c.push(...t.preJob?.steps??[]),l.push(...t.postJob?.steps??[]),this.applyTemplate(r,t)}r.steps=[...s,...i],this.assignJobHookSteps(r,`preJob`,[...c,...a]),this.assignJobHookSteps(r,`postJob`,[...l,...o]),delete r.extends}for(let e of n.keys())delete t[e]}applyTemplate(e,t){e.steps=[...t.steps??[],...e.steps??[]],this.mergeJobHookSteps(e,t,`preJob`),this.mergeJobHookSteps(e,t,`postJob`),(t.env||e.env)&&(e.env={...t.env,...e.env}),t[`system-prompt`]&&e[`system-prompt`]?e[`system-prompt`]=`${t[`system-prompt`]}\n\n${e[`system-prompt`]}`:t[`system-prompt`]&&(e[`system-prompt`]=t[`system-prompt`]),t[`runs-on`]&&!e[`runs-on`]&&(e[`runs-on`]=t[`runs-on`]),t.strategy&&!e.strategy&&(e.strategy=t.strategy),t.context&&!e.context&&(e.context=t.context)}mergeJobHookSteps(e,t,n){let r=t[n]?.steps??[],i=e[n]?.steps??[];this.assignJobHookSteps(e,n,[...r,...i])}assignJobHookSteps(e,t,n){if(n.length>0){e[t]={steps:n};return}delete e[t]}loadSecrets(e){let t=new Map;if(!S(e))return t;let n=w(e,`utf-8`);for(let e of n.split(`
33
- `)){let n=e.trim();if(!n||n.startsWith(`#`))continue;let r=n.indexOf(`=`);r!==-1&&t.set(n.slice(0,r),n.slice(r+1))}return t}interpolate(e,t){return e.replace(/\$\{\{\s*(.+?)\s*\}\}/g,(e,n)=>{let r=n.trim().split(`.`);if(r[0]===`inputs`&&r[1])return t.inputs.get(r[1])||``;if(r[0]===`matrix`&&r[1])return t.matrix[r[1]]||``;if(r[0]===`secrets`&&r[1])return t.secrets.get(r[1])||process.env[r[1]]||``;if(r[0]===`env`&&r[1])return t.env[r[1]]||process.env[r[1]]||``;if(r[0]===`runner`&&r[1]===`os`)return`macOS`;if(r[0]===`github`&&r[1]===`sha`)try{return a(`git rev-parse HEAD`,{encoding:`utf-8`,stdio:[`ignore`,`pipe`,`ignore`]}).trim()}catch{return`local`}return n.includes(`hashFiles`)?`local`:`\${{ ${n} }}`})}isActionSkipped(e){return on.some(t=>e.startsWith(t))}resolveJobOrder(e,t){let n=new Set,r=[],i=t=>{if(n.has(t))return;n.add(t);let a=e[t];if(!a)throw Error(`Job not found: ${t}`);let o=a.needs?Array.isArray(a.needs)?a.needs:[a.needs]:[];for(let e of o)i(e);r.push(t)};if(t)i(t);else for(let t of Object.keys(e))i(t);return r}expandMatrix(e){if(!e.strategy?.matrix)return[{}];let{include:t,...n}=e.strategy.matrix;if(t&&t.length>0)return t;let r=Object.keys(n).filter(e=>Array.isArray(n[e]));if(r.length===0)return[{}];let i=[{}];for(let e of r){let t=n[e],r=[];for(let n of i)for(let i of t)r.push({...n,[e]:String(i)});i=r}return i}};const un=`\x1B[0m`,dn=`\x1B[2m`;var fn=class{constructor(e,t){this.stepRunner=e,this.logger=t}createWorktree(e,t){let n=t.toLowerCase().replace(/[^a-z0-9-]+/g,`-`).replace(/^-|-$/g,``),r=b(v(e),`${te(e)}-worktree`),i=b(r,n);S(r)||C(r,{recursive:!0});let o=`worktree/${n}`;S(i)&&(this.logger.info(` ${dn}clean${un} Removing stale worktree at ${i}`),this.cleanupWorktreePath(e,i));try{a(`git branch -D "${o}"`,{cwd:e,stdio:[`ignore`,`pipe`,`ignore`]})}catch{}return this.logger.info(` create${un} Worktree at ${i} (branch: ${o})`),a(`git worktree add -b "${o}" "${i}" HEAD`,{cwd:e,stdio:[`ignore`,`pipe`,`ignore`]}),i}removeWorktree(e,t){if(S(t)){this.logger.info(` ${dn}remove${un} Worktree at ${t}`);try{this.cleanupWorktreePath(e,t)}catch{this.logger.warn(` warn${un} Failed to remove worktree (may need manual cleanup)`)}}}cleanupWorktreePath(e,t){if(this.isRegisteredWorktree(e,t)){a(`git worktree remove --force "${t}"`,{cwd:e,stdio:[`ignore`,`pipe`,`ignore`]});return}ne(t,{recursive:!0,force:!0})}isRegisteredWorktree(e,t){try{return a(`git worktree list --porcelain`,{cwd:e,encoding:`utf-8`,stdio:[`ignore`,`pipe`,`ignore`]}).split(`
34
- `).some(e=>e.startsWith(`worktree `)&&b(e.slice(9))===t)}catch{return!1}}async runHook(e,t,n,r,i,a){let o={inputs:r,matrix:{},secrets:i,env:n};for(let n of e.steps)if(!await this.stepRunner.runStep(n,o,{...a,workflowDir:t}))return!1;return!0}};const J=`\x1B[0m`,Y=`\x1B[1m`,pn=`\x1B[2m`,mn=`\x1B[32m`,X=`\x1B[33m`,Z=`\x1B[31m`,Q=`\x1B[34m`,$=`SIGINT`,hn=`SIGTERM`,gn=`workflow_execution`,_n=`restart-from`,vn=`user_prompt`,yn=`agent_decision`,bn=`workflow_dispatch`,xn=`Workflow post-cleanup failed`,Sn=`STOP`,Cn=`shift`,wn=`ctrl`,Tn=`meta`;var En=class{logger;parser;serviceManager;stepRunner;jobRunner;triggerService;worktreeService;registry;outputLines=[];interruptedSignal=null;constructor(e,t={}){let n=e||{info:e=>process.stdout.write(`${e}\n`),error:e=>console.error(e),warn:e=>console.error(e)},r=e=>{this.outputLines.push(e),this.outputLines.length>5e3&&this.outputLines.shift()};this.logger={info:e=>{r(e),n.info(e)},error:e=>{r(e),n.error(e)},warn:e=>{r(e),n.warn(e)}},this.parser=t.parser??new ln,this.serviceManager=t.serviceManager??new ct(this.logger),this.stepRunner=t.stepRunner??new Zt(this.parser,this.logger),this.jobRunner=t.jobRunner??new mt(this.parser,this.stepRunner,this.logger),this.triggerService=t.triggerService??new an(this.stepRunner,this.logger),this.worktreeService=t.worktreeService??new fn(this.stepRunner,this.logger),this.registry=t.registry??new Ze}async run(e){this.outputLines=[],this.interruptedSignal=null;let t=e.dryRun??!1,n=e.continueOnError??!1,r=!1,i=e=>{r||(r=!0,this.interrupt(e))},a=()=>i($),o=()=>i(hn);process.on($,a),process.on(hn,o);try{return await this.executeWorkflow(e,t,n,e.keepWorktree??!1)}catch(e){if(e instanceof P||this.isInterrupted())return await this.serviceManager.stopAll(),this.createInterruptedResult();throw e}finally{process.off($,a),process.off(hn,o),await this.serviceManager.stopAll()}}interrupt(e,t={phase:gn}){if(this.interruptedSignal)return;this.interruptedSignal=e;let n=t.phase===gn?``:` (${t.phase})`;this.logger.info(`\n\n${X}${Y}Interrupted — shutting down...${J}${n}`),this.triggerService.abortActivePrompt(e),this.stepRunner.stopActiveStep(e),this.serviceManager.stopAll()}isInterrupted(){return this.interruptedSignal===$||this.interruptedSignal===hn}createInterruptedResult(){return{exitCode:130,output:this.outputLines.join(`
35
- `)}}parseFixRestartFrom(e,t){if(!e.startsWith(`---`))return{};let n=e.indexOf(`---`,3);if(n<0)return{};let r=e.slice(3,n);for(let e of r.split(`
36
- `)){let n=e.trim();if(n.startsWith(`${_n}:`)){let e=n.slice(`${_n}:`.length).trim();return t.includes(e)?{restartFrom:e}:{invalidTarget:e}}}return{}}async pathExists(e){try{return await c(e),!0}catch(e){if(e.code===`ENOENT`)return!1;throw e}}async waitForServiceStartup(){await new Promise(e=>{setTimeout(e,2e3)})}resolveRunner(e){if(e.runner&&e.cliAgent&&e.runner!==e.cliAgent)throw Error(`Conflicting runner selectors: runner="${e.runner}" cliAgent="${e.cliAgent}"`);return e.runner??e.cliAgent}async executeWorkflow(e,t,n,r=!1){let i=b(e.workflowPath),o=this.parser.parseWorkflowFile(i),s=this.resolveRunner(e),c=this.installStopKeybinding(o.keybindings?.[Sn]),l=null,d=null,f=null,p=v(i),g=p,_=null,ee=!1,y=!1,x=!!o.worktree,S=new Map,C=e.secretFile?this.parser.loadSecrets(e.secretFile):new Map,w={};try{let c=o.on?.[bn]?.inputs||{};for(let[e,t]of Object.entries(c))t.default&&S.set(e,t.default);if(e.inputs)for(let[t,n]of Object.entries(e.inputs))S.set(t,n);if(o.env)for(let[e,t]of Object.entries(o.env))w[e]=String(t);if(e.env)for(let[t,n]of Object.entries(e.env))w[t]=n;s&&(w.WORKFLOW_RUNNER=s,w.WORKFLOW_CLI_AGENT=s);let f=p;for(;f!==`/`;){if(await this.pathExists(b(f,`.git`))){p=f;break}f=v(f)}let ne=o[`max-workflows`];if(ne){let t=this.registry.resolveWorkspace(e.workspace,o.workspace),n=await this.registry.countRunningWorkflows(t);if(n>=ne)throw new tt(t,n,ne)}let re=o[`launch-command`];if(re&&!e.skipLaunch){let t=this.buildLaunchCliCommand(e,i),n=e.name||o.name||te(i),r=e.name?n:`${n}-${this.parser.generateHumanReadableId()}`,s=re.replace(`{name}`,r).replace(`{command}`,t);this.logger.info(`${pn}Delegating via launch-command: ${s}${J}`);try{return a(s,{stdio:`inherit`,cwd:p}),{exitCode:0,output:this.outputLines.join(`
37
- `)}}catch(e){return{exitCode:e.status??1,output:this.outputLines.join(`
38
- `)}}}let ie=e.name||o.name||te(i),T=e.name?ie:`${ie}-${this.parser.generateHumanReadableId()}`;if(l=await this.registry.createRun({displayName:T,dryRun:t,workflowPath:i,workflowWorkspace:o.workspace,workspace:e.workspace}),w.WORKFLOW_RUN_DIR=l.runDir,w.WORKFLOW_WORKSPACE=l.workspace,w.CONTEXT_FILE&&(w.CONTEXT_FILE=l.contextPath,w.CHANGELOG_FILE=l.changelogPath),this.logger.info(`${Y}${`═`.repeat(60)}${J}`),this.logger.info(` ${Y}run-workflow${J} - ${l.displayName}`),this.logger.info(`${Y}${`═`.repeat(60)}${J}`),this.logger.info(` File: ${i}`),this.logger.info(` Workspace: ${l.workspace||`default`}`),this.logger.info(` Run Dir: ${l.runDir}`),this.logger.info(` CWD: ${p}`),S.size>0&&this.logger.info(` Inputs: ${[...S.entries()].map(([e,t])=>`${e}=${t}`).join(`, `)}`),C.size>0&&this.logger.info(` Secrets: ${[...C.keys()].join(`, `)}`),e.job&&this.logger.info(` Target: ${e.job}`),s&&this.logger.info(` Runner: ${s}`),o.keybindings?.[Sn]&&this.logger.info(` Stop Key: ${o.keybindings[Sn]}`),o.pre){let e=o.pre.services?.length||0,t=o.pre.steps?.length||0;this.logger.info(` Pre: ${e} service(s), ${t} setup step(s)`)}if(o.post){let e=o.post.services?.length||0,t=o.post.steps?.length||0;this.logger.info(` Post: ${e} service(s), ${t} cleanup step(s)`)}x&&this.logger.info(` Worktree: enabled${r?` (keep on completion)`:``}`);let ae=o.on&&vn in o.on?vn:o.on?.[yn]?yn:bn;if(this.logger.info(` Trigger: ${ae}`),this.logger.info(` Mode: ${t?`dry-run`:`execute`}`),this.logger.info(`${Y}${`═`.repeat(60)}${J}`),!await this.triggerService.handleUserPrompt(o,e.prompt||null,w,p,t))return this.isInterrupted()?(d=this.createInterruptedResult(),d):(this.logger.error(`\n${Z}${Y}Workflow aborted: no prompt provided${J}`),d={exitCode:1,output:this.outputLines.join(`
39
- `)},d);if(!await this.triggerService.handleAgentDecision(o,w,S,C,{runner:s,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,workflowDir:p},e.prompt||null))return this.isInterrupted()?(d=this.createInterruptedResult(),d):(this.logger.info(`\n${X}${Y}Workflow skipped: agent decided nothing to do${J}`),d={exitCode:2,output:this.outputLines.join(`
40
- `)},d);if(g=p,x&&!t)o.worktree?.beforeCreate&&(this.logger.info(`\n ${Q}${Y}worktree${J} ${Y}beforeCreate${J}`),this.logger.info(` ${`─`.repeat(50)}`),await this.worktreeService.runHook(o.worktree.beforeCreate,p,w,S,C,{runner:s,cliAgent:e.cliAgent,dryRun:t,continueOnError:n})||(this.isInterrupted()?d=this.createInterruptedResult():(this.logger.error(`\n${Z}${Y}Workflow aborted: worktree beforeCreate hook failed${J}`),d={exitCode:1,output:this.outputLines.join(`
41
- `)}))),d||(this.logger.info(`\n ${Q}${Y}worktree${J} ${Y}Creating worktree${J}`),this.logger.info(` ${`─`.repeat(50)}`),_=this.worktreeService.createWorktree(p,l.displayName),p=_,w.WORKTREE_PATH=_,w.WORKFLOW_NAME=l.displayName,w.ORIGINAL_REPO_PATH=g,o.worktree?.afterCreate&&(this.logger.info(`\n ${Q}${Y}worktree${J} ${Y}afterCreate${J}`),this.logger.info(` ${`─`.repeat(50)}`),await this.worktreeService.runHook(o.worktree.afterCreate,_,w,S,C,{runner:s,cliAgent:e.cliAgent,dryRun:t,continueOnError:n})||(this.isInterrupted()?d=this.createInterruptedResult():(this.logger.error(`\n${Z}${Y}Workflow aborted: worktree afterCreate hook failed${J}`),d={exitCode:1,output:this.outputLines.join(`
42
- `)}),y=!r)));else if(x&&t){let e=v(p),t=te(p),n=l.displayName.toLowerCase().replace(/[^a-z0-9-]+/g,`-`).replace(/^-|-$/g,``),r=b(e,`${t}-worktree`,n);this.logger.info(`\n ${Q}${Y}worktree${J} ${Y}(dry-run)${J} Would create at ${r}`)}!d&&o.pre&&(await this.runPreBlock(o.pre,w,S,C,{runner:s,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,workflowDir:p})||(this.isInterrupted()?d=this.createInterruptedResult():(this.logger.error(`\n${Z}${Y}Workflow aborted: pre-conditions failed${J}`),d={exitCode:1,output:this.outputLines.join(`
43
- `)}),y=!!(_&&!r)));let oe=Number(w.MAX_RETRIES)||3,E=this.parser.resolveJobOrder(o.jobs,e.job||null);this.logger.info(`\n ${pn}Job order: ${E.join(` -> `)} (max retries: ${oe})${J}`);let se=new Set,D=w.WORKFLOW_RUN_DIR?b(w.WORKFLOW_RUN_DIR,`fix.md`):null,O=0,k=0;for(;!d&&k<E.length;){let r=E[k],i=o.jobs[r],a=await this.jobRunner.runJob(r,i,{inputs:S,secrets:C,workflowEnv:w,workflowSystemPrompt:o[`system-prompt`],jobOrder:E,allJobs:o.jobs},{runner:s,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,workflowDir:p,maxRetries:oe});if(this.isInterrupted()&&(d=this.createInterruptedResult()),!d&&!a&&(this.logger.error(`\n${Z}${Y}Workflow failed at job "${r}"${J}`),await this.serviceManager.stopAll(),d={exitCode:1,output:this.outputLines.join(`
44
- `)}),!d){if(se.add(r),D&&await this.pathExists(D)&&!t){if(O++,O>3){this.logger.error(`\n${Z}${Y}Fix loop limit reached (3 cycles). Aborting.${J}`),d={exitCode:1,output:this.outputLines.join(`
45
- `)};continue}let e=(await u(D,`utf-8`)).trim();await m(D);let{restartFrom:t,invalidTarget:n}=this.parseFixRestartFrom(e,E);if(this.logger.info(`\n${X}${Y}fix.md detected after "${r}" (cycle ${O}/3)${J}`),this.logger.info(`${pn}${e.split(`
46
- `)[0].slice(0,100)}${e.includes(`
47
- `)?`...`:``}${J}`),n&&this.logger.warn(`${X}fix.md restart-from "${n}" is not a valid job (available: ${E.join(`, `)}), falling back to default${J}`),w.CONTEXT_FILE&&await this.pathExists(w.CONTEXT_FILE)){let t=await u(w.CONTEXT_FILE,`utf-8`);await h(w.CONTEXT_FILE,`${t}\n\n---\n## Fix Request (cycle ${O})\n${e}`,`utf-8`)}let i=t?E.indexOf(t):-1,a=E.indexOf(`development`);k=i>=0?i:a>=0?a:1,this.logger.info(`${X}Restarting from job "${E[k]}"${J}\n`);continue}k++}}!d&&this.isInterrupted()&&(d=this.createInterruptedResult()),d||=(ee=!0,this.logger.info(`\n${Y}${`═`.repeat(60)}${J}`),this.logger.info(` ${mn}${Y}Workflow completed successfully${J}`),this.logger.info(` Jobs run: ${[...se].join(`, `)}`),_&&this.logger.info(` Worktree: ${_}`),this.logger.info(`${Y}${`═`.repeat(60)}${J}\n`),{exitCode:0,output:this.outputLines.join(`
48
- `)})}catch(e){if(f=e,e instanceof P||this.isInterrupted())d=this.createInterruptedResult();else throw e}finally{if(c?.(),ee&&d?.exitCode===0&&x&&o.worktree?.beforeCompleted&&!t&&_&&(this.logger.info(`\n ${Q}${Y}worktree${J} ${Y}beforeCompleted${J}`),this.logger.info(` ${`─`.repeat(50)}`),await this.worktreeService.runHook(o.worktree.beforeCompleted,_,w,S,C,{runner:s,cliAgent:e.cliAgent,dryRun:t,continueOnError:n})||(this.isInterrupted()?d=this.createInterruptedResult():this.logger.warn(`\n${X}${Y}Warning: worktree beforeCompleted hook failed${J}`))),o.post)try{await this.runPostBlock(o.post,w,S,C,{runner:s,cliAgent:e.cliAgent,dryRun:t,continueOnError:n,workflowDir:p})||(this.logger.error(`\n${Z}${Y}${xn}${J}`),(!d||d.exitCode===0||d.exitCode===2)&&(d={exitCode:1,output:this.outputLines.join(`
49
- `)}))}catch(e){f??=e,this.logger.error(`\n${Z}${Y}${xn}${J}`),(!d||d.exitCode===0||d.exitCode===2)&&(d={exitCode:1,output:this.outputLines.join(`
50
- `)})}ee&&d?.exitCode===0&&x&&o.worktree?.afterCompleted&&!t&&!r&&(this.logger.info(`\n ${Q}${Y}worktree${J} ${Y}afterCompleted${J}`),this.logger.info(` ${`─`.repeat(50)}`),await this.worktreeService.runHook(o.worktree.afterCompleted,g,w,S,C,{runner:s,cliAgent:e.cliAgent,dryRun:t,continueOnError:n})||(this.isInterrupted()?d=this.createInterruptedResult():this.logger.warn(`\n${X}${Y}Warning: worktree afterCompleted hook failed${J}`))),y&&_&&this.worktreeService.removeWorktree(g,_),d&&={...d,output:this.outputLines.join(`
51
- `)},l&&await this.finalizeRegistryRun(l,d,f)}return d??{exitCode:1,output:this.outputLines.join(`
52
- `)}}installStopKeybinding(e){if(!e)return null;let t=this.parseKeybinding(e);if(!t)return this.logger.warn(` ${X}Keybinding ${Sn} ignored: unsupported value "${e}"${J}`),null;if(!process.stdin.isTTY||typeof process.stdin.setRawMode!=`function`)return null;let n=process.stdin,r=n.isRaw;oe(n);let i=(e,n={})=>{if(this.keyMatchesBinding(n,t)){this.interrupt($,{phase:gn,source:`keybinding`});return}this.isCtrlC(n)&&this.interrupt($,{phase:gn,source:`keypress`})};return n.on(`keypress`,i),n.setRawMode(!0),n.resume(),()=>{n.off(`keypress`,i),!r&&typeof n.setRawMode==`function`&&n.setRawMode(!1)}}parseKeybinding(e){let t=e.toLowerCase().split(`+`).map(e=>e.trim()).filter(Boolean),n=t.at(-1);if(!n)return null;let r=new Set(t.slice(0,-1));return[...r].find(e=>e!==wn&&e!==Cn&&e!==Tn&&e!==`cmd`)?null:{ctrl:r.has(wn),key:n,meta:r.has(Tn)||r.has(`cmd`),shift:r.has(Cn)}}keyMatchesBinding(e,t){let n=e.name?.toLowerCase();if(!n||n!==t.key)return!1;let r=!!e.ctrl===t.ctrl,i=!!e.meta===t.meta,a=t.shift?!0:!e.shift;return r&&i&&a}isCtrlC(e){return e.ctrl===!0&&e.name?.toLowerCase()===`c`||e.sequence===``}async finalizeRegistryRun(e,t,n){let r=t?.exitCode===130||n instanceof P,i=t?.exitCode??(r?130:1),a=i===0||i===2?`completed`:`error`,o=i===0?`success`:i===2?`skipped`:r||this.isInterrupted()?`interrupted`:`failed`,s=n instanceof Error?n.message:i===1?this.lastMeaningfulOutputLine():void 0;await this.registry.finalizeRun(e,{errorMessage:s,exitCode:i,outcome:o,stage:a})}resolveCliPath(){let e=et,t=b(`/`);for(;e!==t;){let t=b(e,`cli.cjs`);if(S(t))return t;let n=b(e,`dist`,`cli.cjs`);if(S(n))return n;e=v(e)}throw Error(`Unable to locate workflow-mcp cli.cjs from `+et)}buildLaunchCliCommand(e,t){let n=[`node`,this.resolveCliPath(),`run-workflow`,t,`--skip-launch`],r=this.resolveRunner(e);if(e.job&&n.push(`--job`,e.job),r&&n.push(`--runner`,r),e.dryRun&&n.push(`--dry-run`),e.continueOnError&&n.push(`--continue-on-error`),e.keepWorktree&&n.push(`--keep-worktree`),e.prompt&&n.push(`--prompt`,`'${e.prompt.replace(/'/g,`'\\''`)}'`),e.name&&n.push(`--name`,`'${e.name}'`),e.workspace&&n.push(`--workspace`,`'${e.workspace}'`),e.secretFile&&n.push(`--secret-file`,e.secretFile),e.inputs)for(let[t,r]of Object.entries(e.inputs))n.push(`--input`,`${t}=${r}`);if(e.env)for(let[t,r]of Object.entries(e.env))n.push(`--env`,`${t}=${r}`);return n.join(` `)}lastMeaningfulOutputLine(){return[...this.outputLines].reverse().find(e=>e.trim().length>0)}async runPreBlock(e,t,n,r,i){if(this.logger.info(`\n ${Q}${Y}pre${J} ${Y}Pre-conditions${J}`),this.logger.info(` ${`─`.repeat(50)}`),t.CONTEXT_FILE&&await l(v(b(i.workflowDir,t.CONTEXT_FILE)),{recursive:!0}),e.services&&e.services.length>0){this.logger.info(`\n ${Q}${Y}services${J}`);for(let n of e.services)if(await this.serviceManager.startService(n,t,i.workflowDir,i.dryRun)&&n[`ready-check`]&&!i.dryRun){let e=await this.serviceManager.waitForServiceReady(n,i.workflowDir,t,()=>this.interruptedSignal!==null);if(this.isInterrupted())return!1;if(!e&&!i.continueOnError)return await this.serviceManager.stopAll(),!1}!i.dryRun&&this.serviceManager.getRunningServices().length>0&&await this.waitForServiceStartup()}if(e.steps&&e.steps.length>0){this.logger.info(`\n ${Q}${Y}setup${J}`);let a={inputs:n,matrix:{},secrets:r,env:t};for(let t of e.steps)if(!await this.stepRunner.runStep(t,a,i))return this.logger.error(`\n ${Z}Pre-condition step failed${J}`),await this.serviceManager.stopAll(),!1}return this.logger.info(`\n ${mn}Pre-conditions ready${J}`),!0}async runPostBlock(e,t,n,r,i){this.logger.info(`\n ${Q}${Y}post${J} ${Y}Cleanup${J}`),this.logger.info(` ${`─`.repeat(50)}`),await this.serviceManager.stopAll();try{if(e.services&&e.services.length>0){this.logger.info(`\n ${Q}${Y}services${J}`);for(let n of e.services)if(await this.serviceManager.startService(n,t,i.workflowDir,i.dryRun)&&n[`ready-check`]&&!i.dryRun&&!await this.serviceManager.waitForServiceReady(n,i.workflowDir,t,()=>!1)&&!i.continueOnError)return!1;!i.dryRun&&this.serviceManager.getRunningServices().length>0&&await this.waitForServiceStartup()}if(e.steps&&e.steps.length>0){this.logger.info(`\n ${Q}${Y}cleanup${J}`);let a={inputs:n,matrix:{},secrets:r,env:t};for(let t of e.steps)if(!await this.stepRunner.runStep(t,a,i))return this.logger.error(`\n ${Z}Post-cleanup step failed${J}`),!1}return this.logger.info(`\n ${mn}Cleanup complete${J}`),!0}finally{await this.serviceManager.stopAll()}}};const Dn=r.object({workflowPath:r.string().describe(`Path to the workflow YAML file (e.g., .github/workflows/deploy.yml)`),runner:r.string().optional().describe(`Preferred runner key for step command maps (e.g. ollama, claude, codex)`),cliAgent:r.string().optional().describe(`Deprecated alias for runner. Preferred runner command key for step command maps.`),job:r.string().optional().describe(`Run only this job (and its dependencies)`),inputs:r.record(r.string(),r.string()).optional().describe(`Workflow dispatch inputs as key-value pairs`),env:r.record(r.string(),r.string()).optional().describe(`Extra environment variables as key-value pairs`),secretFile:r.string().optional().describe(`Path to a dotenv-style secrets file`),dryRun:r.boolean().optional().describe(`Print steps without executing`),continueOnError:r.boolean().optional().describe(`Continue past step failures`),keepWorktree:r.boolean().optional().describe(`Keep worktree on completion (skip merge and cleanup for retry)`),prompt:r.string().optional().describe(`User prompt for user_prompt trigger workflows`),name:r.string().optional().describe(`Name for the workflow run context directory`),workspace:r.string().optional().describe(`Workspace for workflow registry storage`)});var On=class e{static TOOL_NAME=`run_workflow`;constructor(e=new En){this.service=e}getInputSchema(){return Dn}getDefinition(){return{name:e.TOOL_NAME,description:`Run a GitHub Actions workflow file locally. Parses the workflow YAML and executes run steps on the host machine, respecting job dependencies, matrix strategies, environment variables, and workflow_dispatch inputs.`,inputSchema:r.toJSONSchema(Dn)}}async execute(e){try{let t=Dn.parse(e);if(t.runner&&t.cliAgent&&t.runner!==t.cliAgent)throw Error(`Conflicting runner selectors: runner="${t.runner}" cliAgent="${t.cliAgent}"`);let n={cliAgent:t.cliAgent,runner:t.runner??t.cliAgent,workflowPath:t.workflowPath,job:t.job,inputs:t.inputs,env:t.env,secretFile:t.secretFile,dryRun:t.dryRun,continueOnError:t.continueOnError,keepWorktree:t.keepWorktree,prompt:t.prompt,name:t.name,workspace:t.workspace},r=await this.service.run(n);return r.exitCode!==0&&r.exitCode!==2?{content:[{type:`text`,text:`Workflow failed (exit code ${r.exitCode}):\n\n${r.output}`}],isError:!0}:{content:[{type:`text`,text:r.output||`Workflow completed successfully.`}]}}catch(e){return{content:[{type:`text`,text:`Error: ${e instanceof Error?e.message:`Unknown error`}`}],isError:!0}}}},kn=class e{static TOOL_NAME=`schedule-cron`;constructor(e=new Te){this.service=e}getInputSchema(){return be}getDefinition(){return{name:e.TOOL_NAME,description:`Schedule a headless Claude Code or Codex CLI run as a system cron job. Specify either a cron expression or an interval in minutes.`,inputSchema:r.toJSONSchema(be)}}async execute(t){try{let e=be.parse(t),n=await this.service.schedule(e);return{content:[{type:`text`,text:JSON.stringify(n,null,2)}]}}catch(n){let r=new le(`Failed to schedule cron job.`,`SCHEDULE_CRON_TOOL_FAILED`,{tool:e.TOOL_NAME,name:t.name,cwd:t.cwd},{cause:n});return console.error(`[${e.TOOL_NAME}] ${r.message}`,r.context),{content:[{type:`text`,text:JSON.stringify({code:r.code,context:r.context,message:r.message},null,2)}],isError:!0}}}};const An=[On.TOOL_NAME,$e.TOOL_NAME,kn.TOOL_NAME,Ee.TOOL_NAME];function jn(){let r=new e({name:`workflow-mcp`,version:`0.1.0`},{capabilities:{tools:{}}}),i=new On,a=new $e,o=new kn,s=new Ee;return r.setRequestHandler(n,async()=>({tools:[i.getDefinition(),a.getDefinition(),o.getDefinition(),s.getDefinition()]})),r.setRequestHandler(t,async e=>{let{name:t,arguments:n}=e.params;if(t===On.TOOL_NAME)return await i.execute(n);if(t===$e.TOOL_NAME)return await a.execute(n);if(t===kn.TOOL_NAME)return await o.execute(n);if(t===Ee.TOOL_NAME)return await s.execute();throw new ce(t,An)}),r}var Mn=class{server;transport=null;constructor(e){this.server=e}async start(){this.transport=new k,await this.server.connect(this.transport),console.error(`workflow-mcp MCP server started on stdio`)}async stop(){this.transport&&=(await this.transport.close(),null)}};export{Ze as a,be as c,vt as i,jn as n,Te as o,En as r,he as s,Mn as t};