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