@agimon-ai/doompi-workflow 0.0.1-alpha.0 → 0.0.1-alpha.2

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.
@@ -1,10 +1,11 @@
1
- import{LEADER_DISABLE_ACTION as e,LEADER_ENABLE_ACTION as t,LEADER_LAUNCH_ACTION as n,LEADER_MANAGE_ACTION as r,LEADER_RECOVER_ACTION as i,PACKAGE_SOURCE as a}from"../types.mjs";import{registerRunProvider as o}from"./backgroundWork.mjs";import{PI_SESSION_ENV as s,finishedRunSummary as c,isSessionRun as l,launchNotice as u,runsForSession as ee,toolResultText as d,withOptions as f}from"./piToolBridge.mjs";import{WORKFLOW_PI_TOOL_NAMES as te,createWorkflowLaunchExecutor as ne,registerWorkflowPiTools as re}from"./piTools.mjs";import{openWorkflowLaunchWizard as ie}from"./workflowLauncher.mjs";import{openWorkflowPickerOverlay as ae}from"./workflowPicker.mjs";import{openWorkflowChoice as oe,openWorkflowInput as se}from"./workflowPrompt.mjs";import{TerminalInputBatcher as ce}from"./workflowOverlay.mjs";import{WorkflowRunPanelComponent as le,runPanelUiOptions as ue}from"./workflowRunPanel.mjs";import{stepReportLines as de}from"./workflowStatusRow.mjs";import{WorkflowInspectorComponent as fe}from"./workflowInspector.mjs";import{WORKFLOW_PULSE_FRAMES as pe,workflowFooterStatus as me}from"./workflowFooterStatus.mjs";import{formatWorkflowStepMessage as he,isWorkflowStepMessageDetails as ge,renderWorkflowStepMessage as _e,workflowStepMessageDetails as ve}from"./workflowStepMessage.mjs";import{createRequire as p}from"node:module";import{registerDoomLeaderActionHandlers as ye}from"@agimon-ai/doompi-extension-contracts/leader";import{spawn as m}from"node:child_process";import{existsSync as be}from"node:fs";import{basename as h,dirname as xe,extname as Se,resolve as Ce}from"node:path";import{fileURLToPath as we}from"node:url";import{createDoomTelemetry as Te}from"@agimon-ai/doompi-telemetry";import{resolveRootSessionId as g}from"@agimon-ai/doompi-extension-contracts/child-process";import{registerDoomFooterContribution as Ee}from"@agimon-ai/doompi-extension-contracts/footer";import{registerDoomModeContribution as De}from"@agimon-ai/doompi-extension-contracts/mode";import{registerDoomSkillSource as Oe}from"@agimon-ai/doompi-extension-contracts/skills";import{truncateToWidth as _,visibleWidth as v}from"@earendil-works/pi-tui";import{createProtocolRuntime as ke}from"@agimon-ai/doompi-extension-contracts/protocol";import{WorkflowModeChanged as Ae}from"@agimon-ai/doompi-extension-contracts/workflow";import{createEmbeddedWorkflowFeature as je,readWorkflowProgress as Me,summarizeWorkflowProgress as y}from"@agimon-ai/workflow-mcp";const Ne={overlay:!0,overlayOptions:{anchor:`top-left`,width:`100%`,maxHeight:`100%`,margin:0}},Pe=`agiflow-workflow`,Fe=`workflow-step`,Ie=`workflow-mcp-follow`,Le=`SPC › w / workflows › w / launch`,Re=`paused`,b=`ctrl+alt+w`,x=`ctrl+alt+q`,ze={alt:`⌥`,cmd:`⌘`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`};function S(e,t=process.platform){let n=e.split(`+`);return t===`darwin`?n.map(e=>ze[e]??e.toUpperCase()).join(``):n.map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(`+`)}const C=1e4,w=`running`,T=`completed`,E=`failed`,D=`error`,Be=`error`,Ve=`doom_workflow.launch_requested`,He=`../../skills`,O={running:`▸`,repair:`⚠`,completed:`✔`,failed:`✖`,separator:`·`};function Ue(e,t=process.platform){let n=S(x,t);return e?[`Esc Esc closes this view`,`${S(b,t)} switches typing`,`${n} closes`].join(` ${O.separator} `):`view only ${O.separator} ${n} closes this view`}function We(e,t=process.platform){let n=[S(x,t),`close`];return e?[[`Esc Esc`,`close`],[S(b,t),`switch typing`],n]:[n]}function Ge(e,t,n,r){let i=`${O.running} ${e.runKey}`,a=[e.workspace,A(e),e.runner].filter(Boolean).join(` ${O.separator} `),o=`${S(b)} to background`,s=n.fg(`accent`,_(i,r));return v(i)+3+v(a)<=r&&(s+=` `+n.fg(`muted`,a),v(i)+3+v(a)+3+v(o)<=r&&(s+=` `+n.fg(`dim`,o))),[s,...t.map(e=>_(e,r))]}function Ke(e,t,n){let r=[];for(let i of e){let e=i.status===T?O.completed:i.status===E?O.failed:O.running,a=i.status===T?`success`:i.status===E?Be:`accent`,o=i.total?` ${O.separator} ${(i.index??0)+1}/${i.total}`:``;if(r.push(_(t.fg(a,`${e} ${i.name}`)+t.fg(`dim`,o),n)),i.status===w)for(let e of i.steps){let i=e.status===T?O.completed:e.status===E?O.failed:O.running,a=e.status===w?`muted`:e.status===E?Be:`dim`;r.push(_(t.fg(a,` ${i} ${e.name}`),n))}}return r}function k(e,t){let n=Date.parse(e.startedAt);return Number.isFinite(n)?t.filter(e=>{let t=Date.parse(e.at);return!Number.isFinite(t)||t>=n}):t}function A(e){return e.stale?`stale`:e.stage===D?E:e.stage}const qe=[`CMUX_SURFACE_ID`,`CMUX_WORKSPACE_ID`,`WORKFLOW_LAUNCHER_TYPE`,`WORKFLOW_TMUX_SESSION_NAME`,`WORKFLOW_STATUS_FILE`],j=`tmux`,M=`cmux`,Je=`native`,Ye=`No durable launcher identity is recorded for this run.`;function Xe(e){return h(e,Se(e))}function Ze(){let e={...process.env};for(let t of qe)delete e[t];return e}function Qe(){let e=process.env.WORKFLOW_LAUNCHER;return e===j||e===M?e:void 0}function $e(e){return e.map(e=>`'${e.replaceAll(`'`,`'\\''`)}'`).join(` `)}function et(){try{return p(import.meta.url).resolve(`@agimon-ai/workflow-mcp/cli`)}catch{return}}const tt=(e,t,n)=>{m(e,[...t],{...n,detached:!0,stdio:`ignore`}).unref()};function nt(e,t){if(Array.isArray(e)){for(let n of e)nt(n,t);return}if(!(!e||typeof e!=`object`))for(let[n,r]of Object.entries(e))(n===`interactiveRun`||n===`run`)&&r&&typeof r==`object`&&!Array.isArray(r)?t.push(Object.keys(r)):nt(r,t)}function rt(e){let t=[];if(nt(e,t),t.length!==0)return t[0].filter(e=>t.every(t=>t.includes(e))).sort()}async function N(e,t){let n=[],r=1,i=!0;for(;i;){let a=await e.listRunsPage({page:r,pageSize:100,workspace:t});n.push(...a.items),i=a.hasNextPage,r+=1}return n}function it(e){return[e.runKey,e.workspace,A(e),e.runner].filter(Boolean).join(` ${O.separator} `)}function P(p={}){return m=>{let h=p.featureFactory?.()??je(),{parser:Se,recoverService:_,registry:v,runService:ze,runTool:E}=h,Be=p.spawnDetached??tt,qe=p.monitorIntervalMs??3e4,nt=p.followIntervalMs??2e3,P,at=!1,F,ot=!1,I,st=new Map,L=new Map,ct=new Set,lt=0,R,ut,dt,ft=0,z=!1,pt=!1,mt=De(m,{source:a,id:`workflow`,order:20}),ht={runs:[],failed:0},gt,_t=0,B,vt=Ee(m,{source:a,id:`workflow-runs`,order:50}),yt,V,bt,xt=!1,St=new Map,H,Ct=[],U=new Map,wt=new Map,W=new Map,Tt=[],Et=new Map,Dt,G,K=e=>(G??=Te({serviceName:`doom-workflow`,packageName:`@agimon-ai/doompi-workflow`,cwd:e?.cwd,env:process.env,enableLogs:!0,enableTraces:!0}),G),Ot=e=>{e&&(Dt=g(e))},kt=()=>{let e=Dt??yt?.sessionManager.getSessionId();return e?g(e):void 0},At=(e,t)=>e.sessionManager.getSessionId().trim()===t,jt=h.createRecoverTool({envOverrides:()=>{let e=kt();return e?{[s]:e}:void 0}}),Mt=h.createControlTool(),q=new Set,Nt=e=>{let t=Date.now();return q.add(e),K().recordEvent(`doom_workflow.run_started`,{outcome:`started`}),e.then(()=>(q.delete(e),K().recordEvent(`doom_workflow.run_finished`,{duration_ms:Date.now()-t,outcome:`completed`})),n=>(q.delete(e),K().recordError(`doom_workflow.run_failed`,n,{duration_ms:Date.now()-t}))),e},Pt=(e,t)=>{let n=rt(Se.parseWorkflowFile(e));if(!(!n||n.includes(t)))return`Runner ${JSON.stringify(t)} is not available across this workflow. Compatible runners: ${n.join(`, `)||`none`}.`},Ft=ne({activeRunCount:async()=>ft,observeSession:Ot,rejectRunner:Pt,runTool:E,trackPendingRun:Nt,...p.launchAckPollMs===void 0?{}:{launchAckPollMs:p.launchAckPollMs},...p.launchAckTimeoutMs===void 0?{}:{launchAckTimeoutMs:p.launchAckTimeoutMs},findLaunchedRun:async({sessionId:e,since:t,workflowPath:n})=>{let r=Xe(n),i=n=>{if(!l(n,e)||n.stale||n.workflowId&&n.workflowId!==r)return!1;let i=Date.parse(n.startedAt);return Number.isFinite(i)&&i>=t-2e3};return H?(await H.refresh(),Tt.find(i)):(await N(v)).find(i)},onLateFailure:(e,t)=>{let n=e instanceof Error?e.message:String(e);t.ui.notify(u(`Workflow launch failed after it was reported started.\n${n}`),`warning`)},onLaunch:async e=>(await K(e).recordEvent(Ve,{outcome:`requested`}),X(e,!1))}),It=(e,t)=>{if(!e.isIdle())return;let n=Y(t),r=Et.get(n);r||(r=new Set,Et.set(n,r));for(let e of de(t.runKey,W.get(n)??[])){if(r.has(e.key))continue;r.add(e.key);let n=ve(t.displayName,e);m.sendMessage({customType:Fe,content:he(n),details:n,display:!0},{triggerTurn:!1})}},Lt=e=>e.filter(e=>e.stage===w&&!e.stale).map(e=>{let t=Y(e),n=U.get(t),r=n?[...n.values()].includes(w):!1,i=e.executionState===Re?`paused`:r?`running`:`starting`,a=wt.get(t);return{key:t,state:i,...a===void 0?{}:{progress:a}}}),Rt=(e,t,n,r)=>{let i=U.get(e)??new Map;(r||!i.has(t))&&i.set(t,n),U.set(e,i)},zt=(e,t)=>{t.phase===`job`&&t.status===w&&(t.index===void 0||!t.total||wt.set(e,Math.min(1,Math.max(0,t.index)/t.total)))},J,Bt=e=>{J||(J=setTimeout(()=>{J=void 0,X(e,!0)},120),J.unref?.())},Vt=e=>{W.delete(e),U.delete(e),wt.delete(e),Et.delete(e)},Ht=async e=>{let t=g(e.sessionManager.getSessionId()),n=h.createRunControl({recordFilter:e=>l(e,t)});H=n,Ct.push(n.on(`step`,t=>{let n=Y(t.record),r=W.get(n)??[];r.push({at:t.at,job:t.job,status:t.status,step:t.step,type:`step`}),W.set(n,r),Rt(n,t.job,w,!1),It(e,t.record),Bt(e)}),n.on(`job`,t=>{let n=Y(t.record);Rt(n,t.job,t.status,!0),zt(n,t),Bt(e)}),n.on(`runStarted`,t=>{Tt.unshift(t.record),Tt.length>32&&(Tt.length=32),Bt(e)}),n.on(`runUpdated`,()=>void X(e,!0)),n.on(`runFinished`,()=>void X(e,!0))),await n.start()},Ut=()=>{J&&clearTimeout(J),J=void 0;for(let e of Ct.splice(0))e();H?.dispose(),H=void 0,W.clear(),U.clear(),wt.clear(),Et.clear()},Wt=()=>{V?.teardown(),V?.handle?.hide(),V=void 0},Y=e=>e.runId??`${e.workspace}/${e.runKey}`,Gt=(e,t=!1)=>{let n=me(ht,_t);!t&&n?.fingerprint===gt||(gt=n?.fingerprint,vt.update(n?.footer),e.ui.setStatus(Pe,n?.text),qt(e,n?.pulsing??!1))},Kt=()=>{z!==pt&&(pt=z,mt.update(z?{label:`WORKFLOW`,color:`accent`}:void 0))},qt=(e,t)=>{if(!t){B&&clearInterval(B),B=void 0;return}B||(B=setInterval(()=>{_t=(_t+1)%pe.length,Gt(e)},250),B.unref?.())},X=async(e,t)=>{let n=K(e);if(at){n.recordEvent(`doom_workflow.monitor_skipped`,{outcome:`already_running`});return}let r=Date.now();at=!0;try{let i=await N(v),a=g(e.sessionManager.getSessionId()),o=At(e,a),s=i.filter(e=>l(e,a)),u=s.filter(e=>e.stage===w&&!e.stale),d=s.filter(e=>e.stage===D);n.recordEvent(`doom_workflow.monitor_counts`,{"workflow.run_count":s.length,"workflow.running_count":u.length,"workflow.failed_count":d.length,mode:z?`on`:`off`});let f=ee(i,a).filter(e=>!e.stale);ft=f.length,R?.update(f.map(e=>({id:e.runId??e.runKey,sessionId:a}))),ht={runs:Lt(s),failed:d.length},Gt(e,!0),Zt(new Set(s.map(e=>Y(e))));let te=new Set(s.filter(e=>e.stage===w&&!e.stale).map(e=>Y(e)));for(let e of[...W.keys(),...U.keys()])!te.has(e)&&!L.has(e)&&Vt(e);let ne=new Map(s.map(e=>[Y(e),e.stage]));if(t&&o)for(let e of s){if(e.stage===w)continue;let t=Y(e),n=st.get(t),r=Date.parse(e.startedAt),i=n===void 0&&(lt===0||!Number.isFinite(r)||r>=lt);!ct.has(t)&&(n===w||i)&&L.set(t,e)}if(t)for(let t of s){if(st.get(Y(t))!==w||t.stage===w)continue;let r=t.stage===T,i=t.failedJob?` at ${t.failedJob}`:``;e.ui.notify(r?`${O.completed} ${t.runKey} completed`:`${O.failed} ${t.runKey} ${A(t)}${i}`,r?`info`:`warning`),n.recordEvent(`doom_workflow.run_transition`,{outcome:r?`completed`:A(t)})}if(o&&L.size>0)try{let e=[...L.values()],t=await Promise.all(e.map(async e=>{if(e.stage===T)return c(e,[]);let t=Y(e),n=W.get(t)??k(e,await Me(v.runDirectoryFor(e)));return c(e,y(n))}));m.sendMessage({customType:`workflow-run-finished`,content:t.join(`
1
+ import{LEADER_DISABLE_ACTION as e,LEADER_ENABLE_ACTION as t,LEADER_LAUNCH_ACTION as n,LEADER_MANAGE_ACTION as r,LEADER_RECOVER_ACTION as i,PACKAGE_SOURCE as a}from"../types.mjs";import{registerRunProvider as o}from"./backgroundWork.mjs";import{finishedRunSummary as s,isSessionRun as c,launchNotice as l,runsForSession as u,toolResultText as d,withOptions as f}from"./piToolBridge.mjs";import{WORKFLOW_PI_TOOL_NAMES as p,createWorkflowLaunchExecutor as ee,registerWorkflowPiTools as te}from"./piTools.mjs";import{WORKFLOW_PULSE_FRAMES as ne,workflowFooterStatus as re}from"./workflowFooterStatus.mjs";import{stepReportLines as ie}from"./workflowStatusRow.mjs";import{WorkflowInspectorComponent as ae}from"./workflowInspector.mjs";import{openWorkflowLaunchWizard as oe}from"./workflowLauncher.mjs";import{TerminalInputBatcher as se}from"./workflowOverlay.mjs";import{openWorkflowPickerOverlay as ce}from"./workflowPicker.mjs";import{openWorkflowChoice as le,openWorkflowInput as ue}from"./workflowPrompt.mjs";import{WorkflowRunPanelComponent as de,runPanelUiOptions as fe}from"./workflowRunPanel.mjs";import{formatWorkflowStepMessage as pe,isWorkflowStepMessageDetails as me,renderWorkflowStepMessage as he,workflowStepMessageDetails as ge}from"./workflowStepMessage.mjs";import{createRequire as m}from"node:module";import{registerDoomLeaderActionHandlers as _e}from"@agimon-ai/doompi-extension-contracts/leader";import{spawn as h}from"node:child_process";import{existsSync as ve}from"node:fs";import{readFile as ye}from"node:fs/promises";import{basename as be,dirname as xe,extname as g,resolve as _}from"node:path";import{fileURLToPath as Se}from"node:url";import{SUBAGENT_ROOT_SESSION_ENV as Ce,resolveRootSessionId as v}from"@agimon-ai/doompi-extension-contracts/child-process";import{registerDoomFooterContribution as we}from"@agimon-ai/doompi-extension-contracts/footer";import{registerDoomModeContribution as Te}from"@agimon-ai/doompi-extension-contracts/mode";import{createProtocolRuntime as Ee}from"@agimon-ai/doompi-extension-contracts/protocol";import{registerDoomSkillSource as De}from"@agimon-ai/doompi-extension-contracts/skills";import{WorkflowModeChanged as Oe}from"@agimon-ai/doompi-extension-contracts/workflow";import{createDoomTelemetry as ke}from"@agimon-ai/doompi-telemetry";import{createEmbeddedWorkflowFeature as Ae,readWorkflowProgress as je,summarizeWorkflowProgress as Me}from"@agimon-ai/workflow-mcp";import{truncateToWidth as y,visibleWidth as b}from"@earendil-works/pi-tui";const Ne={overlay:!0,overlayOptions:{anchor:`top-left`,width:`100%`,maxHeight:`100%`,margin:0}},Pe=`agiflow-workflow`,Fe=`workflow-step`,Ie=`workflow-mcp-follow`,Le=`SPC › w / workflows › w / launch`,Re=`paused`,x=`ctrl+alt+w`,S=`ctrl+alt+q`,ze={alt:`⌥`,cmd:`⌘`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`};function C(e,t=process.platform){let n=e.split(`+`);return t===`darwin`?n.map(e=>ze[e]??e.toUpperCase()).join(``):n.map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(`+`)}const w=1e4,T=`running`,E=`completed`,D=`failed`,O=`error`,Be=`error`,Ve=`doom_workflow.launch_requested`,He=`../../skills`,k={running:`▸`,repair:`⚠`,completed:`✔`,failed:`✖`,separator:`·`};function Ue(e,t=process.platform){let n=C(S,t);return e?[`Esc Esc closes this view`,`${C(x,t)} switches typing`,`${n} closes`].join(` ${k.separator} `):`view only ${k.separator} ${n} closes this view`}function We(e,t=process.platform){let n=[C(S,t),`close`];return e?[[`Esc Esc`,`close`],[C(x,t),`switch typing`],n]:[n]}function Ge(e,t,n,r){let i=`${k.running} ${e.runKey}`,a=[e.workspace,Je(e),e.runner].filter(Boolean).join(` ${k.separator} `),o=`${C(x)} to background`,s=n.fg(`accent`,y(i,r));return b(i)+3+b(a)<=r&&(s+=` `+n.fg(`muted`,a),b(i)+3+b(a)+3+b(o)<=r&&(s+=` `+n.fg(`dim`,o))),[s,...t.map(e=>y(e,r))]}function Ke(e,t,n){let r=[];for(let i of e){let e=i.status===E?k.completed:i.status===D?k.failed:k.running,a=i.status===E?`success`:i.status===D?Be:`accent`,o=i.total?` ${k.separator} ${(i.index??0)+1}/${i.total}`:``;if(r.push(y(t.fg(a,`${e} ${i.name}`)+t.fg(`dim`,o),n)),i.status===T)for(let e of i.steps){let i=e.status===E?k.completed:e.status===D?k.failed:k.running,a=e.status===T?`muted`:e.status===D?Be:`dim`;r.push(y(t.fg(a,` ${i} ${e.name}`),n))}}return r}function qe(e,t){let n=Date.parse(e.startedAt);return Number.isFinite(n)?t.filter(e=>{let t=Date.parse(e.at);return!Number.isFinite(t)||t>=n}):t}function Je(e){return e.stale?`stale`:e.stage===O?D:e.stage}const Ye=[`CMUX_SURFACE_ID`,Ce,`CMUX_WORKSPACE_ID`,`WORKFLOW_LAUNCHER_TYPE`,`WORKFLOW_TMUX_SESSION_NAME`,`WORKFLOW_STATUS_FILE`],A=`tmux`,j=`cmux`,Xe=`native`,Ze=`No durable launcher identity is recorded for this run.`;function Qe(e){return be(e,g(e))}function $e(){let e={...process.env};for(let t of Ye)delete e[t];return e}function et(){let e=process.env.WORKFLOW_LAUNCHER;return e===A||e===j?e:void 0}function tt(e){return e.map(e=>`'${e.replaceAll(`'`,`'\\''`)}'`).join(` `)}function nt(){try{return m(import.meta.url).resolve(`@agimon-ai/workflow-mcp/cli`)}catch{return}}const rt=async(e,t,n)=>{await new Promise((r,i)=>{let a=h(e,[...t],{...n,detached:!0,stdio:`ignore`});a.once(`error`,i),a.once(`spawn`,()=>{a.off(`error`,i),a.unref(),r()})})};function it(e,t){if(Array.isArray(e)){for(let n of e)it(n,t);return}if(!(!e||typeof e!=`object`))for(let[n,r]of Object.entries(e))(n===`interactiveRun`||n===`run`)&&r&&typeof r==`object`&&!Array.isArray(r)?t.push(Object.keys(r)):it(r,t)}function at(e){let t=[];if(it(e,t),t.length!==0)return t[0].filter(e=>t.every(t=>t.includes(e))).sort()}async function M(e,t){let n=[],r=1,i=!0;for(;i;){let a=await e.listRunsPage({page:r,pageSize:100,workspace:t});n.push(...a.items),i=a.hasNextPage,r+=1}return n}function ot(e){return[e.runKey,e.workspace,Je(e),e.runner].filter(Boolean).join(` ${k.separator} `)}function N(m={}){return h=>{let g=m.featureFactory?.()??Ae(),{parser:Ce,recoverService:y,registry:b,runService:ze,runTool:D}=g,Be=m.spawnDetached??rt,Ye=m.monitorIntervalMs??3e4,it=m.followIntervalMs??2e3,N,st=!1,P,ct=!1,F,lt=new Map,I=new Map,ut=new Set,dt=0,L,ft,pt,mt=0,R=!1,ht=!1,gt=Te(h,{source:a,id:`workflow`,order:20}),_t={runs:[],failed:0},vt,yt=0,z,bt=we(h,{source:a,id:`workflow-runs`,order:50}),xt,B,V,St=!1,Ct=new Map,H,wt=[],U=new Map,Tt=new Map,W=new Map,Et=[],Dt=new Map,Ot,G,K=e=>(G??=ke({serviceName:`doom-workflow`,packageName:`@agimon-ai/doompi-workflow`,cwd:e?.cwd,env:process.env,enableLogs:!0,enableTraces:!0}),G),kt=e=>{e&&(Ot=v(e))},At=()=>{let e=Ot??xt?.sessionManager.getSessionId();return e?v(e):void 0},jt=(e,t)=>e.sessionManager.getSessionId().trim()===t,Mt=g.createRecoverTool({ownerSessionId:At}),Nt=g.createControlTool(),q=new Set,Pt=e=>{let t=Date.now();return q.add(e),K().recordEvent(`doom_workflow.run_started`,{outcome:`started`}),e.then(()=>(q.delete(e),K().recordEvent(`doom_workflow.run_finished`,{duration_ms:Date.now()-t,outcome:`completed`})),n=>(q.delete(e),K().recordError(`doom_workflow.run_failed`,n,{duration_ms:Date.now()-t}))),e},Ft=(e,t)=>{let n=at(Ce.parseWorkflowFile(e));if(!(!n||n.includes(t)))return`Runner ${JSON.stringify(t)} is not available across this workflow. Compatible runners: ${n.join(`, `)||`none`}.`},It=ee({activeRunCount:async()=>mt,observeSession:kt,rejectRunner:Ft,runTool:D,trackPendingRun:Pt,...m.launchAckPollMs===void 0?{}:{launchAckPollMs:m.launchAckPollMs},...m.launchAckTimeoutMs===void 0?{}:{launchAckTimeoutMs:m.launchAckTimeoutMs},findLaunchedRun:async({sessionId:e,since:t,workflowPath:n})=>{let r=Qe(n),i=n=>{if(!c(n,e)||n.stale||n.workflowId&&n.workflowId!==r)return!1;let i=Date.parse(n.startedAt);return Number.isFinite(i)&&i>=t-2e3};return H?(await H.refresh(),Et.find(i)):(await M(b)).find(i)},onLateFailure:(e,t)=>{let n=e instanceof Error?e.message:String(e);t.ui.notify(l(`Workflow launch failed after it was reported started.\n${n}`),`warning`)},onLaunch:async e=>(await K(e).recordEvent(Ve,{outcome:`requested`}),X(e,!1))}),Lt=(e,t)=>{if(!e.isIdle())return;let n=Y(t),r=Dt.get(n);r||(r=new Set,Dt.set(n,r));for(let e of ie(t.runKey,W.get(n)??[])){if(r.has(e.key))continue;r.add(e.key);let n=ge(t.displayName,e);h.sendMessage({customType:Fe,content:pe(n),details:n,display:!0},{triggerTurn:!1})}},Rt=e=>e.filter(e=>e.stage===T&&!e.stale).map(e=>{let t=Y(e),n=U.get(t),r=n?[...n.values()].includes(T):!1,i=e.executionState===Re?`paused`:r?`running`:`starting`,a=Tt.get(t);return{key:t,state:i,...a===void 0?{}:{progress:a}}}),zt=(e,t,n,r)=>{let i=U.get(e)??new Map;(r||!i.has(t))&&i.set(t,n),U.set(e,i)},Bt=(e,t)=>{t.phase===`job`&&t.status===T&&(t.index===void 0||!t.total||Tt.set(e,Math.min(1,Math.max(0,t.index)/t.total)))},J,Vt=e=>{J||(J=setTimeout(()=>{J=void 0,X(e,!0)},120),J.unref?.())},Ht=e=>{W.delete(e),U.delete(e),Tt.delete(e),Dt.delete(e)},Ut=async e=>{let t=v(e.sessionManager.getSessionId()),n=g.createRunControl({recordFilter:e=>c(e,t)});H=n,wt.push(n.on(`step`,t=>{let n=Y(t.record),r=W.get(n)??[];r.push({at:t.at,job:t.job,status:t.status,step:t.step,type:`step`}),W.set(n,r),zt(n,t.job,T,!1),Lt(e,t.record),Vt(e)}),n.on(`job`,t=>{let n=Y(t.record);zt(n,t.job,t.status,!0),Bt(n,t),Vt(e)}),n.on(`runStarted`,t=>{Et.unshift(t.record),Et.length>32&&(Et.length=32),Vt(e)}),n.on(`runUpdated`,()=>void X(e,!0)),n.on(`runFinished`,()=>void X(e,!0))),await n.start()},Wt=()=>{J&&clearTimeout(J),J=void 0;for(let e of wt.splice(0))e();H?.dispose(),H=void 0,W.clear(),U.clear(),Tt.clear(),Dt.clear()},Gt=()=>{B?.teardown(),B?.handle?.hide(),B=void 0},Y=e=>e.runId??`${e.workspace}/${e.runKey}`,Kt=(e,t=!1)=>{let n=re(_t,yt);!t&&n?.fingerprint===vt||(vt=n?.fingerprint,bt.update(n?.footer),e.ui.setStatus(Pe,n?.text),Jt(e,n?.pulsing??!1))},qt=()=>{R!==ht&&(ht=R,gt.update(R?{label:`WORKFLOW`,color:`accent`}:void 0))},Jt=(e,t)=>{if(!t){z&&clearInterval(z),z=void 0;return}z||(z=setInterval(()=>{yt=(yt+1)%ne.length,Kt(e)},250),z.unref?.())},X=async(e,t)=>{let n=K(e);if(st){n.recordEvent(`doom_workflow.monitor_skipped`,{outcome:`already_running`});return}let r=Date.now();st=!0;try{let i=await M(b),a=v(e.sessionManager.getSessionId()),o=jt(e,a),l=i.filter(e=>c(e,a)),d=l.filter(e=>e.stage===T&&!e.stale),f=l.filter(e=>e.stage===O);n.recordEvent(`doom_workflow.monitor_counts`,{"workflow.run_count":l.length,"workflow.running_count":d.length,"workflow.failed_count":f.length,mode:R?`on`:`off`});let p=u(i,a).filter(e=>!e.stale);mt=p.length,L?.update(p.map(e=>({id:e.runId??e.runKey,sessionId:a}))),_t={runs:Rt(l),failed:f.length},Kt(e,!0),Qt(new Set(l.map(e=>Y(e))));let ee=new Set(l.filter(e=>e.stage===T&&!e.stale).map(e=>Y(e)));for(let e of[...W.keys(),...U.keys()])!ee.has(e)&&!I.has(e)&&Ht(e);let te=new Map(l.map(e=>[Y(e),e.stage]));if(t&&o)for(let e of l){if(e.stage===T)continue;let t=Y(e),n=lt.get(t),r=Date.parse(e.startedAt),i=n===void 0&&(dt===0||!Number.isFinite(r)||r>=dt);!ut.has(t)&&(n===T||i)&&I.set(t,e)}if(t)for(let t of l){if(lt.get(Y(t))!==T||t.stage===T)continue;let r=t.stage===E,i=t.failedJob?` at ${t.failedJob}`:``;e.ui.notify(r?`${k.completed} ${t.runKey} completed`:`${k.failed} ${t.runKey} ${Je(t)}${i}`,r?`info`:`warning`),n.recordEvent(`doom_workflow.run_transition`,{outcome:r?`completed`:Je(t)})}if(o&&I.size>0)try{let e=[...I.values()],t=await Promise.all(e.map(async e=>{if(e.stage===E)return s(e,[]);let t=Y(e),n=W.get(t)??qe(e,await je(b.runDirectoryFor(e)));return s(e,Me(n))}));h.sendMessage({customType:`workflow-run-finished`,content:t.join(`
2
2
 
3
- `),display:!0,details:{runIds:e.map(e=>e.runId??Y(e))}},{triggerTurn:!0,deliverAs:`steer`});for(let t of e){let e=Y(t);ct.add(e),L.delete(e),Vt(e)}}catch(e){await n.recordError(`doom_workflow.terminal_delivery_failed`,e,{"workflow.pending_terminal_count":L.size})}st=ne,n.recordEvent(`doom_workflow.monitor_finished`,{duration_ms:Date.now()-r,outcome:`completed`})}catch(t){await n.recordError(`doom_workflow.monitor_failed`,t,{duration_ms:Date.now()-r}),e.ui.setStatus(Pe,void 0),e.ui.notify(`Workflow status refresh failed: ${t instanceof Error?t.message:String(t)}`,`warning`)}finally{at=!1}},Jt=e=>{F&&clearInterval(F),F=void 0,I=void 0,e.ui.setWidget(Ie,void 0)},Yt=async e=>{let t=e.launcher;if(!t)return[Ye];if(t.type===j){let n=t.paneId??t.sessionId??t.sessionName,r=await m.exec(j,[`capture-pane`,`-p`,`-t`,n,`-S`,`-24`],{timeout:C});return r.code===0?r.stdout.trimEnd().split(`
4
- `).slice(-24):[`Unable to read ${e.runKey}: ${r.stderr.trim()||`tmux exited ${r.code}`}`]}if(t.type===Je)return[`This run is hosted natively and has no launcher screen to read.`];let n=await m.exec(M,[`read-screen`,`--workspace`,t.workspaceId,`--scrollback`,`--lines`,`24`],{timeout:C});return n.code===0?n.stdout.trimEnd().split(`
5
- `).slice(-24):[`Unable to read ${e.runKey}: ${n.stderr.trim()||`cmux exited ${n.code}`}`]},Z=new Map,Xt=async e=>{let t=Y(e),n=Z.get(t)??{at:0,lines:[]};if(Z.set(t,n),n.inFlight||Date.now()-n.at<750)return n.lines;n.inFlight=Yt(e);try{n.lines=await n.inFlight}finally{n.at=Date.now(),n.inFlight=void 0}return n.lines},Zt=e=>{for(let t of Z.keys())e.has(t)||Z.delete(t)},Qt=async e=>{let t=e.launcher;if(!t)return Ye;if(t.type===Je)return`This run is hosted natively rather than in a multiplexer, so there is no separate window to bring forward.`;if(t.type===M)return await m.exec(M,[`select-workspace`,`--workspace`,t.workspaceId],{timeout:C}),`Opened ${e.runKey} in cmux.`;if(!process.env.TMUX)return`Pi is not attached to tmux. Follow the output instead, or attach to the recorded tmux session manually.`;let n=t.sessionId??t.sessionName,r=await m.exec(j,[`switch-client`,`-t`,n],{timeout:C});return r.code===0?(t.paneId&&await m.exec(j,[`select-pane`,`-t`,t.paneId],{timeout:C}),`Opened ${e.runKey} in tmux.`):r.stderr.trim()||`Unable to open tmux session ${n}.`},$t=async(e,t)=>{if(!ot){ot=!0;try{let n=y(k(e,await Me(v.runDirectoryFor(e)))),r=n.length>0?void 0:await Xt(e);t.ui.setWidget(Ie,(t,i)=>({render:t=>Ge(e,r??Ke(n,i,t),i,t),invalidate:()=>{}}),{placement:`aboveEditor`})}finally{ot=!1}}},en=async(e,t)=>{Jt(t),I=e,await $t(e,t),F=setInterval(()=>{I&&$t(I,t)},nt),F.unref?.()},tn=async(e,t)=>{let n=e.launcher;if(!(!n||n.type===Je)){if(n.type===j){let e=n.paneId??n.sessionId??n.sessionName;await m.exec(j,[`send-keys`,`-t`,e,`-l`,t],{timeout:C});return}await m.exec(M,[`send`,`--workspace`,n.workspaceId,t],{timeout:C})}},nn=async(e,t)=>{Wt();let n=e.launcher?.type===j||e.launcher?.type===M,r=new ce(t=>tn(e,t)),i=[],a=[],o,s=!1,c=async()=>{i=y(k(e,await Me(v.runDirectoryFor(e)))),a=await Xt(e)};await c();let l=()=>{o&&clearInterval(o),o=void 0,r.dispose()},u={interactive:n,runKey:e.runKey,teardown:l};V=u;let ee=()=>{Wt(),t.ui.notify(`Closed the ${e.runKey} view. The run keeps going.`,`info`)};return t.ui.custom((d,f,te,ne)=>(o=setInterval(()=>{s||(s=!0,c().finally(()=>{s=!1,d.requestRender()}))},200),o.unref?.(),new le(d,f,{runKey:e.runKey,label:`${O.running} ${it(e)}`,breadcrumb:`SPC › w / workflows › run`,interactive:n,footer:Ue(n),hints:We(n),snapshot:e=>({progress:Ke(i,f,e),output:a}),onUnfollow:()=>{u.handle?.unfocus(),t.ui.notify(`Typing goes to this session. The run keeps going.`,`info`)},onClose:ee,sendInput:e=>r.write(e),flushInput:()=>void r.flush(),unfollowShortcut:b,closeShortcut:x,onDispose:()=>{l(),V===u&&(V=void 0),ne()}})),{...ue(n),onHandle:e=>{u.handle=e}}),t.ui.notify(n?`${e.runKey}: typing goes to the run. Press Esc twice to close, ${S(b)} to type here instead.`:`${e.runKey}: view only, because this run is hosted natively. ${S(x)} closes it.`,`info`),n?[`Showing ${e.runKey} over Pi. Typing goes to the run.`,`Pressing Escape twice quickly closes the view, and a single Escape still reaches the run.`,`${S(b)} moves typing between the run and this session,`,`and ${S(x)} closes the view. The run keeps going either way.`].join(` `):[`Showing ${e.runKey} over Pi, as a view only:`,`the run is hosted natively rather than in a multiplexer, so there is no terminal to type into.`,`Typing stays with this session, and ${S(x)} closes the view.`].join(` `)},rn=async(e,t=!1)=>{if(!(xt||V||I)){xt=!0;try{let n=await e.ui.custom((e,t,n,r)=>new fe(e,t,{list:async()=>{let e=kt(),t=(await N(v)).filter(t=>t.stage===w&&!t.stale&&l(t,e));return St=new Map(t.map(e=>[Y(e),e])),Promise.all(t.map(async e=>({displayName:e.displayName,executionState:e.executionState,jobs:y(k(e,await Me(v.runDirectoryFor(e)))),key:Y(e),output:[],runKey:e.runKey,startedAt:e.startedAt,workspace:e.workspace})))},output:async e=>{let t=St.get(e.key);return t?Xt(t):[`Workflow is no longer active.`]}},r),{...Ne,onHandle:e=>{bt=e}});if(n){let r=await Q(n);t?await on(r,e):await nn(r,e)}}finally{bt=void 0,xt=!1,St.clear()}}},Q=async(e,t=kt())=>{Ot(t);let n=(await N(v,e.workspace)).find(t=>t.runKey===e.runKey);if(!n||!l(n,t))throw Error(f(`No workflow run matches ${JSON.stringify(e.runKey)} in this session.`,[`workflow_run with action status: use a run key from this session before retrying.`,`Tell the user this session did not launch that run rather than guessing a similar key. A run launched by another Pi session, or from the command line, is managed there or with the workflow-mcp CLI.`]));return n},an=async(e,t)=>{let n=et();if(!n)return;let r=await Q(e,g(t.sessionManager.getSessionId())),i;try{i=Se.parseWorkflowFile(r.workflowPath)}catch{return}if(!i[`launch-command`])return;let a=r.originalRepoPath??t.cwd,o=$e([process.execPath,n,`recover-workflow`,r.runKey,`--workspace`,r.workspace,...e.runner?[`--runner`,e.runner]:[]]),s=Qe();return Be(process.execPath,[n,`launch-process`,...s?[`--launcher`,s]:[],`--name`,`${r.displayName} recover`,`--cwd`,a,`--command`,o],{cwd:a,env:Ze()}),await K(t).recordEvent(`doom_workflow.recover_delegated`,{launcher:s??`auto`,outcome:`delegated`}),`Recovering ${r.runKey} in a launcher of its own, so this session keeps its terminal. It reports back here when it ends.`},on=async(e,t)=>{let n=e;for(;;){let e=[`Open output`],r=n.executionState??w;r===w&&e.push(`Pause`),r===Re&&e.push(`Resume`),n.stage===w&&e.push(`Stop`),e.push(`Back`);let i=await oe(t,{title:`Manage ${n.runKey}`,breadcrumb:`SPC › w / workflows › l / manage`,choices:e});if(!i||i===`Back`)return;if(n=await Q(n),n.stage!==w){t.ui.notify(`${n.runKey} is no longer running.`,`warning`);return}if(i===`Open output`){await nn(n,t);return}if(i===`Stop`){if(!await t.ui.confirm(`Stop ${n.runKey}?`,`The workflow will be interrupted and will not be restarted automatically.`))continue;if(n=await Q(n),n.stage!==w){t.ui.notify(`${n.runKey} is no longer running.`,`warning`);return}}if(i!==`Pause`&&i!==`Resume`&&i!==`Stop`)continue;let a=await Mt.execute({action:i.toLowerCase(),...i===`Stop`?{reason:`Stopped from the workflow manager.`}:{},runKey:n.runKey,workspace:n.workspace});if(a.isError)throw Error(d(a)||`Unable to ${i.toLowerCase()} workflow.`);t.ui.notify(d(a)||`${i} requested for ${n.runKey}.`,`info`),await X(t,!1),n=await Q(n)}},sn=(e,t)=>({editor:(t,n)=>e.ui.editor(t,n),input:(n,r)=>se(e,{title:n,breadcrumb:t,...r?{fallback:r}:{}}),notify:(t,n)=>e.ui.notify(t,n),select:(n,r)=>oe(e,{title:n,breadcrumb:t,choices:r})}),cn=async e=>{if(!e.hasUI){e.ui.notify(`Workflow launch needs an interactive Pi session.`,`warning`);return}let t=await ie({compatibleRunners:rt,cwd:e.cwd,launch:(e,t)=>Ft.execute(e,t),listWorkflowsTool:h.listWorkflowsTool,parseWorkflow:e=>Se.parseWorkflowFile(e),pickWorkflow:t=>ae(e,{title:`WORKFLOW LAUNCHER`,breadcrumb:Le,unit:`workflows`,action:`launch`,filterPlaceholder:`type to filter by name, path or tag`,emptyMessage:`No workflow definitions were found in this repository.`},t.map(e=>({key:e.path,name:e.name,detail:e.relativePath,search:[e.description,...e.tags].join(` `),value:e}))),ui:sn(e,Le)},e);t&&e.ui.notify(u(d(t)),`info`)},ln=async e=>{let t=kt(),n=(await N(v)).filter(e=>e.stage===D&&l(e,t));if(n.length===0){e.ui.notify(`No failed workflows belong to this session.`,`info`);return}let r=await ae(e,{title:`WORKFLOW RECOVERY`,breadcrumb:`SPC › w / workflows › r / recover`,unit:`failed runs`,action:`recover`,filterPlaceholder:`type to filter by name, run key or failed job`,emptyMessage:`No failed workflows belong to this session.`},n.map(e=>({key:`${e.workspace}/${e.runKey}`,name:e.displayName,detail:`${e.workspace}/${e.runKey}${e.failedJob?` · ${e.failedJob}`:``}`,...e.failedJob?{search:e.failedJob}:{},value:e})));if(!r)return;let i=await Q(r);if(i.stage!==D)throw Error(`${i.runKey} is no longer failed.`);let a=Ce(xe(we(import.meta.url)),He,`workflow-recovery`,`SKILL.md`);if(!be(a))throw Error(`Workflow recovery skill is unavailable at ${a}.`);un(!0),e.ui.notify(`Workflow tools enabled. The agent will investigate this failure and choose the safe next step.`,`info`),m.sendUserMessage([`Investigate recovery for this failed workflow run. This selection is permission to investigate, not permission to recover blindly.`,`Run identity: ${JSON.stringify({runKey:i.runKey,workspace:i.workspace})}`,`Read the package-owned recovery skill with the read tool at ${JSON.stringify(a)} before acting.`,`Inspect status, every relevant log source, process state, the staged run.json, and only the repair referenced by activeRepairId.`,`Choose exactly one outcome: recover, launch fresh, or defer. Never edit issue.md or repair.json, never scan historical repairs, and never pass recovery dryRun or a job override.`,`Do not recover terminal, approval-pending, blocked, or otherwise unsafe work. If evidence is incomplete, defer and explain what is missing.`,`If recovery is justified, preserve the recorded worktree and repair metadata, then verify real process and registry progress before reporting success.`].join(`
6
- `),{deliverAs:`followUp`})},un=e=>{let t=new Set(m.getActiveTools());for(let n of te)e?t.add(n):t.delete(n);m.setActiveTools([...t]),z=e,Kt(),p.onModeChange?.(e)},dn=()=>z?[`${O.completed} Workflow mode on`,`Ask for workflows in plain language: the agent can list, launch, inspect, pause, resume, stop, and recover runs.`,`${S(b)} stops following a run without stopping it.`].join(`
3
+ `),display:!0,details:{runIds:e.map(e=>e.runId??Y(e))}},{triggerTurn:!0,deliverAs:`steer`});for(let t of e){let e=Y(t);ut.add(e),I.delete(e),Ht(e)}}catch(e){await n.recordError(`doom_workflow.terminal_delivery_failed`,e,{"workflow.pending_terminal_count":I.size})}lt=te,n.recordEvent(`doom_workflow.monitor_finished`,{duration_ms:Date.now()-r,outcome:`completed`})}catch(t){await n.recordError(`doom_workflow.monitor_failed`,t,{duration_ms:Date.now()-r}),e.ui.setStatus(Pe,void 0),e.ui.notify(`Workflow status refresh failed: ${t instanceof Error?t.message:String(t)}`,`warning`)}finally{st=!1}},Yt=e=>{P&&clearInterval(P),P=void 0,F=void 0,e.ui.setWidget(Ie,void 0)},Xt=async e=>{let t=e.launcher;if(!t)return[Ze];if(t.type===A){let n=t.paneId??t.sessionId??t.sessionName,r=await h.exec(A,[`capture-pane`,`-p`,`-t`,n,`-S`,`-24`],{timeout:w});return r.code===0?r.stdout.trimEnd().split(`
4
+ `).slice(-24):[`Unable to read ${e.runKey}: ${r.stderr.trim()||`tmux exited ${r.code}`}`]}if(t.type===Xe)return[`This run is hosted natively and has no launcher screen to read.`];let n=await h.exec(j,[`read-screen`,`--workspace`,t.workspaceId,`--scrollback`,`--lines`,`24`],{timeout:w});return n.code===0?n.stdout.trimEnd().split(`
5
+ `).slice(-24):[`Unable to read ${e.runKey}: ${n.stderr.trim()||`cmux exited ${n.code}`}`]},Z=new Map,Zt=async e=>{let t=Y(e),n=Z.get(t)??{at:0,lines:[]};if(Z.set(t,n),n.inFlight||Date.now()-n.at<750)return n.lines;n.inFlight=Xt(e);try{n.lines=await n.inFlight}finally{n.at=Date.now(),n.inFlight=void 0}return n.lines},Qt=e=>{for(let t of Z.keys())e.has(t)||Z.delete(t)},$t=async e=>{let t=e.launcher;if(!t)return Ze;if(t.type===Xe)return`This run is hosted natively rather than in a multiplexer, so there is no separate window to bring forward.`;if(t.type===j)return await h.exec(j,[`select-workspace`,`--workspace`,t.workspaceId],{timeout:w}),`Opened ${e.runKey} in cmux.`;if(!process.env.TMUX)return`Pi is not attached to tmux. Follow the output instead, or attach to the recorded tmux session manually.`;let n=t.sessionId??t.sessionName,r=await h.exec(A,[`switch-client`,`-t`,n],{timeout:w});return r.code===0?(t.paneId&&await h.exec(A,[`select-pane`,`-t`,t.paneId],{timeout:w}),`Opened ${e.runKey} in tmux.`):r.stderr.trim()||`Unable to open tmux session ${n}.`},en=async(e,t)=>{if(!ct){ct=!0;try{let n=Me(qe(e,await je(b.runDirectoryFor(e)))),r=n.length>0?void 0:await Zt(e);t.ui.setWidget(Ie,(t,i)=>({render:t=>Ge(e,r??Ke(n,i,t),i,t),invalidate:()=>{}}),{placement:`aboveEditor`})}finally{ct=!1}}},tn=async(e,t)=>{Yt(t),F=e,await en(e,t),P=setInterval(()=>{F&&en(F,t)},it),P.unref?.()},nn=async(e,t)=>{let n=e.launcher;if(!(!n||n.type===Xe)){if(n.type===A){let e=n.paneId??n.sessionId??n.sessionName;await h.exec(A,[`send-keys`,`-t`,e,`-l`,t],{timeout:w});return}await h.exec(j,[`send`,`--workspace`,n.workspaceId,t],{timeout:w})}},rn=async(e,t)=>{Gt();let n=e.launcher?.type===A||e.launcher?.type===j,r=new se(t=>nn(e,t)),i=[],a=[],o,s=!1,c=async()=>{i=Me(qe(e,await je(b.runDirectoryFor(e)))),a=await Zt(e)};await c();let l=()=>{o&&clearInterval(o),o=void 0,r.dispose()},u={interactive:n,runKey:e.runKey,teardown:l};B=u;let d=()=>{Gt(),t.ui.notify(`Closed the ${e.runKey} view. The run keeps going.`,`info`)};return t.ui.custom((f,p,ee,te)=>(o=setInterval(()=>{s||(s=!0,c().finally(()=>{s=!1,f.requestRender()}))},200),o.unref?.(),new de(f,p,{runKey:e.runKey,label:`${k.running} ${ot(e)}`,breadcrumb:`SPC › w / workflows › run`,interactive:n,footer:Ue(n),hints:We(n),snapshot:e=>({progress:Ke(i,p,e),output:a}),onUnfollow:()=>{u.handle?.unfocus(),t.ui.notify(`Typing goes to this session. The run keeps going.`,`info`)},onClose:d,sendInput:e=>r.write(e),flushInput:()=>void r.flush(),unfollowShortcut:x,closeShortcut:S,onDispose:()=>{l(),B===u&&(B=void 0),te()}})),{...fe(n),onHandle:e=>{u.handle=e}}),t.ui.notify(n?`${e.runKey}: typing goes to the run. Press Esc twice to close, ${C(x)} to type here instead.`:`${e.runKey}: view only, because this run is hosted natively. ${C(S)} closes it.`,`info`),n?[`Showing ${e.runKey} over Pi. Typing goes to the run.`,`Pressing Escape twice quickly closes the view, and a single Escape still reaches the run.`,`${C(x)} moves typing between the run and this session,`,`and ${C(S)} closes the view. The run keeps going either way.`].join(` `):[`Showing ${e.runKey} over Pi, as a view only:`,`the run is hosted natively rather than in a multiplexer, so there is no terminal to type into.`,`Typing stays with this session, and ${C(S)} closes the view.`].join(` `)},an=async(e,t=!1)=>{if(!(St||B||F)){St=!0;try{let n=await e.ui.custom((e,t,n,r)=>new ae(e,t,{list:async()=>{let e=At(),t=(await M(b)).filter(t=>t.stage===T&&!t.stale&&c(t,e));return Ct=new Map(t.map(e=>[Y(e),e])),Promise.all(t.map(async e=>({displayName:e.displayName,executionState:e.executionState,jobs:Me(qe(e,await je(b.runDirectoryFor(e)))),key:Y(e),output:[],runKey:e.runKey,startedAt:e.startedAt,workspace:e.workspace})))},output:async e=>{let t=Ct.get(e.key);return t?Zt(t):[`Workflow is no longer active.`]}},r),{...Ne,onHandle:e=>{V=e}});if(n){let r=await Q(n);t?await un(r,e):await rn(r,e)}}finally{V=void 0,St=!1,Ct.clear()}}},Q=async(e,t=At())=>{kt(t);let n=(await M(b,e.workspace)).filter(n=>n.runKey===e.runKey&&c(n,t));if(n.length>1)throw Error(f(`Workflow run key ${JSON.stringify(e.runKey)} exists in more than one workspace in this session.`,[`Retry with the workspace from the run identity.`]));let r=n[0];if(!r)throw Error(f(`No workflow run matches ${JSON.stringify(e.runKey)} in this session.`,[`workflow_run with action status: use a run key from this session before retrying.`,`Tell the user this session did not launch that run rather than guessing a similar key. A run launched by another Pi session, or from the command line, is managed there or with the workflow-mcp CLI.`]));return r},on=async e=>{let t=(await M(b,e.workspace)).filter(t=>t.runKey===e.runKey&&t.stage===O);if(t.length>1)throw Error(f(`Failed workflow run key ${JSON.stringify(e.runKey)} exists in more than one workspace.`,[`Retry with the workspace from the recovery picker or run identity.`]));let n=t[0];if(!n)throw Error(f(`No failed workflow run matches ${JSON.stringify(e.runKey)}.`,[`Use the recovery picker to select an interrupted or failed run, or retry with its workspace.`,`Do not recover a running or completed workflow.`]));return n},sn=async e=>{let t=b.runDirectoryFor(e),n=[_(t,`changelog.md`),_(t,`context.md`),_(t,`progress.ndjson`)],r=[];for(let e of n)try{let t=await ye(e,`utf-8`);r.push(`--- ${be(e)} ---`,t.slice(-65536).trimEnd())}catch(e){if(e.code!==`ENOENT`)throw e}return r.length>0?r:[`No durable recovery evidence files are recorded for this run.`]},cn=async(e,t)=>{let n=await on(e);return await K(t).recordEvent(`doom_workflow.recovery_evidence_requested`,{outcome:`requested`}),[`Workflow: ${ot(n)}`,`--- run.json ---`,JSON.stringify(n,null,2),...await sn(n)].join(`
6
+ `)},ln=async(e,t)=>{let n=nt();if(!n)return;let r=await on(e),i;try{i=Ce.parseWorkflowFile(r.workflowPath)}catch{return}if(!i[`launch-command`])return;let a=v(t.sessionManager.getSessionId()),o=await b.claimRunRecovery(r.workspace,r.runKey,a),s=r.originalRepoPath??t.cwd,c=tt([process.execPath,n,`recover-workflow`,r.runKey,`--workspace`,r.workspace,`--recovery-claim`,o.claimId,...e.runner?[`--runner`,e.runner]:[]]),l=et();try{await Be(process.execPath,[n,`launch-process`,...l?[`--launcher`,l]:[],`--name`,`${r.displayName} recover`,`--cwd`,s,`--command`,c],{cwd:s,env:$e()})}catch(e){throw await b.releaseRunRecoveryClaim(r.workspace,r.runKey,o.claimId),e}return await K(t).recordEvent(`doom_workflow.recover_delegated`,{launcher:l??`auto`,outcome:`delegated`}),`Recovering ${r.runKey} in a launcher of its own, so this session keeps its terminal. It reports back here when it ends.`},un=async(e,t)=>{let n=e;for(;;){let e=[`Open output`],r=n.executionState??T;r===T&&e.push(`Pause`),r===Re&&e.push(`Resume`),n.stage===T&&e.push(`Stop`),e.push(`Back`);let i=await le(t,{title:`Manage ${n.runKey}`,breadcrumb:`SPC › w / workflows › l / manage`,choices:e});if(!i||i===`Back`)return;if(n=await Q(n),n.stage!==T){t.ui.notify(`${n.runKey} is no longer running.`,`warning`);return}if(i===`Open output`){await rn(n,t);return}if(i===`Stop`){if(!await t.ui.confirm(`Stop ${n.runKey}?`,`The workflow will be interrupted and will not be restarted automatically.`))continue;if(n=await Q(n),n.stage!==T){t.ui.notify(`${n.runKey} is no longer running.`,`warning`);return}}if(i!==`Pause`&&i!==`Resume`&&i!==`Stop`)continue;if(!n.runId)throw Error(`${n.runKey} has no controllable run generation.`);let a=await Nt.execute({action:i.toLowerCase(),expectedRunId:n.runId,...i===`Stop`?{reason:`Stopped from the workflow manager.`}:{},runKey:n.runKey,workspace:n.workspace});if(a.isError)throw Error(d(a)||`Unable to ${i.toLowerCase()} workflow.`);t.ui.notify(d(a)||`${i} requested for ${n.runKey}.`,`info`),await X(t,!1),n=await Q(n)}},dn=(e,t)=>({editor:(t,n)=>e.ui.editor(t,n),input:(n,r)=>ue(e,{title:n,breadcrumb:t,...r?{fallback:r}:{}}),notify:(t,n)=>e.ui.notify(t,n),select:(n,r)=>le(e,{title:n,breadcrumb:t,choices:r})}),fn=async e=>{if(!e.hasUI){e.ui.notify(`Workflow launch needs an interactive Pi session.`,`warning`);return}let t=await oe({compatibleRunners:at,cwd:e.cwd,launch:(e,t)=>It.execute(e,t),listWorkflowsTool:g.listWorkflowsTool,parseWorkflow:e=>Ce.parseWorkflowFile(e),pickWorkflow:t=>ce(e,{title:`WORKFLOW LAUNCHER`,breadcrumb:Le,unit:`workflows`,action:`launch`,filterPlaceholder:`type to filter by name, path or tag`,emptyMessage:`No workflow definitions were found in this repository.`},t.map(e=>({key:e.path,name:e.name,detail:e.relativePath,search:[e.description,...e.tags].join(` `),value:e}))),ui:dn(e,Le)},e);t&&e.ui.notify(l(d(t)),`info`)},pn=async e=>{let t=(await M(b)).filter(e=>e.stage===O);if(t.length===0){e.ui.notify(`No failed workflows are available to recover.`,`info`);return}let n=await ce(e,{title:`WORKFLOW RECOVERY`,breadcrumb:`SPC › w / workflows › r / recover`,unit:`failed runs`,action:`recover`,filterPlaceholder:`type to filter by name, run key or failed job`,emptyMessage:`No failed workflows are available to recover.`},t.map(e=>({key:`${e.workspace}/${e.runKey}`,name:e.displayName,detail:`${e.workspace}/${e.runKey}${e.failedJob?` · ${e.failedJob}`:``}`,...e.failedJob?{search:e.failedJob}:{},value:e})));if(!n)return;let r=await on(n);if(r.stage!==O)throw Error(`${r.runKey} is no longer failed.`);let i=_(xe(Se(import.meta.url)),He,`workflow-recovery`,`SKILL.md`);if(!ve(i))throw Error(`Workflow recovery skill is unavailable at ${i}.`);mn(!0),e.ui.notify(`Workflow tools enabled. The agent will investigate this failure and choose the safe next step.`,`info`),h.sendUserMessage([`Investigate recovery for this failed workflow run. This selection is permission to investigate, not permission to recover blindly.`,`Run identity: ${JSON.stringify({runKey:r.runKey,workspace:r.workspace})}`,`Read the package-owned recovery skill with the read tool at ${JSON.stringify(i)} before acting.`,`Inspect status, every relevant log source, process state, the staged run.json, and only the repair referenced by activeRepairId.`,`Choose exactly one outcome: recover, launch fresh, or defer. Never edit issue.md or repair.json, never scan historical repairs, and never pass recovery dryRun or a job override.`,`Do not recover terminal, approval-pending, blocked, or otherwise unsafe work. If evidence is incomplete, defer and explain what is missing.`,`If recovery is justified, preserve the recorded worktree and repair metadata, then verify real process and registry progress before reporting success.`].join(`
7
+ `),{deliverAs:`followUp`})},mn=e=>{let t=new Set(h.getActiveTools());for(let n of p)e?t.add(n):t.delete(n);h.setActiveTools([...t]),R=e,qt(),m.onModeChange?.(e)},hn=()=>R?[`${k.completed} Workflow mode on`,`Ask for workflows in plain language: the agent can list, launch, inspect, pause, resume, stop, and recover runs.`,`${C(x)} stops following a run without stopping it.`].join(`
7
8
  `):[`Workflow mode off`,`Press SPC w e to give the agent its workflow tools. Runs already going are unaffected.`].join(`
8
- `),fn=async(e,t)=>{un(e),z||Jt(t),t.ui.notify(dn(),`info`),await X(t,!1)};m.registerShortcut(b,{description:`Open workflows or move typing between a workflow run and this session`,handler:async e=>{let t=V?.handle;if(t){if(!V?.interactive){e.ui.notify(`This run is hosted natively, so the view takes no typing. Your keys stay here.`,`info`);return}t.isFocused()?(t.unfocus(),e.ui.notify(`Typing goes to this session. The run keeps going.`,`info`)):(t.focus(),e.ui.notify(`Typing goes to ${V?.runKey}.`,`info`));return}if(I){Jt(e),e.ui.notify(`Workflow continues in the background.`,`info`);return}await rn(e)}}),m.registerShortcut(x,{description:`Close the workflow run view`,handler:e=>{if(!V)return;let t=V.runKey;Wt(),e.ui.notify(`Closed the ${t} view. The run keeps going.`,`info`)}}),m.on(`resources_discover`,()=>{let e=Ce(xe(we(import.meta.url)),He);return{skillPaths:be(e)?[e]:[]}});let pn=Ce(xe(we(import.meta.url)),He);be(pn)&&Oe(m,{source:`@agimon-ai/doompi-workflow`,directories:[pn]}),m.registerMessageRenderer(Fe,(e,{outputPad:t},n)=>{let r=ge(e.details)?e.details:void 0,i=typeof e.content==`string`?e.content:JSON.stringify(e.content);return _e(r,i,t,n)}),re(m,{runTool:E,recoverTool:jt,launchExecutor:Ft,controlTool:Mt,requireSessionRun:Q,delegateRecovery:an,trackPendingRun:Nt,observeSession:Ot,activeRunCount:async()=>ft,onLaunch:async e=>(await K(e).recordEvent(Ve,{outcome:`requested`}),X(e,!1)),followRun:async(e,t)=>{let n=await Q(e,t.sessionManager.getSessionId());return await K(t).recordEvent(`doom_workflow.follow_started`,{outcome:`started`}),await en(n,t),`Following ${it(n)}. Press ${S(b)} to stop following; the run keeps going.`},tailRun:async(e,t)=>{let n=await Q(e,t.sessionManager.getSessionId());return await K(t).recordEvent(`doom_workflow.tail_requested`,{outcome:`requested`}),[`Workflow: ${it(n)}`,...await Yt(n)].join(`
9
- `)},openRun:async(e,t)=>{let n=await Q(e,t.sessionManager.getSessionId());return await K(t).recordEvent(`doom_workflow.open_requested`,{outcome:`requested`}),t.hasUI?nn(n,t):Qt(n)},rejectRunner:Pt});let $=e=>e;ye(m,{source:`@agimon-ai/doompi-workflow`,handlers:{[t]:e=>fn(!0,$(e)),[e]:e=>fn(!1,$(e)),[r]:e=>rn($(e),!0),[n]:e=>cn($(e)),[i]:e=>ln($(e))},onError:(e,t,n)=>{$(n).ui.notify(e instanceof Error?e.message:String(e),`error`)}}),m.on(`session_start`,async(e,t)=>{let n=K(t);await n.recordEvent(`doom_workflow.session_started`,{mode:process.env.WORKFLOW_MCP_MODE===`on`?`on`:`off`,outcome:`started`}),yt=t;let r=g(t.sessionManager.getSessionId());Ot(r),lt=Date.now(),st=new Map,L.clear(),ct.clear(),ut=ke({bus:m.events,source:`@agimon-ai/doompi-workflow`,sessionId:r}),dt?.(),dt=ut.onNotification(Ae,({mode:e})=>{let r=e===`on`;r!==z&&(un(r),n.recordEvent(`doom_workflow.mode_changed`,{mode:e}),X(t,!1))}),un(process.env.WORKFLOW_MCP_MODE===`on`),R?.dispose(),R=o(ut),await Ht(t),await X(t,!1),P&&clearInterval(P),P=setInterval(()=>void X(t,!0),qe),P.unref?.()}),m.on(`session_shutdown`,async(e,t)=>{if(q.size>0){for(let e of[ze,_])e.interrupt(`SIGTERM`,{phase:`workflow`,source:`pi-session-shutdown`});await Promise.race([Promise.allSettled(q),new Promise(e=>setTimeout(e,1e4).unref?.())])}Ut(),P&&clearInterval(P),P=void 0,B&&clearInterval(B),B=void 0,ht={runs:[],failed:0},gt=void 0,vt.update(void 0),R?.dispose(),R=void 0,dt?.(),dt=void 0,ut=void 0,bt?.hide(),bt=void 0,xt=!1,St.clear(),Z.clear(),Wt(),ft=0,Jt(t),t.ui.setStatus(Pe,void 0),pt&&(pt=!1,mt.update(void 0)),await G?.recordEvent(`doom_workflow.session_finished`,{"workflow.pending_count":q.size,outcome:q.size===0?`completed`:`interrupted`}),await G?.shutdown(),G=void 0})}}export{rt as compatibleRunners,P as createWorkflowPiExtension,Ue as panelHint,We as panelHints,S as shortcutLabel};
9
+ `),gn=async(e,t)=>{mn(e),R||Yt(t),t.ui.notify(hn(),`info`),await X(t,!1)};h.registerShortcut(x,{description:`Open workflows or move typing between a workflow run and this session`,handler:async e=>{let t=B?.handle;if(t){if(!B?.interactive){e.ui.notify(`This run is hosted natively, so the view takes no typing. Your keys stay here.`,`info`);return}t.isFocused()?(t.unfocus(),e.ui.notify(`Typing goes to this session. The run keeps going.`,`info`)):(t.focus(),e.ui.notify(`Typing goes to ${B?.runKey}.`,`info`));return}if(F){Yt(e),e.ui.notify(`Workflow continues in the background.`,`info`);return}await an(e)}}),h.registerShortcut(S,{description:`Close the workflow run view`,handler:e=>{if(!B)return;let t=B.runKey;Gt(),e.ui.notify(`Closed the ${t} view. The run keeps going.`,`info`)}}),h.on(`resources_discover`,()=>{let e=_(xe(Se(import.meta.url)),He);return{skillPaths:ve(e)?[e]:[]}});let _n=_(xe(Se(import.meta.url)),He);ve(_n)&&De(h,{source:`@agimon-ai/doompi-workflow`,directories:[_n]}),h.registerMessageRenderer(Fe,(e,{outputPad:t},n)=>{let r=me(e.details)?e.details:void 0,i=typeof e.content==`string`?e.content:JSON.stringify(e.content);return he(r,i,t,n)}),te(h,{runTool:D,recoverTool:Mt,launchExecutor:It,controlTool:Nt,requireSessionRun:Q,requireRecoverableRun:on,readRecoveryEvidence:cn,delegateRecovery:ln,trackPendingRun:Pt,observeSession:kt,activeRunCount:async()=>mt,onLaunch:async e=>(await K(e).recordEvent(Ve,{outcome:`requested`}),X(e,!1)),followRun:async(e,t)=>{let n=await Q(e,t.sessionManager.getSessionId());return await K(t).recordEvent(`doom_workflow.follow_started`,{outcome:`started`}),await tn(n,t),`Following ${ot(n)}. Press ${C(x)} to stop following; the run keeps going.`},tailRun:async(e,t)=>{let n=await Q(e,t.sessionManager.getSessionId());return await K(t).recordEvent(`doom_workflow.tail_requested`,{outcome:`requested`}),[`Workflow: ${ot(n)}`,...await Xt(n)].join(`
10
+ `)},openRun:async(e,t)=>{let n=await Q(e,t.sessionManager.getSessionId());return await K(t).recordEvent(`doom_workflow.open_requested`,{outcome:`requested`}),t.hasUI?rn(n,t):$t(n)},rejectRunner:Ft});let $=e=>e;_e(h,{source:`@agimon-ai/doompi-workflow`,handlers:{[t]:e=>gn(!0,$(e)),[e]:e=>gn(!1,$(e)),[r]:e=>an($(e),!0),[n]:e=>fn($(e)),[i]:e=>pn($(e))},onError:(e,t,n)=>{$(n).ui.notify(e instanceof Error?e.message:String(e),`error`)}}),h.on(`session_start`,async(e,t)=>{let n=K(t);await n.recordEvent(`doom_workflow.session_started`,{mode:process.env.WORKFLOW_MCP_MODE===`on`?`on`:`off`,outcome:`started`}),xt=t;let r=v(t.sessionManager.getSessionId());kt(r),dt=Date.now(),lt=new Map,I.clear(),ut.clear(),ft=Ee({bus:h.events,source:`@agimon-ai/doompi-workflow`,sessionId:r}),pt?.(),pt=ft.onNotification(Oe,({mode:e})=>{let r=e===`on`;r!==R&&(mn(r),n.recordEvent(`doom_workflow.mode_changed`,{mode:e}),X(t,!1))}),mn(process.env.WORKFLOW_MCP_MODE===`on`),L?.dispose(),L=o(ft),await Ut(t),await X(t,!1),N&&clearInterval(N),N=setInterval(()=>void X(t,!0),Ye),N.unref?.()}),h.on(`session_shutdown`,async(e,t)=>{if(q.size>0){for(let e of[ze,y])e.interrupt(`SIGTERM`,{phase:`workflow`,source:`pi-session-shutdown`});await Promise.race([Promise.allSettled(q),new Promise(e=>setTimeout(e,1e4).unref?.())])}Wt(),N&&clearInterval(N),N=void 0,z&&clearInterval(z),z=void 0,_t={runs:[],failed:0},vt=void 0,bt.update(void 0),L?.dispose(),L=void 0,pt?.(),pt=void 0,ft=void 0,V?.hide(),V=void 0,St=!1,Ct.clear(),Z.clear(),Gt(),mt=0,Yt(t),t.ui.setStatus(Pe,void 0),ht&&(ht=!1,gt.update(void 0)),await G?.recordEvent(`doom_workflow.session_finished`,{"workflow.pending_count":q.size,outcome:q.size===0?`completed`:`interrupted`}),await G?.shutdown(),G=void 0})}}export{at as compatibleRunners,N as createWorkflowPiExtension,Ue as panelHint,We as panelHints,C as shortcutLabel};
10
11
  //# sourceMappingURL=piExtension.mjs.map