@agimon-ai/doompi-workflow 0.0.1-alpha.37 → 0.0.1-alpha.39

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.
Files changed (125) hide show
  1. package/LICENSE +6 -0
  2. package/README.md +14 -8
  3. package/dist/_virtual/_rolldown/runtime.cjs +1 -0
  4. package/dist/adapters/pi/extension.cjs +1 -1
  5. package/dist/adapters/pi/extension.mjs +1 -1
  6. package/dist/adapters/pi/workflow/piExtension.cjs +6 -8
  7. package/dist/adapters/pi/workflow/piExtension.cjs.map +1 -1
  8. package/dist/adapters/pi/workflow/piExtension.mjs +6 -8
  9. package/dist/adapters/pi/workflow/piExtension.mjs.map +1 -1
  10. package/dist/adapters/pi/workflow/piTools.cjs +3 -3
  11. package/dist/adapters/pi/workflow/piTools.mjs +3 -3
  12. package/dist/adapters/pi/workflow/workflowLauncher.cjs +1 -1
  13. package/dist/adapters/pi/workflow/workflowLauncher.cjs.map +1 -1
  14. package/dist/adapters/pi/workflow/workflowLauncher.mjs +1 -1
  15. package/dist/adapters/pi/workflow/workflowLauncher.mjs.map +1 -1
  16. package/dist/adapters/workflowCatalogChannel.cjs +2 -0
  17. package/dist/adapters/workflowCatalogChannel.cjs.map +1 -0
  18. package/dist/adapters/workflowCatalogChannel.d.cts +23 -0
  19. package/dist/adapters/workflowCatalogChannel.d.cts.map +1 -0
  20. package/dist/adapters/workflowCatalogChannel.d.mts +23 -0
  21. package/dist/adapters/workflowCatalogChannel.d.mts.map +1 -0
  22. package/dist/adapters/workflowCatalogChannel.mjs +2 -0
  23. package/dist/adapters/workflowCatalogChannel.mjs.map +1 -0
  24. package/dist/adapters/workflowHubApi.cjs +2 -0
  25. package/dist/adapters/workflowHubApi.cjs.map +1 -0
  26. package/dist/adapters/workflowHubApi.d.cts +24 -0
  27. package/dist/adapters/workflowHubApi.d.cts.map +1 -0
  28. package/dist/adapters/workflowHubApi.d.mts +24 -0
  29. package/dist/adapters/workflowHubApi.d.mts.map +1 -0
  30. package/dist/adapters/workflowHubApi.mjs +2 -0
  31. package/dist/adapters/workflowHubApi.mjs.map +1 -0
  32. package/dist/adapters/workflowWatcher.cjs +2 -0
  33. package/dist/adapters/workflowWatcher.cjs.map +1 -0
  34. package/dist/adapters/workflowWatcher.d.cts +24 -0
  35. package/dist/adapters/workflowWatcher.d.cts.map +1 -0
  36. package/dist/adapters/workflowWatcher.d.mts +24 -0
  37. package/dist/adapters/workflowWatcher.d.mts.map +1 -0
  38. package/dist/adapters/workflowWatcher.mjs +2 -0
  39. package/dist/adapters/workflowWatcher.mjs.map +1 -0
  40. package/dist/adapters/workflowsHubChannel.cjs +2 -0
  41. package/dist/adapters/workflowsHubChannel.cjs.map +1 -0
  42. package/dist/adapters/workflowsHubChannel.d.cts +20 -0
  43. package/dist/adapters/workflowsHubChannel.d.cts.map +1 -0
  44. package/dist/adapters/workflowsHubChannel.d.mts +20 -0
  45. package/dist/adapters/workflowsHubChannel.d.mts.map +1 -0
  46. package/dist/adapters/workflowsHubChannel.mjs +2 -0
  47. package/dist/adapters/workflowsHubChannel.mjs.map +1 -0
  48. package/dist/hubApi.cjs +1 -0
  49. package/dist/hubApi.d.cts +2 -0
  50. package/dist/hubApi.d.mts +2 -0
  51. package/dist/hubApi.mjs +1 -0
  52. package/dist/schemas/workflowPi.cjs +1 -1
  53. package/dist/schemas/workflowPi.cjs.map +1 -1
  54. package/dist/schemas/workflowPi.mjs +1 -1
  55. package/dist/schemas/workflowPi.mjs.map +1 -1
  56. package/dist/services/webWorkflowCatalog.cjs +2 -0
  57. package/dist/services/webWorkflowCatalog.cjs.map +1 -0
  58. package/dist/services/webWorkflowCatalog.d.cts +49 -0
  59. package/dist/services/webWorkflowCatalog.d.cts.map +1 -0
  60. package/dist/services/webWorkflowCatalog.d.mts +49 -0
  61. package/dist/services/webWorkflowCatalog.d.mts.map +1 -0
  62. package/dist/services/webWorkflowCatalog.mjs +2 -0
  63. package/dist/services/webWorkflowCatalog.mjs.map +1 -0
  64. package/dist/services/workflowLaunchCommand.cjs +2 -0
  65. package/dist/services/workflowLaunchCommand.cjs.map +1 -0
  66. package/dist/services/workflowLaunchCommand.mjs +2 -0
  67. package/dist/services/workflowLaunchCommand.mjs.map +1 -0
  68. package/dist/services/workflowRuns.cjs +3 -0
  69. package/dist/services/workflowRuns.cjs.map +1 -0
  70. package/dist/services/workflowRuns.d.cts +17 -0
  71. package/dist/services/workflowRuns.d.cts.map +1 -0
  72. package/dist/services/workflowRuns.d.mts +17 -0
  73. package/dist/services/workflowRuns.d.mts.map +1 -0
  74. package/dist/services/workflowRuns.mjs +3 -0
  75. package/dist/services/workflowRuns.mjs.map +1 -0
  76. package/dist/services/workflowTerminal.cjs +2 -0
  77. package/dist/services/workflowTerminal.cjs.map +1 -0
  78. package/dist/services/workflowTerminal.mjs +2 -0
  79. package/dist/services/workflowTerminal.mjs.map +1 -0
  80. package/dist/tui/workflow/workflowCatalog.cjs +1 -1
  81. package/dist/tui/workflow/workflowCatalog.cjs.map +1 -1
  82. package/dist/tui/workflow/workflowCatalog.mjs +1 -1
  83. package/dist/tui/workflow/workflowCatalog.mjs.map +1 -1
  84. package/dist/types/webWorkflowTerminal.cjs +2 -0
  85. package/dist/types/webWorkflowTerminal.cjs.map +1 -0
  86. package/dist/types/webWorkflowTerminal.mjs +2 -0
  87. package/dist/types/webWorkflowTerminal.mjs.map +1 -0
  88. package/dist/types/webWorkflows.cjs +2 -0
  89. package/dist/types/webWorkflows.cjs.map +1 -0
  90. package/dist/types/webWorkflows.d.cts +95 -0
  91. package/dist/types/webWorkflows.d.cts.map +1 -0
  92. package/dist/types/webWorkflows.d.mts +95 -0
  93. package/dist/types/webWorkflows.d.mts.map +1 -0
  94. package/dist/types/webWorkflows.mjs +2 -0
  95. package/dist/types/webWorkflows.mjs.map +1 -0
  96. package/dist/webHub.cjs +1 -0
  97. package/dist/webHub.d.cts +3 -0
  98. package/dist/webHub.d.mts +3 -0
  99. package/dist/webHub.mjs +1 -0
  100. package/package.json +47 -11
  101. package/src/prompts/doompi-author-workflow/SKILL.md +1 -3
  102. package/src/types/webWorkflowTerminal.ts +92 -0
  103. package/src/types/webWorkflows.ts +145 -0
  104. package/web/ArtifactsPane.tsx +433 -0
  105. package/web/LICENSE +29 -0
  106. package/web/LaunchWorkflowDialog.tsx +249 -0
  107. package/web/StepTerminalPanel.tsx +251 -0
  108. package/web/WorkflowCatalogDrawer.tsx +228 -0
  109. package/web/WorkflowToolMessage.tsx +54 -0
  110. package/web/WorkflowsActivitySection.tsx +152 -0
  111. package/web/WorkflowsPanel.tsx +737 -0
  112. package/web/ansiSpans.ts +230 -0
  113. package/web/catalogStore.ts +112 -0
  114. package/web/index.ts +61 -0
  115. package/web/launchLine.ts +66 -0
  116. package/web/runDuration.ts +15 -0
  117. package/web/terminalApi.ts +151 -0
  118. package/web/tsconfig.json +22 -0
  119. package/web/workflowActivity.ts +102 -0
  120. package/web/workflowToolRender.ts +585 -0
  121. package/web/workflowsStore.ts +90 -0
  122. package/dist/tui/workflow/terminalGrid.cjs +0 -2
  123. package/dist/tui/workflow/terminalGrid.cjs.map +0 -1
  124. package/dist/tui/workflow/terminalGrid.mjs +0 -2
  125. package/dist/tui/workflow/terminalGrid.mjs.map +0 -1
@@ -1,11 +1,9 @@
1
- import{LEADER_CATALOG_ACTION as e,LEADER_DISABLE_ACTION as t,LEADER_ENABLE_ACTION as n,LEADER_MANAGE_ACTION as r,LEADER_RECOVER_ACTION as i,PACKAGE_SOURCE as a}from"../../../types/index.mjs";import{registerRunProvider as o}from"../../../services/backgroundWork.mjs";import{narrateWorkflowTransition as s}from"../../../services/workflowNarration.mjs";import{WorkflowInspectorComponent as c}from"../../../tui/workflow/workflowInspector.mjs";import{TerminalInputBatcher as l}from"../../../tui/workflow/workflowOverlay.mjs";import{terminalReplaySchema as ee}from"../../../schemas/workflowPi.mjs";import{renderGridLines as u}from"../../../tui/workflow/terminalGrid.mjs";import{WORKFLOW_FINISHED_MESSAGE as te,registerWorkflowFinishedRenderer as ne}from"../../../tui/workflow/workflowFinishedMessage.mjs";import{openWorkflowCatalogOverlay as re}from"../../../tui/workflow/workflowCatalog.mjs";import{openWorkflowPickerOverlay as ie}from"../../../tui/workflow/workflowPicker.mjs";import{WorkflowProgressOverlay as ae,workflowProgressRow as oe}from"../../../tui/workflow/workflowProgressOverlay.mjs";import{openWorkflowChoice as se,openWorkflowInput as ce}from"../../../tui/workflow/workflowPrompt.mjs";import{WorkflowRunPanelComponent as le,runPanelUiOptions as ue}from"../../../tui/workflow/workflowRunPanel.mjs";import{isWorkflowStepMessageDetails as de,renderWorkflowStepMessage as fe}from"../../../tui/workflow/workflowStepMessage.mjs";import{finishedRunSummary as pe,isSessionRun as me,launchNotice as he,runsForSession as ge,toolResultText as _e,withOptions as ve}from"./piToolBridge.mjs";import{WORKFLOW_PI_TOOL_NAMES as ye,createWorkflowLaunchExecutor as be,registerWorkflowPiTools as xe}from"./piTools.mjs";import{launchWorkflowEntry as Se,loadWorkflowCatalog as Ce,summarizeWorkflowFile as we}from"./workflowLauncher.mjs";import{createRequire as d}from"node:module";import{existsSync as Te}from"node:fs";import{basename as Ee,dirname as De,extname as f,resolve as Oe}from"node:path";import{fileURLToPath as ke}from"node:url";import{SUBAGENT_ROOT_SESSION_ENV as p,resolveRootSessionId as m}from"@agimon-ai/doompi-extension-contracts/child-process";import{DOOM_UI_HUB_SERVICE as Ae,requireDoomUiHub as je}from"@agimon-ai/doompi-extension-contracts/ui-hub";import{spawn as Me}from"node:child_process";import{randomUUID as Ne}from"node:crypto";import{readFile as Pe}from"node:fs/promises";import{DOOM_MINOR_MODE_CATALOG_SERVICE as Fe,registerMinorModeOwner as Ie,requireMinorModeCatalog as Le}from"@agimon-ai/doompi-extension-contracts/mode";import{DOOM_NARRATION_SERVICE as Re,createNarrationRequest as ze,requireDoomNarrationService as Be}from"@agimon-ai/doompi-extension-contracts/narration";import{createDoomReadinessCoordinator as Ve,readDoomReadinessCoordinator as He}from"@agimon-ai/doompi-extension-contracts/readiness";import{createDoomTelemetry as Ue}from"@agimon-ai/doompi-telemetry";import{createEmbeddedWorkflowFeature as We,readWorkflowProgress as Ge,summarizeWorkflowProgress as Ke}from"@agimon-ai/workflow-mcp";import{truncateToWidth as h,visibleWidth as g}from"@earendil-works/pi-tui";const qe={overlay:!0,overlayOptions:{anchor:`top-left`,width:`100%`,maxHeight:`100%`,margin:0}},Je=`workflow-mcp-follow`,Ye=`activate`,Xe=`deactivate`;function Ze(e,t){return{activation:e?`active`:`inactive`,condition:`ready`,...e?{detail:t>0?`${t} active runs`:`tools enabled`,color:`accent`}:{},actions:[...e?[{id:Ye,enabled:!1,disabledReason:`Workflow mode is active.`}]:[{id:Ye,enabled:!0}],...e?[{id:Xe,enabled:!0}]:[{id:Xe,enabled:!1,disabledReason:`Workflow mode is inactive.`}]]}}const _=`ctrl+alt+w`,v=`ctrl+alt+q`,Qe={alt:`⌥`,cmd:`⌘`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`};function y(e,t=process.platform){let n=e.split(`+`);return t===`darwin`?n.map(e=>Qe[e]??e.toUpperCase()).join(``):n.map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(`+`)}const b=1e4,x=`running`,S=`completed`,C=`failed`,w=`error`,T=`error`,$e=`doom_workflow.launch_requested`,et=`../../../../skills`,E={running:`▸`,repair:`⚠`,completed:`✔`,failed:`✖`,separator:`·`};function tt(e,t=process.platform){let n=y(v,t);return e?[`Esc Esc closes this view`,`${y(_,t)} switches typing`,`${n} closes`].join(` ${E.separator} `):`view only ${E.separator} ${n} closes this view`}function nt(e,t=process.platform){let n=[y(v,t),`close`];return e?[[`Esc Esc`,`close`],[y(_,t),`switch typing`],n]:[n]}function rt(e,t,n,r){let i=`${E.running} ${e.runKey}`,a=[e.workspace,ot(e),e.runner].filter(Boolean).join(` ${E.separator} `),o=`${y(_)} to background`,s=n.fg(`accent`,h(i,r));return g(i)+3+g(a)<=r&&(s+=` `+n.fg(`muted`,a),g(i)+3+g(a)+3+g(o)<=r&&(s+=` `+n.fg(`dim`,o))),[s,...t.map(e=>h(e,r))]}function it(e,t,n){let r=[];for(let i of e){let e=i.status===S?E.completed:i.status===C?E.failed:E.running,a=i.status===S?`success`:i.status===C?T:`accent`,o=i.total?` ${E.separator} ${(i.index??0)+1}/${i.total}`:``;if(r.push(h(t.fg(a,`${e} ${i.name}`)+t.fg(`dim`,o),n)),i.status===x)for(let e of i.steps){let i=e.status===S?E.completed:e.status===C?E.failed:E.running,a=e.status===x?`muted`:e.status===C?T:`dim`;r.push(h(t.fg(a,` ${i} ${e.name}`),n))}}return r}function at(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 ot(e){return e.stale?`stale`:e.stage===w?C:e.stage}const st=`CMUX_WORKSPACE_ID`,D=`TMUX_PANE`,ct=`WORKFLOW_TMUX_SESSION_NAME`,lt=[`CMUX_SURFACE_ID`,p,st,D,`WORKFLOW_LAUNCHER_TYPE`,ct,`WORKFLOW_STATUS_FILE`],O=`tmux`,k=`cmux`,ut=`native`,dt=`This run points at Pi's own terminal, so its launcher screen is hidden. Use workflow status instead.`;function ft(e){let t=e.launcher;if(t?.type===k){let e=process.env[st];return!!e&&t.workspaceId===e}if(t?.type!==O)return!1;let n=process.env[D],r=process.env[ct];return!!n&&t.paneId===n||!!r&&t.sessionName===r}const pt=`No durable launcher identity is recorded for this run.`;function mt(e){return Ee(e,f(e))}function ht(){let e={...process.env};for(let t of lt)delete e[t];return e}function gt(){let e=process.env.WORKFLOW_LAUNCHER;return e===O||e===k?e:void 0}function _t(e){return e.map(e=>`'${e.replaceAll(`'`,`'\\''`)}'`).join(` `)}function vt(){try{return d(import.meta.url).resolve(`@agimon-ai/workflow-mcp/cli`)}catch{return}}const yt=async(e,t,n)=>{await new Promise((r,i)=>{let a=Me(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 bt(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 xt(e){return[e.runKey,e.workspace,ot(e),e.runner].filter(Boolean).join(` ${E.separator} `)}function St(d={}){return f=>{let p=d.featureFactory?.()??We(),Me=Ne(),h=!0,g=0,Qe,C,T,st=d.isActive??(()=>!0),D=()=>h&&st(),ct=e=>{let t=e.diagnostics.join(`; `),n=(e.error?.message??t)||`Initialization did not complete.`,r=`${e.packageId} initialization ${e.state}: ${n}`;B?.hasUI?B.ui.notify(r,`warning`):process.emitWarning(r)},lt=e=>(d.cordis?He(d.cordis):void 0)||(T??=Ve({notify:ct}),T),A=async e=>{e?.throwIfAborted();let t=C;if(!t)throw Error(`Workflow session initialization has not started.`);let n=await t;if(e?.throwIfAborted(),await n.wait(e?{signal:e}:void 0),!D()||t!==C)throw Error(`Workflow readiness belongs to a stale extension generation.`)},St=(e,t)=>{let n=Reflect.get(e,t,e);return typeof n==`function`?n.bind(e):n},Ct=new Proxy(f,{get(e,t){return t===`registerTool`?t=>{e.registerTool({...t,execute:async(e,n,r,i,a)=>(await A(r),t.execute(e,n,r,i,a))})}:St(e,t)}}),{parser:wt,recoverService:Tt,registry:M,runService:Et,runTool:Dt}=p,Ot=d.spawnDetached??yt,kt=d.monitorIntervalMs??3e4,At=d.followIntervalMs??2e3,N,jt=!1,P,Mt=!1,F,Nt=new Map,I=new Map,Pt=new Set,Ft=0,It=d.cordis?o(d.cordis):void 0,Lt=!1,L=0,R=!1,Rt,z;d.cordis?.inject([Fe],e=>{let t=Ze(R,L),n=Ie(Le(e),{descriptor:{source:a,id:`workflow`,label:`Workflow`,description:`Workflow discovery, launch, inspection, control, and recovery tools.`,order:20,actions:[{id:Ye,label:`Activate`,description:`Enable workflow tools for this session.`,contexts:[`tui`,`headless`],parameters:[]},{id:Xe,label:`Deactivate`,description:`Disable workflow tools without stopping active runs.`,contexts:[`tui`,`headless`],parameters:[]}]},initialState:t,async handleAction(e,t,n){if(await A(),e===Ye)return await Rn(!0,n.context),{message:`Workflow mode activated.`};if(e===Xe)return await Rn(!1,n.context),{message:`Workflow mode deactivated.`};throw Error(`Unknown workflow mode action: ${e}`)}});return z=n,Rt=JSON.stringify(t),()=>{n.dispose(),z===n&&(z=void 0,Rt=void 0)}});let B,V,H,zt=!1,Bt=new Map,U,Vt=new Set,W=new Map,Ht=[],G=new Map,Ut=0,Wt=new ae,Gt,Kt,K=e=>(Kt??=Ue({serviceName:`doom-workflow`,packageName:`@agimon-ai/doompi-workflow`,cwd:e?.cwd,env:process.env,enableLogs:!0,enableTraces:!0}),Kt),qt,Jt=e=>{K(B).recordWarning(`doom_workflow.narration_failed`,e).catch(e=>{process.emitWarning(`Doom-workflow could not record a narration failure: ${String(e)}`)})},Yt={narrate(e){let t=qt,n=ze(e);if(!(!t||!n))try{Promise.resolve(t.request(n)).catch(Jt)}catch(e){Jt(e)}}};d.cordis?.inject([Re],e=>{let t=Be(e);return qt=t,()=>{qt===t&&(qt=void 0)}});let Xt=e=>{e&&D()&&(Gt=m(e))},Zt=()=>{let e=Gt??B?.sessionManager.getSessionId();return e?m(e):void 0},Qt=(e,t)=>e.sessionManager.getSessionId().trim()===t,$t=p.createRecoverTool({ownerSessionId:Zt}),en=p.createControlTool(),q=new Set,tn=e=>{let t=Date.now(),n=K();return q.add(e),n.recordEvent(`doom_workflow.run_started`,{outcome:`started`}),e.then(()=>(q.delete(e),n.recordEvent(`doom_workflow.run_finished`,{duration_ms:Date.now()-t,outcome:`completed`})),r=>(q.delete(e),n.recordError(`doom_workflow.run_failed`,r,{duration_ms:Date.now()-t}))),e},nn=(e,t)=>{let n=bt(wt.parseWorkflowFile(e));if(!(!n||n.includes(t)))return`Runner ${JSON.stringify(t)} is not available across this workflow. Compatible runners: ${n.join(`, `)||`none`}.`},rn=be({activeRunCount:async()=>L,observeSession:Xt,rejectRunner:nn,runTool:Dt,trackPendingRun:tn,...d.launchAckPollMs===void 0?{}:{launchAckPollMs:d.launchAckPollMs},...d.launchAckTimeoutMs===void 0?{}:{launchAckTimeoutMs:d.launchAckTimeoutMs},findLaunchedRun:async({sessionId:e,since:t,workflowPath:n})=>{let r=mt(n),i=n=>{if(!me(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 U?(await U.refresh(),Ht.find(i)):(await j(M)).find(i)},onLateFailure:(e,t)=>{let n=e instanceof Error?e.message:String(e);t.ui.notify(he(`Workflow launch failed after it was reported started.\n${n}`),`warning`)},onLaunch:async e=>(await K(e).recordEvent($e,{outcome:`requested`}),X(e,!1))}),an=()=>{let e=[...G.values()].sort((e,t)=>Date.parse(e.startedAt)-Date.parse(t.startedAt)||Y(e).localeCompare(Y(t)));Wt.update(e.map(e=>oe(e,W.get(Y(e))??[])))},on=e=>{Ut+=1;let t=Y(e);e.stage===x&&!e.stale?G.set(t,e):G.delete(t),an()},sn=e=>{G.clear();for(let t of e)G.set(Y(t),t);an()},cn=(e,t)=>{let n=W.get(e)??[];n.push(t),W.set(e,n)},J,ln=e=>{!D()||e!==B||J||(J=setTimeout(()=>{J=void 0,D()&&e===B&&X(e,!0)},120),J.unref?.())},un=e=>{W.delete(e)},dn=async e=>{let t=m(e.sessionManager.getSessionId()),n=p.createRunControl({recordFilter:e=>me(e,t)});U=n;let r=()=>D()&&e===B&&U===n,i=[n.on(`step`,t=>{if(!r())return;let n=Y(t.record);cn(n,{at:t.at,job:t.job,status:t.status,step:t.step,type:`step`,...t.reason===void 0?{}:{reason:t.reason}}),on(t.record),ln(e)}),n.on(`job`,t=>{if(!r())return;let n=Y(t.record);cn(n,{at:t.at,job:t.job,status:t.status,type:`job`,...t.index===void 0?{}:{index:t.index},...t.reason===void 0?{}:{reason:t.reason},...t.total===void 0?{}:{total:t.total}}),on(t.record),ln(e)}),n.on(`runStarted`,t=>{r()&&(Ht.unshift(t.record),Ht.length>32&&(Ht.length=32),on(t.record),ln(e))}),n.on(`runUpdated`,t=>{r()&&(on(t.record),X(e,!0))}),n.on(`runFinished`,t=>{r()&&(on(t.record),X(e,!0))})];for(let e of i)Vt.add(e);try{await n.start()}catch(e){for(let e of i)Vt.delete(e),e();throw n.dispose(),U===n&&(U=void 0),e}if(!D()||e!==B||U!==n){for(let e of i)Vt.delete(e),e();n.dispose(),U===n&&(U=void 0)}},fn=()=>{J&&clearTimeout(J),J=void 0;for(let e of Vt)e();Vt.clear(),U?.dispose(),U=void 0,W.clear(),Ut+=1,G.clear(),an()},pn=()=>{V?.teardown(),V?.handle?.hide(),V=void 0},Y=e=>e.runId??`${e.workspace}/${e.runKey}`,mn=()=>{let e=z;if(!e)return;let t=Ze(R,L),n=JSON.stringify(t);n!==Rt&&(Rt=n,e.publish(t))},X=async(e,t)=>{if(!D()||e!==B)return;let n=K(e);if(jt){n.recordEvent(`doom_workflow.monitor_skipped`,{outcome:`already_running`});return}let r=Date.now(),i=Ut;jt=!0;try{let a=await j(M);if(!D()||e!==B)return;let o=m(e.sessionManager.getSessionId()),c=Qt(e,o),l=a.filter(e=>me(e,o)),ee=l.filter(e=>e.stage===x&&!e.stale),u=l.filter(e=>e.stage===w);i===Ut&&sn(ee),n.recordEvent(`doom_workflow.monitor_counts`,{"workflow.run_count":l.length,"workflow.running_count":ee.length,"workflow.failed_count":u.length,mode:R?`on`:`off`});let ne=ge(a,o).filter(e=>!e.stale);L=ne.length,mn(),It?.update(ne.map(e=>({id:e.runId??e.runKey,sessionId:o}))),xn(new Set(l.map(e=>Y(e))));let re=new Map(l.map(e=>[Y(e),e.stage]));for(let e of l){let n=Y(e),r=Nt.get(n),i=Date.parse(e.startedAt),a=r===void 0&&(Ft===0||!Number.isFinite(i)||i>=Ft);Lt&&s(Yt,e,r===x||r===S||r===w?r:void 0,a),!(!t||!c||e.stage===x)&&!Pt.has(n)&&(r===x||a)&&I.set(n,e)}let ie=new Set([...l.filter(e=>e.stage===x&&!e.stale).map(e=>Y(e)),...I.keys()]);for(let e of W.keys())ie.has(e)||un(e);if(t)for(let t of l){if(Nt.get(Y(t))!==x||t.stage===x)continue;let r=t.stage===S,i=t.failedJob?` at ${t.failedJob}`:``;e.ui.notify(r?`${E.completed} ${t.runKey} completed`:`${E.failed} ${t.runKey} ${ot(t)}${i}`,r?`info`:`warning`),n.recordEvent(`doom_workflow.run_transition`,{outcome:r?`completed`:ot(t)})}if(c&&I.size>0)try{let e=[...I.values()],t=await Promise.all(e.map(async e=>{if(e.stage===S)return pe(e,[]);let t=Y(e),n=W.get(t)??at(e,await Ge(M.runDirectoryFor(e)));return pe(e,Ke(n))}));f.sendMessage({customType:te,content:t.join(`
1
+ import{createWorkflowTerminalService as e}from"../../../services/workflowTerminal.mjs";import{LEADER_CATALOG_ACTION as t,LEADER_DISABLE_ACTION as n,LEADER_ENABLE_ACTION as r,LEADER_MANAGE_ACTION as i,LEADER_RECOVER_ACTION as a,PACKAGE_SOURCE as o}from"../../../types/index.mjs";import{registerRunProvider as s}from"../../../services/backgroundWork.mjs";import{narrateWorkflowTransition as c}from"../../../services/workflowNarration.mjs";import{WorkflowInspectorComponent as l}from"../../../tui/workflow/workflowInspector.mjs";import{TerminalInputBatcher as ee}from"../../../tui/workflow/workflowOverlay.mjs";import{WORKFLOW_FINISHED_MESSAGE as te,registerWorkflowFinishedRenderer as ne}from"../../../tui/workflow/workflowFinishedMessage.mjs";import{openWorkflowCatalogOverlay as u}from"../../../tui/workflow/workflowCatalog.mjs";import{openWorkflowPickerOverlay as re}from"../../../tui/workflow/workflowPicker.mjs";import{WorkflowProgressOverlay as ie,workflowProgressRow as ae}from"../../../tui/workflow/workflowProgressOverlay.mjs";import{openWorkflowChoice as oe,openWorkflowInput as se}from"../../../tui/workflow/workflowPrompt.mjs";import{WorkflowRunPanelComponent as ce,runPanelUiOptions as le}from"../../../tui/workflow/workflowRunPanel.mjs";import{isWorkflowStepMessageDetails as ue,renderWorkflowStepMessage as de}from"../../../tui/workflow/workflowStepMessage.mjs";import{finishedRunSummary as fe,isSessionRun as pe,launchNotice as me,runsForSession as he,toolResultText as ge,withOptions as _e}from"./piToolBridge.mjs";import{WORKFLOW_PI_TOOL_NAMES as ve,createWorkflowLaunchExecutor as ye,registerWorkflowPiTools as be}from"./piTools.mjs";import{launchWorkflowEntry as xe,loadWorkflowCatalog as Se,summarizeWorkflowFile as Ce}from"./workflowLauncher.mjs";import{isLaunchParseFailure as we,parseWorkflowLaunchCommand as Te,resolveWorkflowEntry as Ee,validateWorkflowLaunch as De}from"../../../services/workflowLaunchCommand.mjs";import{createRequire as d}from"node:module";import{randomUUID as Oe}from"node:crypto";import{existsSync as ke}from"node:fs";import{basename as Ae,dirname as je,extname as f,resolve as Me}from"node:path";import{WorkflowTerminalService as Ne,createEmbeddedWorkflowFeature as Pe,readWorkflowProgress as Fe,summarizeWorkflowProgress as Ie}from"@agimon-ai/workflow-mcp";import{fileURLToPath as Le}from"node:url";import{SUBAGENT_ROOT_SESSION_ENV as p,resolveRootSessionId as m}from"@agimon-ai/doompi-extension-contracts/child-process";import{DOOM_UI_HUB_SERVICE as Re,requireDoomUiHub as ze}from"@agimon-ai/doompi-extension-contracts/ui-hub";import{spawn as Be}from"node:child_process";import{readFile as Ve}from"node:fs/promises";import{DOOM_MINOR_MODE_CATALOG_SERVICE as He,registerMinorModeOwner as Ue,requireMinorModeCatalog as We}from"@agimon-ai/doompi-extension-contracts/mode";import{DOOM_NARRATION_SERVICE as Ge,createNarrationRequest as Ke,requireDoomNarrationService as qe}from"@agimon-ai/doompi-extension-contracts/narration";import{createDoomReadinessCoordinator as Je,readDoomReadinessCoordinator as Ye}from"@agimon-ai/doompi-extension-contracts/readiness";import{createDoomTelemetry as Xe}from"@agimon-ai/doompi-telemetry";import{truncateToWidth as h,visibleWidth as g}from"@earendil-works/pi-tui";const Ze={overlay:!0,overlayOptions:{anchor:`top-left`,width:`100%`,maxHeight:`100%`,margin:0}},Qe=`workflow-mcp-follow`,$e=`activate`,et=`deactivate`;function tt(e,t){return{activation:e?`active`:`inactive`,condition:`ready`,...e?{detail:t>0?`${t} active runs`:`tools enabled`,color:`accent`}:{},actions:[...e?[{id:$e,enabled:!1,disabledReason:`Workflow mode is active.`}]:[{id:$e,enabled:!0}],...e?[{id:et,enabled:!0}]:[{id:et,enabled:!1,disabledReason:`Workflow mode is inactive.`}]]}}const _=`ctrl+alt+w`,v=`ctrl+alt+q`,nt={alt:`⌥`,cmd:`⌘`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`};function y(e,t=process.platform){let n=e.split(`+`);return t===`darwin`?n.map(e=>nt[e]??e.toUpperCase()).join(``):n.map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(`+`)}const rt=1e4,b=`running`,x=`completed`,S=`failed`,C=`error`,w=`error`,it=`doom_workflow.launch_requested`,at=`../../../../skills`,T={running:`▸`,repair:`⚠`,completed:`✔`,failed:`✖`,separator:`·`};function ot(e,t=process.platform){let n=y(v,t);return e?[`Esc Esc closes this view`,`${y(_,t)} switches typing`,`${n} closes`].join(` ${T.separator} `):`view only ${T.separator} ${n} closes this view`}function st(e,t=process.platform){let n=[y(v,t),`close`];return e?[[`Esc Esc`,`close`],[y(_,t),`switch typing`],n]:[n]}function ct(e,t,n,r){let i=`${T.running} ${e.runKey}`,a=[e.workspace,dt(e),e.runner].filter(Boolean).join(` ${T.separator} `),o=`${y(_)} to background`,s=n.fg(`accent`,h(i,r));return g(i)+3+g(a)<=r&&(s+=` `+n.fg(`muted`,a),g(i)+3+g(a)+3+g(o)<=r&&(s+=` `+n.fg(`dim`,o))),[s,...t.map(e=>h(e,r))]}function lt(e,t,n){let r=[];for(let i of e){let e=i.status===x?T.completed:i.status===S?T.failed:T.running,a=i.status===x?`success`:i.status===S?w:`accent`,o=i.total?` ${T.separator} ${(i.index??0)+1}/${i.total}`:``;if(r.push(h(t.fg(a,`${e} ${i.name}`)+t.fg(`dim`,o),n)),i.status===b)for(let e of i.steps){let i=e.status===x?T.completed:e.status===S?T.failed:T.running,a=e.status===b?`muted`:e.status===S?w:`dim`;r.push(h(t.fg(a,` ${i} ${e.name}`),n))}}return r}function ut(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 dt(e){return e.stale?`stale`:e.stage===C?S:e.stage}const ft=[`CMUX_SURFACE_ID`,p,`CMUX_WORKSPACE_ID`,`TMUX_PANE`,`WORKFLOW_LAUNCHER_TYPE`,`WORKFLOW_TMUX_SESSION_NAME`,`WORKFLOW_STATUS_FILE`],pt=`tmux`,mt=`cmux`,ht=`This run points at Pi's own terminal, so its launcher screen is hidden. Use workflow status instead.`;function gt(e){return Ae(e,f(e))}function _t(){let e={...process.env};for(let t of ft)delete e[t];return e}function vt(){let e=process.env.WORKFLOW_LAUNCHER;return e===pt||e===mt?e:void 0}function yt(e){return e.map(e=>`'${e.replaceAll(`'`,`'\\''`)}'`).join(` `)}function bt(){try{return d(import.meta.url).resolve(`@agimon-ai/workflow-mcp/cli`)}catch{return}}const xt=async(e,t,n)=>{await new Promise((r,i)=>{let a=Be(e,[...t],{...n,detached:!0,stdio:`ignore`});a.once(`error`,i),a.once(`spawn`,()=>{a.off(`error`,i),a.unref(),r()})})};function E(e,t){if(Array.isArray(e)){for(let n of e)E(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)):E(r,t)}function St(e){let t=[];if(E(e,t),t.length!==0)return t[0].filter(e=>t.every(t=>t.includes(e))).sort()}async function D(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 Ct(e){return[e.runKey,e.workspace,dt(e),e.runner].filter(Boolean).join(` ${T.separator} `)}function wt(d={}){return f=>{let p=d.featureFactory?.()??Pe(),Be=Oe(),h=!0,g=0,nt,S,w,ft=d.isActive??(()=>!0),E=()=>h&&ft(),wt=e=>{let t=e.diagnostics.join(`; `),n=(e.error?.message??t)||`Initialization did not complete.`,r=`${e.packageId} initialization ${e.state}: ${n}`;R?.hasUI?R.ui.notify(r,`warning`):process.emitWarning(r)},Tt=e=>(d.cordis?Ye(d.cordis):void 0)||(w??=Je({notify:wt}),w),O=async e=>{e?.throwIfAborted();let t=S;if(!t)throw Error(`Workflow session initialization has not started.`);let n=await t;if(e?.throwIfAborted(),await n.wait(e?{signal:e}:void 0),!E()||t!==S)throw Error(`Workflow readiness belongs to a stale extension generation.`)},Et=(e,t)=>{let n=Reflect.get(e,t,e);return typeof n==`function`?n.bind(e):n},Dt=new Proxy(f,{get(e,t){return t===`registerTool`?t=>{e.registerTool({...t,execute:async(e,n,r,i,a)=>(await O(r),t.execute(e,n,r,i,a))})}:Et(e,t)}}),{parser:Ot,recoverService:kt,registry:k,runService:At,runTool:jt}=p,Mt=d.spawnDetached??xt,Nt=d.monitorIntervalMs??3e4,Pt=d.followIntervalMs??2e3,A,Ft=!1,j,It=!1,M,Lt=new Map,N=new Map,Rt=new Set,zt=0,Bt=d.cordis?s(d.cordis):void 0,Vt=!1,P=0,F=!1,I,L;d.cordis?.inject([He],e=>{let t=tt(F,P),n=Ue(We(e),{descriptor:{source:o,id:`workflow`,label:`Workflow`,description:`Workflow discovery, launch, inspection, control, and recovery tools.`,order:20,actions:[{id:$e,label:`Activate`,description:`Enable workflow tools for this session.`,contexts:[`tui`,`headless`],parameters:[]},{id:et,label:`Deactivate`,description:`Disable workflow tools without stopping active runs.`,contexts:[`tui`,`headless`],parameters:[]}]},initialState:t,async handleAction(e,t,n){if(await O(),e===$e)return await Bn(!0,n.context),{message:`Workflow mode activated.`};if(e===et)return await Bn(!1,n.context),{message:`Workflow mode deactivated.`};throw Error(`Unknown workflow mode action: ${e}`)}});return L=n,I=JSON.stringify(t),()=>{n.dispose(),L===n&&(L=void 0,I=void 0)}});let R,z,B,Ht=!1,Ut=new Map,V,Wt=new Set,H=new Map,Gt=[],U=new Map,Kt=0,qt=new ie,Jt,Yt,W=e=>(Yt??=Xe({serviceName:`doom-workflow`,packageName:`@agimon-ai/doompi-workflow`,cwd:e?.cwd,env:process.env,enableLogs:!0,enableTraces:!0}),Yt),Xt,Zt=e=>{W(R).recordWarning(`doom_workflow.narration_failed`,e).catch(e=>{process.emitWarning(`Doom-workflow could not record a narration failure: ${String(e)}`)})},Qt={narrate(e){let t=Xt,n=Ke(e);if(!(!t||!n))try{Promise.resolve(t.request(n)).catch(Zt)}catch(e){Zt(e)}}};d.cordis?.inject([Ge],e=>{let t=qe(e);return Xt=t,()=>{Xt===t&&(Xt=void 0)}});let $t=e=>{e&&E()&&(Jt=m(e))},en=()=>{let e=Jt??R?.sessionManager.getSessionId();return e?m(e):void 0},tn=(e,t)=>e.sessionManager.getSessionId().trim()===t,nn=p.createRecoverTool({ownerSessionId:en}),rn=p.createControlTool(),G=new Set,an=e=>{let t=Date.now(),n=W();return G.add(e),n.recordEvent(`doom_workflow.run_started`,{outcome:`started`}),e.then(()=>(G.delete(e),n.recordEvent(`doom_workflow.run_finished`,{duration_ms:Date.now()-t,outcome:`completed`})),r=>(G.delete(e),n.recordError(`doom_workflow.run_failed`,r,{duration_ms:Date.now()-t}))),e},on=(e,t)=>{let n=St(Ot.parseWorkflowFile(e));if(!(!n||n.includes(t)))return`Runner ${JSON.stringify(t)} is not available across this workflow. Compatible runners: ${n.join(`, `)||`none`}.`},sn=ye({activeRunCount:async()=>P,observeSession:$t,rejectRunner:on,runTool:jt,trackPendingRun:an,...d.launchAckPollMs===void 0?{}:{launchAckPollMs:d.launchAckPollMs},...d.launchAckTimeoutMs===void 0?{}:{launchAckTimeoutMs:d.launchAckTimeoutMs},findLaunchedRun:async({sessionId:e,since:t,workflowPath:n})=>{let r=gt(n),i=n=>{if(!pe(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 V?(await V.refresh(),Gt.find(i)):(await D(k)).find(i)},onLateFailure:(e,t)=>{let n=e instanceof Error?e.message:String(e);t.ui.notify(me(`Workflow launch failed after it was reported started.\n${n}`),`warning`)},onLaunch:async e=>(await W(e).recordEvent(it,{outcome:`requested`}),J(e,!1))}),cn=()=>{let e=[...U.values()].sort((e,t)=>Date.parse(e.startedAt)-Date.parse(t.startedAt)||q(e).localeCompare(q(t)));qt.update(e.map(e=>ae(e,H.get(q(e))??[])))},ln=e=>{Kt+=1;let t=q(e);e.stage===b&&!e.stale?U.set(t,e):U.delete(t),cn()},un=e=>{U.clear();for(let t of e)U.set(q(t),t);cn()},dn=(e,t)=>{let n=H.get(e)??[];n.push(t),H.set(e,n)},K,fn=e=>{!E()||e!==R||K||(K=setTimeout(()=>{K=void 0,E()&&e===R&&J(e,!0)},120),K.unref?.())},pn=e=>{H.delete(e)},mn=async e=>{let t=m(e.sessionManager.getSessionId()),n=p.createRunControl({recordFilter:e=>pe(e,t)});V=n;let r=()=>E()&&e===R&&V===n,i=[n.on(`step`,t=>{if(!r())return;let n=q(t.record);dn(n,{at:t.at,job:t.job,status:t.status,step:t.step,type:`step`,...t.reason===void 0?{}:{reason:t.reason}}),ln(t.record),fn(e)}),n.on(`job`,t=>{if(!r())return;let n=q(t.record);dn(n,{at:t.at,job:t.job,status:t.status,type:`job`,...t.index===void 0?{}:{index:t.index},...t.reason===void 0?{}:{reason:t.reason},...t.total===void 0?{}:{total:t.total}}),ln(t.record),fn(e)}),n.on(`runStarted`,t=>{r()&&(Gt.unshift(t.record),Gt.length>32&&(Gt.length=32),ln(t.record),fn(e))}),n.on(`runUpdated`,t=>{r()&&(ln(t.record),J(e,!0))}),n.on(`runFinished`,t=>{r()&&(ln(t.record),J(e,!0))})];for(let e of i)Wt.add(e);try{await n.start()}catch(e){for(let e of i)Wt.delete(e),e();throw n.dispose(),V===n&&(V=void 0),e}if(!E()||e!==R||V!==n){for(let e of i)Wt.delete(e),e();n.dispose(),V===n&&(V=void 0)}},hn=()=>{K&&clearTimeout(K),K=void 0;for(let e of Wt)e();Wt.clear(),V?.dispose(),V=void 0,H.clear(),Kt+=1,U.clear(),cn()},gn=()=>{z?.teardown(),z?.handle?.hide(),z=void 0},q=e=>e.runId??`${e.workspace}/${e.runKey}`,_n=()=>{let e=L;if(!e)return;let t=tt(F,P),n=JSON.stringify(t);n!==I&&(I=n,e.publish(t))},J=async(e,t)=>{if(!E()||e!==R)return;let n=W(e);if(Ft){n.recordEvent(`doom_workflow.monitor_skipped`,{outcome:`already_running`});return}let r=Date.now(),i=Kt;Ft=!0;try{let a=await D(k);if(!E()||e!==R)return;let o=m(e.sessionManager.getSessionId()),s=tn(e,o),l=a.filter(e=>pe(e,o)),ee=l.filter(e=>e.stage===b&&!e.stale),ne=l.filter(e=>e.stage===C);i===Kt&&un(ee),n.recordEvent(`doom_workflow.monitor_counts`,{"workflow.run_count":l.length,"workflow.running_count":ee.length,"workflow.failed_count":ne.length,mode:F?`on`:`off`});let u=he(a,o).filter(e=>!e.stale);P=u.length,_n(),Bt?.update(u.map(e=>({id:e.runId??e.runKey,sessionId:o}))),Sn(new Set(l.map(e=>q(e))));let re=new Map(l.map(e=>[q(e),e.stage]));for(let e of l){let n=q(e),r=Lt.get(n),i=Date.parse(e.startedAt),a=r===void 0&&(zt===0||!Number.isFinite(i)||i>=zt);Vt&&c(Qt,e,r===b||r===x||r===C?r:void 0,a),!(!t||!s||e.stage===b)&&!Rt.has(n)&&(r===b||a)&&N.set(n,e)}let ie=new Set([...l.filter(e=>e.stage===b&&!e.stale).map(e=>q(e)),...N.keys()]);for(let e of H.keys())ie.has(e)||pn(e);if(t)for(let t of l){if(Lt.get(q(t))!==b||t.stage===b)continue;let r=t.stage===x,i=t.failedJob?` at ${t.failedJob}`:``;e.ui.notify(r?`${T.completed} ${t.runKey} completed`:`${T.failed} ${t.runKey} ${dt(t)}${i}`,r?`info`:`warning`),n.recordEvent(`doom_workflow.run_transition`,{outcome:r?`completed`:dt(t)})}if(s&&N.size>0)try{let e=[...N.values()],t=await Promise.all(e.map(async e=>{if(e.stage===x)return fe(e,[]);let t=q(e),n=H.get(t)??ut(e,await Fe(k.runDirectoryFor(e)));return fe(e,Ie(n))}));f.sendMessage({customType:te,content:t.join(`
2
2
 
3
- `),display:!0,details:{runIds:e.map(e=>e.runId??Y(e)),runs:e.map(e=>({runKey:e.runKey,workspace:e.workspace,stage:e.stage,...e.workflowId?{workflowId:e.workflowId}:{},...e.failedJob?{failedJob:e.failedJob}:{},...e.errorMessage?{error:e.errorMessage}:{}}))}},{triggerTurn:!0,deliverAs:`steer`});for(let t of e){let e=Y(t);Pt.add(e),I.delete(e),un(e)}}catch(e){await n.recordError(`doom_workflow.terminal_delivery_failed`,e,{"workflow.pending_terminal_count":I.size})}Nt=re,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.notify(`Workflow status refresh failed: ${t instanceof Error?t.message:String(t)}`,`warning`)}finally{jt=!1}},hn=e=>{P&&clearInterval(P),P=void 0,F=void 0,e.ui.setWidget(Je,void 0)},gn=new Map,_n=(e,t,n)=>{let r=e.launcher;if(!r||r.type!==O||ft(e)||t<40||n<10)return;let i=Y(e),a=`${t}x${n}`;if(gn.get(i)===a)return;gn.set(i,a);let o=r.windowId??r.paneId??r.sessionId??r.sessionName;f.exec(O,[`resize-window`,`-t`,o,`-x`,String(t),`-y`,String(n)],{timeout:b})},vn=async e=>{let t=await f.exec(k,[`rpc`,`terminal.replay`,JSON.stringify({workspace_id:e})],{timeout:b});if(t.code!==0)return;let n;try{n=JSON.parse(t.stdout)}catch{return}let r=ee.safeParse(n);if(!r.success)return;let i=u(r.data.render_grid,24);return i.length>0?i:void 0},yn=async e=>{let t=e.launcher;if(!t)return[pt];if(ft(e))return[dt];if(t.type===O){let n=t.paneId??t.sessionId??t.sessionName,r=await f.exec(O,[`capture-pane`,`-p`,`-e`,`-t`,n,`-S`,`-24`],{timeout:b});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===ut)return[`This run is hosted natively and has no launcher screen to read.`];let n=await vn(t.workspaceId);if(n)return n;let r=await f.exec(k,[`read-screen`,`--workspace`,t.workspaceId,`--scrollback`,`--lines`,`24`],{timeout:b});return r.code===0?r.stdout.trimEnd().split(`
5
- `).slice(-24):[`Unable to read ${e.runKey}: ${r.stderr.trim()||`cmux exited ${r.code}`}`]},Z=new Map,bn=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=yn(e);try{n.lines=await n.inFlight}finally{n.at=Date.now(),n.inFlight=void 0}return n.lines},xn=e=>{for(let t of Z.keys())e.has(t)||Z.delete(t);for(let t of gn.keys())e.has(t)||gn.delete(t)},Sn=async e=>{let t=e.launcher;if(!t)return pt;if(ft(e))return dt;if(t.type===ut)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 f.exec(k,[`select-workspace`,`--workspace`,t.workspaceId],{timeout:b}),`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 f.exec(O,[`switch-client`,`-t`,n],{timeout:b});return r.code===0?(t.paneId&&await f.exec(O,[`select-pane`,`-t`,t.paneId],{timeout:b}),`Opened ${e.runKey} in tmux.`):r.stderr.trim()||`Unable to open tmux session ${n}.`},Cn=async(e,t)=>{if(!Mt){Mt=!0;try{let n=Ke(at(e,await Ge(M.runDirectoryFor(e)))),r=n.length>0?void 0:await bn(e);t.ui.setWidget(Je,(t,i)=>({render:t=>rt(e,r??it(n,i,t),i,t),invalidate:()=>{}}),{placement:`aboveEditor`})}finally{Mt=!1}}},wn=async(e,t)=>{hn(t),F=e,await Cn(e,t),P=setInterval(()=>{F&&Cn(F,t)},At),P.unref?.()},Tn=async(e,t)=>{let n=e.launcher;if(!(!n||n.type===ut||ft(e))){if(n.type===O){let e=n.paneId??n.sessionId??n.sessionName;await f.exec(O,[`send-keys`,`-t`,e,`-l`,t],{timeout:b});return}await f.exec(k,[`send`,`--workspace`,n.workspaceId,t],{timeout:b})}},En=async(e,t)=>{pn();let n=!ft(e)&&(e.launcher?.type===O||e.launcher?.type===k),r=new l(t=>Tn(e,t)),i=[],a=[],o,s=!1,c=async()=>{i=Ke(at(e,await Ge(M.runDirectoryFor(e)))),a=await bn(e)};await c();let ee=()=>{o&&clearInterval(o),o=void 0,r.dispose()},u={interactive:n,runKey:e.runKey,teardown:ee};V=u;let te=()=>{pn(),t.ui.notify(`Closed the ${e.runKey} view. The run keeps going.`,`info`)};return t.ui.custom((l,ne,re,ie)=>(o=setInterval(()=>{s||(s=!0,c().finally(()=>{s=!1,l.requestRender()}))},200),o.unref?.(),new le(l,ne,{runKey:e.runKey,label:`${E.running} ${xt(e)}`,breadcrumb:`SPC › w / workflows › run`,interactive:n,footer:tt(n),hints:nt(n),snapshot:e=>({progress:it(i,ne,e),output:a}),onViewport:(t,n)=>_n(e,t,n),onUnfollow:()=>{u.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:_,closeShortcut:v,onDispose:()=>{ee(),V===u&&(V=void 0),ie()}})),{...ue(n),onHandle:e=>{u.handle=e}}),t.ui.notify(n?`${e.runKey}: typing goes to the run. Press Esc twice to close, ${y(_)} to type here instead.`:`${e.runKey}: view only, because this run is hosted natively. ${y(v)} 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.`,`${y(_)} moves typing between the run and this session,`,`and ${y(v)} 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 ${y(v)} closes the view.`].join(` `)},Dn=async(e,t=!1)=>{if(!(zt||V||F)){zt=!0;try{let n=await e.ui.custom((e,t,n,r)=>new c(e,t,{list:async()=>{let e=Zt(),t=(await j(M)).filter(t=>t.stage===x&&!t.stale&&me(t,e));return Bt=new Map(t.map(e=>[Y(e),e])),Promise.all(t.map(async e=>({displayName:e.displayName,executionState:e.executionState,jobs:Ke(at(e,await Ge(M.runDirectoryFor(e)))),key:Y(e),output:[],runKey:e.runKey,startedAt:e.startedAt,workspace:e.workspace})))},output:async e=>{let t=Bt.get(e.key);return t?bn(t):[`Workflow is no longer active.`]}},r),{...qe,onHandle:e=>{H=e}});if(n){let r=await Q(n);t?await Mn(r,e):await En(r,e)}}finally{H=void 0,zt=!1,Bt.clear()}}},Q=async(e,t=Zt())=>{Xt(t);let n=(await j(M,e.workspace)).filter(n=>n.runKey===e.runKey&&me(n,t));if(n.length>1)throw Error(ve(`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(ve(`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 j(M,e.workspace)).filter(t=>t.runKey===e.runKey&&t.stage===w);if(t.length>1)throw Error(ve(`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(ve(`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},kn=async e=>{let t=M.runDirectoryFor(e),n=[Oe(t,`changelog.md`),Oe(t,`context.md`),Oe(t,`progress.ndjson`)],r=[];for(let e of n)try{let t=await Pe(e,`utf-8`);r.push(`--- ${Ee(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.`]},An=async(e,t)=>{let n=await On(e);return await K(t).recordEvent(`doom_workflow.recovery_evidence_requested`,{outcome:`requested`}),[`Workflow: ${xt(n)}`,`--- run.json ---`,JSON.stringify(n,null,2),...await kn(n)].join(`
6
- `)},jn=async(e,t)=>{let n=vt();if(!n)return;let r=await On(e),i;try{i=wt.parseWorkflowFile(r.workflowPath)}catch{return}if(!i[`launch-command`])return;let a=m(t.sessionManager.getSessionId()),o=await M.claimRunRecovery(r.workspace,r.runKey,a),s=r.originalRepoPath??t.cwd,c=_t([process.execPath,n,`recover-workflow`,r.runKey,`--workspace`,r.workspace,`--recovery-claim`,o.claimId,...e.runner?[`--runner`,e.runner]:[]]),l=gt();try{await Ot(process.execPath,[n,`launch-process`,...l?[`--launcher`,l]:[],`--name`,`${r.displayName} recover`,`--cwd`,s,`--command`,c],{cwd:s,env:ht()})}catch(e){throw await M.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.`},Mn=async(e,t)=>{let n=e;for(;;){let e=[`Open output`],r=n.executionState??x;r===x&&e.push(`Pause`),r===`paused`&&e.push(`Resume`),n.stage===x&&e.push(`Stop`),e.push(`Back`);let i=await se(t,{title:`Manage ${n.runKey}`,breadcrumb:`SPC › w / workflows › r / runs`,choices:e});if(!i||i===`Back`)return;if(n=await Q(n),n.stage!==x){t.ui.notify(`${n.runKey} is no longer running.`,`warning`);return}if(i===`Open output`){await En(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!==x){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 en.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(_e(a)||`Unable to ${i.toLowerCase()} workflow.`);t.ui.notify(_e(a)||`${i} requested for ${n.runKey}.`,`info`),await X(t,!1),n=await Q(n)}},Nn=(e,t)=>({editor:(t,n)=>e.ui.editor(t,n),input:(n,r)=>ce(e,{title:n,breadcrumb:t,...r?{fallback:r}:{}}),notify:(t,n)=>e.ui.notify(t,n),select:(n,r)=>se(e,{title:n,breadcrumb:t,choices:r})}),Pn=async e=>{if(!e.hasUI){e.ui.notify(`The workflow list needs an interactive Pi session.`,`warning`);return}let t=await Ce(p.listWorkflowsTool,e.cwd),n=new Map(t.map(e=>[e.path,e])),r={compatibleRunners:bt,launch:(e,t)=>rn.execute(e,t),parseWorkflow:e=>wt.parseWorkflowFile(e),ui:Nn(e,`SPC › w / workflows › l / list`)};await re(e,t.map(e=>({key:e.path,name:e.name,relativePath:e.relativePath,description:e.description,tags:e.tags})),{loadDetail:e=>we(e.key,r),launchWorkflow:t=>{let i=n.get(t.key);i&&Se(r,i,e).then(t=>{t&&e.ui.notify(he(_e(t)),`info`)},t=>{e.ui.notify(t instanceof Error?t.message:String(t),`error`)})}})},Fn=async e=>{let t=(await j(M)).filter(e=>e.stage===w);if(t.length===0){e.ui.notify(`No failed workflows are available to recover.`,`info`);return}let n=await ie(e,{title:`WORKFLOW RECOVERY`,breadcrumb:`SPC › w / workflows › c / 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!==w)throw Error(`${r.runKey} is no longer failed.`);let i=Oe(De(ke(import.meta.url)),et,`workflow-recovery`,`SKILL.md`);if(!Te(i))throw Error(`Workflow recovery skill is unavailable at ${i}.`);In(!0),e.ui.notify(`Workflow tools enabled. The agent will investigate this failure and choose the safe next step.`,`info`),f.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`})},In=e=>{if(!D())return;let t=new Set(f.getActiveTools());for(let n of ye)e?t.add(n):t.delete(n);f.setActiveTools([...t]),R=e,mn(),d.onModeChange?.(e)},Ln=()=>R?[`${E.completed} Workflow mode on`,`Ask for workflows in plain language: the agent can list, launch, inspect, pause, resume, stop, and recover runs.`,`${y(_)} stops following a run without stopping it.`].join(`
3
+ `),display:!0,details:{runIds:e.map(e=>e.runId??q(e)),runs:e.map(e=>({runKey:e.runKey,workspace:e.workspace,stage:e.stage,...e.workflowId?{workflowId:e.workflowId}:{},...e.failedJob?{failedJob:e.failedJob}:{},...e.errorMessage?{error:e.errorMessage}:{}}))}},{triggerTurn:!0,deliverAs:`steer`});for(let t of e){let e=q(t);Rt.add(e),N.delete(e),pn(e)}}catch(e){await n.recordError(`doom_workflow.terminal_delivery_failed`,e,{"workflow.pending_terminal_count":N.size})}Lt=re,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.notify(`Workflow status refresh failed: ${t instanceof Error?t.message:String(t)}`,`warning`)}finally{Ft=!1}},Y=e=>{j&&clearInterval(j),j=void 0,M=void 0,e.ui.setWidget(Qe,void 0)},X=new Ne({run:async(e,t,n)=>{let r=await f.exec(e,[...t],{timeout:n.timeoutMs});return{code:r.code,stdout:r.stdout,stderr:r.stderr}},lines:24,timeoutMs:rt}),vn=e({terminal:X,now:()=>Date.now(),refreshMs:750}),yn=new Map,bn=(e,t,n)=>{let r=q(e),i=`${t}x${n}`;yn.get(r)!==i&&(yn.set(r,i),X.resize(e,t,n))},xn=e=>X.targetsCurrentTerminal(e)?Promise.resolve([ht]):vn.screen(q(e),e,24),Sn=e=>{vn.forget(e);for(let t of yn.keys())e.has(t)||yn.delete(t)},Cn=async e=>{let t=e.launcher;if(!t)return`No durable launcher identity is recorded for this run.`;if(X.targetsCurrentTerminal(e))return ht;if(t.type===`native`)return`This run is hosted natively rather than in a multiplexer, so there is no separate window to bring forward.`;if(t.type===mt)return await f.exec(mt,[`select-workspace`,`--workspace`,t.workspaceId],{timeout:rt}),`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 f.exec(pt,[`switch-client`,`-t`,n],{timeout:rt});return r.code===0?(t.paneId&&await f.exec(pt,[`select-pane`,`-t`,t.paneId],{timeout:rt}),`Opened ${e.runKey} in tmux.`):r.stderr.trim()||`Unable to open tmux session ${n}.`},wn=async(e,t)=>{if(!It){It=!0;try{let n=Ie(ut(e,await Fe(k.runDirectoryFor(e)))),r=n.length>0?void 0:await xn(e);t.ui.setWidget(Qe,(t,i)=>({render:t=>ct(e,r??lt(n,i,t),i,t),invalidate:()=>{}}),{placement:`aboveEditor`})}finally{It=!1}}},Tn=async(e,t)=>{Y(t),M=e,await wn(e,t),j=setInterval(()=>{M&&wn(M,t)},Pt),j.unref?.()},En=async(e,t)=>{X.capabilities(e).writable&&await X.write(e,t)},Dn=async(e,t)=>{gn();let n=X.capabilities(e).writable,r=new ee(t=>En(e,t)),i=[],a=[],o,s=!1,c=async()=>{i=Ie(ut(e,await Fe(k.runDirectoryFor(e)))),a=await xn(e)};await c();let l=()=>{o&&clearInterval(o),o=void 0,r.dispose()},te={interactive:n,runKey:e.runKey,teardown:l};z=te;let ne=()=>{gn(),t.ui.notify(`Closed the ${e.runKey} view. The run keeps going.`,`info`)};return t.ui.custom((ee,u,re,ie)=>(o=setInterval(()=>{s||(s=!0,c().finally(()=>{s=!1,ee.requestRender()}))},200),o.unref?.(),new ce(ee,u,{runKey:e.runKey,label:`${T.running} ${Ct(e)}`,breadcrumb:`SPC › w / workflows › run`,interactive:n,footer:ot(n),hints:st(n),snapshot:e=>({progress:lt(i,u,e),output:a}),onViewport:(t,n)=>bn(e,t,n),onUnfollow:()=>{te.handle?.unfocus(),t.ui.notify(`Typing goes to this session. The run keeps going.`,`info`)},onClose:ne,sendInput:e=>r.write(e),flushInput:()=>void r.flush(),unfollowShortcut:_,closeShortcut:v,onDispose:()=>{l(),z===te&&(z=void 0),ie()}})),{...le(n),onHandle:e=>{te.handle=e}}),t.ui.notify(n?`${e.runKey}: typing goes to the run. Press Esc twice to close, ${y(_)} to type here instead.`:`${e.runKey}: view only, because this run is hosted natively. ${y(v)} 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.`,`${y(_)} moves typing between the run and this session,`,`and ${y(v)} 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 ${y(v)} closes the view.`].join(` `)},On=async(e,t=!1)=>{if(!(Ht||z||M)){Ht=!0;try{let n=await e.ui.custom((e,t,n,r)=>new l(e,t,{list:async()=>{let e=en(),t=(await D(k)).filter(t=>t.stage===b&&!t.stale&&pe(t,e));return Ut=new Map(t.map(e=>[q(e),e])),Promise.all(t.map(async e=>({displayName:e.displayName,executionState:e.executionState,jobs:Ie(ut(e,await Fe(k.runDirectoryFor(e)))),key:q(e),output:[],runKey:e.runKey,startedAt:e.startedAt,workspace:e.workspace})))},output:async e=>{let t=Ut.get(e.key);return t?xn(t):[`Workflow is no longer active.`]}},r),{...Ze,onHandle:e=>{B=e}});if(n){let r=await Z(n);t?await Nn(r,e):await Dn(r,e)}}finally{B=void 0,Ht=!1,Ut.clear()}}},Z=async(e,t=en())=>{$t(t);let n=(await D(k,e.workspace)).filter(n=>n.runKey===e.runKey&&pe(n,t));if(n.length>1)throw Error(_e(`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(_e(`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},kn=async e=>{let t=(await D(k,e.workspace)).filter(t=>t.runKey===e.runKey&&t.stage===C);if(t.length>1)throw Error(_e(`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(_e(`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},An=async e=>{let t=k.runDirectoryFor(e),n=[Me(t,`changelog.md`),Me(t,`context.md`),Me(t,`progress.ndjson`)],r=[];for(let e of n)try{let t=await Ve(e,`utf-8`);r.push(`--- ${Ae(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.`]},jn=async(e,t)=>{let n=await kn(e);return await W(t).recordEvent(`doom_workflow.recovery_evidence_requested`,{outcome:`requested`}),[`Workflow: ${Ct(n)}`,`--- run.json ---`,JSON.stringify(n,null,2),...await An(n)].join(`
4
+ `)},Mn=async(e,t)=>{let n=bt();if(!n)return;let r=await kn(e),i;try{i=Ot.parseWorkflowFile(r.workflowPath)}catch{return}if(!i[`launch-command`])return;let a=m(t.sessionManager.getSessionId()),o=await k.claimRunRecovery(r.workspace,r.runKey,a),s=r.originalRepoPath??t.cwd,c=yt([process.execPath,n,`recover-workflow`,r.runKey,`--workspace`,r.workspace,`--recovery-claim`,o.claimId,...e.runner?[`--runner`,e.runner]:[]]),l=vt();try{await Mt(process.execPath,[n,`launch-process`,...l?[`--launcher`,l]:[],`--name`,`${r.displayName} recover`,`--cwd`,s,`--command`,c],{cwd:s,env:_t()})}catch(e){throw await k.releaseRunRecoveryClaim(r.workspace,r.runKey,o.claimId),e}return await W(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.`},Nn=async(e,t)=>{let n=e;for(;;){let e=[`Open output`],r=n.executionState??b;r===b&&e.push(`Pause`),r===`paused`&&e.push(`Resume`),n.stage===b&&e.push(`Stop`),e.push(`Back`);let i=await oe(t,{title:`Manage ${n.runKey}`,breadcrumb:`SPC › w / workflows › r / runs`,choices:e});if(!i||i===`Back`)return;if(n=await Z(n),n.stage!==b){t.ui.notify(`${n.runKey} is no longer running.`,`warning`);return}if(i===`Open output`){await Dn(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 Z(n),n.stage!==b){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 rn.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(ge(a)||`Unable to ${i.toLowerCase()} workflow.`);t.ui.notify(ge(a)||`${i} requested for ${n.runKey}.`,`info`),await J(t,!1),n=await Z(n)}},Pn=(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})}),Fn=async e=>{if(!e.hasUI){e.ui.notify(`The workflow list needs an interactive Pi session.`,`warning`);return}let t=await Se(p.listWorkflowsTool,e.cwd),n=new Map(t.map(e=>[e.path,e])),r={compatibleRunners:St,launch:(e,t)=>sn.execute(e,t),parseWorkflow:e=>Ot.parseWorkflowFile(e),ui:Pn(e,`SPC › w / workflows › l / list`)};await u(e,t.map(e=>({key:e.path,name:e.name,relativePath:e.relativePath,description:e.description,tags:e.tags})),{loadDetail:e=>Ce(e.key,r),launchWorkflow:t=>{let i=n.get(t.key);i&&xe(r,i,e).then(t=>{t&&e.ui.notify(me(ge(t)),`info`)},t=>{e.ui.notify(t instanceof Error?t.message:String(t),`error`)})}})},In=async(e,t)=>{let n=Te(e);if(we(n)){t.ui.notify(n.error,`error`);return}let r=await Se(p.listWorkflowsTool,t.cwd),i=Ee(r,n.workflow);if(!i){t.ui.notify(`No workflow matches '${n.workflow}' under ${t.cwd}.`,`error`);return}let a=Ce(i.path,{compatibleRunners:St,parseWorkflow:e=>Ot.parseWorkflowFile(e)});if(a.error!==void 0){t.ui.notify(`${i.name} could not be read: ${a.error}`,`error`);return}let o=De(a,n);if(o.length>0){t.ui.notify(o.join(` `),`error`);return}let s=Ot.parseWorkflowFile(i.path),c={workflowPath:i.path,...n.runner===void 0?{}:{runner:n.runner},...typeof s.workspace==`string`?{workspace:s.workspace}:{},...n.prompt===void 0?{}:{prompt:n.prompt},...Object.keys(n.inputs).length===0?{}:{inputs:n.inputs}},l=await sn.execute(c,t);t.ui.notify(me(ge(l)),`info`)};f.registerCommand(`workflow-launch`,{description:`Launch a workflow: /workflow-launch <workflow> [runner=x] [key=value …] [prompt]`,handler:async(e,t)=>{await O();try{await In(e,t)}catch(e){t.ui.notify(e instanceof Error?e.message:String(e),`error`)}}});let Ln=async e=>{let t=(await D(k)).filter(e=>e.stage===C);if(t.length===0){e.ui.notify(`No failed workflows are available to recover.`,`info`);return}let n=await re(e,{title:`WORKFLOW RECOVERY`,breadcrumb:`SPC › w / workflows › c / 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 kn(n);if(r.stage!==C)throw Error(`${r.runKey} is no longer failed.`);let i=Me(je(Le(import.meta.url)),at,`workflow-recovery`,`SKILL.md`);if(!ke(i))throw Error(`Workflow recovery skill is unavailable at ${i}.`);Rn(!0),e.ui.notify(`Workflow tools enabled. The agent will investigate this failure and choose the safe next step.`,`info`),f.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(`
5
+ `),{deliverAs:`followUp`})},Rn=e=>{if(!E())return;let t=new Set(f.getActiveTools());for(let n of ve)e?t.add(n):t.delete(n);f.setActiveTools([...t]),F=e,_n(),d.onModeChange?.(e)},zn=()=>F?[`${T.completed} Workflow mode on`,`Ask for workflows in plain language: the agent can list, launch, inspect, pause, resume, stop, and recover runs.`,`${y(_)} stops following a run without stopping it.`].join(`
8
6
  `):[`Workflow mode off`,`Press SPC w e to give the agent its workflow tools. Runs already going are unaffected.`].join(`
9
- `),Rn=async(e,t)=>{In(e),R||hn(t),t.ui.notify(Ln(),`info`),await X(t,!1)};f.registerShortcut(_,{description:`Open workflows or move typing between a workflow run and this session`,handler:async e=>{await A();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(F){hn(e),e.ui.notify(`Workflow continues in the background.`,`info`);return}await Dn(e)}}),f.registerShortcut(v,{description:`Close the workflow run view`,handler:async e=>{if(await A(),!V)return;let t=V.runKey;pn(),e.ui.notify(`Closed the ${t} view. The run keeps going.`,`info`)}}),f.on(`resources_discover`,()=>{if(!R)return{skillPaths:[]};let e=Oe(De(ke(import.meta.url)),et);return{skillPaths:Te(e)?[e]:[]}}),ne(f),f.registerMessageRenderer(`workflow-step`,(e,{outputPad:t},n)=>{let r=de(e.details)?e.details:void 0,i=typeof e.content==`string`?e.content:JSON.stringify(e.content);return fe(r,i,t,n)}),xe(Ct,{runTool:Dt,recoverTool:$t,launchExecutor:rn,controlTool:en,requireSessionRun:Q,requireRecoverableRun:On,readRecoveryEvidence:An,delegateRecovery:jn,trackPendingRun:tn,observeSession:Xt,activeRunCount:async()=>L,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 wn(n,t),`Following ${xt(n)}. Press ${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: ${xt(n)}`,`Stage: ${ot(n)}`,`Raw launcher output is hidden from chat. Use action follow or open to view it.`].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?En(n,t):Sn(n)},rejectRunner:nn});let zn;d.cordis?.inject([Ae],o=>{let s=je(o).registerLeaderActions({source:a,handlers:{[n]:async e=>(await A(),Rn(!0,e)),[t]:async e=>(await A(),Rn(!1,e)),[r]:async e=>(await A(),Dn(e,!0)),[e]:async e=>(await A(),Pn(e)),[i]:async e=>(await A(),Fn(e))},onError:(e,t,n)=>{n.ui.notify(e instanceof Error?e.message:String(e),`error`)}});return zn=s,()=>{s(),zn===s&&(zn=void 0)}}),f.on(`session_start`,(e,t)=>{if(!h||!st())return;let n=++g,r=C;Qe?.abort();let i=new AbortController;Qe=i,B=t;let o=()=>h&&n===g&&B===t&&st();fn(),N&&clearInterval(N),N=void 0,It?.update([]),hn(t),H?.hide(),H=void 0,zt=!1,Bt.clear(),pn(),Z.clear(),Wt.setUICtx(t.ui),K(t).recordEvent(`doom_workflow.session_started`,{mode:process.env.WORKFLOW_MCP_MODE===`on`?`on`:`off`,outcome:`started`});let s=t.sessionManager.getSessionId(),c=m(s);Xt(c),Ft=Date.now(),Lt=!1,Nt=new Map,I.clear(),Pt.clear(),In(d.initialMode??process.env.WORKFLOW_MCP_MODE===`on`);let l=lt(t);C=(async()=>{if(r&&await Promise.allSettled([r.then(e=>e.wait())]),!o())throw Error(`Workflow session initialization was superseded.`);return l.start(a,`${Me}:${n}`,async e=>{let n=()=>i.signal.aborted||!o();return e.throwIfAborted(),n()||(await dn(t),e.throwIfAborted(),n())||(await X(t,!1),e.throwIfAborted(),n())?{value:void 0}:(Lt=!0,N=setInterval(()=>{o()&&X(t,!0)},kt),N.unref?.(),{value:void 0})})})(),Promise.allSettled([C.then(e=>e.wait())])});let $=async(e,t)=>{try{await t()}catch(t){process.emitWarning(`Doom-workflow cleanup could not ${e}: ${String(t)}`)}},Bn;return{dispose:(e=B)=>(Bn??=(async()=>{if(!h)return;h=!1,g+=1;let t=C;C=void 0,Qe?.abort(),Qe=void 0,await $(`stop the workflow observer`,fn);let n=T;if(T=void 0,n?await $(`dispose standalone workflow readiness`,()=>n.dispose()):t&&await Promise.allSettled([t.then(e=>e.wait())]),q.size>0){for(let e of[Et,Tt])await $(`interrupt an inline workflow`,()=>e.interrupt(`SIGTERM`,{phase:`workflow`,source:`pi-session-shutdown`}));await Promise.race([Promise.allSettled(q),new Promise(e=>setTimeout(e,1e4).unref?.())])}N&&clearInterval(N),N=void 0,J&&clearTimeout(J),J=void 0;let r=It;It=void 0,r&&await $(`unregister the background-work provider`,()=>r.dispose()),Lt=!1,H?.hide(),H=void 0,zt=!1,Bt.clear(),Z.clear(),pn(),L=0,e?hn(e):P&&clearInterval(P),P=void 0,F=void 0,Wt.dispose(),R=!1,Rt=void 0;let i=z;z=void 0,i&&(await $(`publish the inactive workflow mode`,()=>i.publish(Ze(!1,0))),await $(`dispose the workflow mode owner`,()=>i.dispose()));let a=zn;zn=void 0,a&&await $(`dispose workflow leader actions`,a);let o=q.size,s=Kt;Kt=void 0,s&&(await $(`record the workflow session finish`,()=>s.recordEvent(`doom_workflow.session_finished`,{"workflow.pending_count":o,outcome:o===0?`completed`:`interrupted`})),await $(`stop workflow telemetry`,()=>s.shutdown())),q.clear(),I.clear(),Pt.clear(),Ht.length=0,Gt=void 0,B=void 0})(),Bn)}}}export{bt as compatibleRunners,St as installWorkflowPiRuntime,tt as panelHint,nt as panelHints,y as shortcutLabel};
7
+ `),Bn=async(e,t)=>{Rn(e),F||Y(t),t.ui.notify(zn(),`info`),await J(t,!1)};f.registerShortcut(_,{description:`Open workflows or move typing between a workflow run and this session`,handler:async e=>{await O();let t=z?.handle;if(t){if(!z?.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 ${z?.runKey}.`,`info`));return}if(M){Y(e),e.ui.notify(`Workflow continues in the background.`,`info`);return}await On(e)}}),f.registerShortcut(v,{description:`Close the workflow run view`,handler:async e=>{if(await O(),!z)return;let t=z.runKey;gn(),e.ui.notify(`Closed the ${t} view. The run keeps going.`,`info`)}}),f.on(`resources_discover`,()=>{if(!F)return{skillPaths:[]};let e=Me(je(Le(import.meta.url)),at);return{skillPaths:ke(e)?[e]:[]}}),ne(f),f.registerMessageRenderer(`workflow-step`,(e,{outputPad:t},n)=>{let r=ue(e.details)?e.details:void 0,i=typeof e.content==`string`?e.content:JSON.stringify(e.content);return de(r,i,t,n)}),be(Dt,{runTool:jt,recoverTool:nn,launchExecutor:sn,controlTool:rn,requireSessionRun:Z,requireRecoverableRun:kn,readRecoveryEvidence:jn,delegateRecovery:Mn,trackPendingRun:an,observeSession:$t,activeRunCount:async()=>P,onLaunch:async e=>(await W(e).recordEvent(it,{outcome:`requested`}),J(e,!1)),followRun:async(e,t)=>{let n=await Z(e,t.sessionManager.getSessionId());return await W(t).recordEvent(`doom_workflow.follow_started`,{outcome:`started`}),await Tn(n,t),`Following ${Ct(n)}. Press ${y(_)} to stop following; the run keeps going.`},tailRun:async(e,t)=>{let n=await Z(e,t.sessionManager.getSessionId());return await W(t).recordEvent(`doom_workflow.tail_requested`,{outcome:`requested`}),[`Workflow: ${Ct(n)}`,`Stage: ${dt(n)}`,`Raw launcher output is hidden from chat. Use action follow or open to view it.`].join(`
8
+ `)},openRun:async(e,t)=>{let n=await Z(e,t.sessionManager.getSessionId());return await W(t).recordEvent(`doom_workflow.open_requested`,{outcome:`requested`}),t.hasUI?Dn(n,t):Cn(n)},rejectRunner:on});let Q;d.cordis?.inject([Re],e=>{let s=ze(e).registerLeaderActions({source:o,handlers:{[r]:async e=>(await O(),Bn(!0,e)),[n]:async e=>(await O(),Bn(!1,e)),[i]:async e=>(await O(),On(e,!0)),[t]:async e=>(await O(),Fn(e)),[a]:async e=>(await O(),Ln(e))},onError:(e,t,n)=>{n.ui.notify(e instanceof Error?e.message:String(e),`error`)}});return Q=s,()=>{s(),Q===s&&(Q=void 0)}}),f.on(`session_start`,(e,t)=>{if(!h||!ft())return;let n=++g,r=S;nt?.abort();let i=new AbortController;nt=i,R=t;let a=()=>h&&n===g&&R===t&&ft();hn(),A&&clearInterval(A),A=void 0,Bt?.update([]),Y(t),B?.hide(),B=void 0,Ht=!1,Ut.clear(),gn(),vn.forget(new Set),qt.setUICtx(t.ui),W(t).recordEvent(`doom_workflow.session_started`,{mode:process.env.WORKFLOW_MCP_MODE===`on`?`on`:`off`,outcome:`started`});let s=t.sessionManager.getSessionId(),c=m(s);$t(c),zt=Date.now(),Vt=!1,Lt=new Map,N.clear(),Rt.clear(),Rn(d.initialMode??process.env.WORKFLOW_MCP_MODE===`on`);let l=Tt(t);S=(async()=>{if(r&&await Promise.allSettled([r.then(e=>e.wait())]),!a())throw Error(`Workflow session initialization was superseded.`);return l.start(o,`${Be}:${n}`,async e=>{let n=()=>i.signal.aborted||!a();return e.throwIfAborted(),n()||(await mn(t),e.throwIfAborted(),n())||(await J(t,!1),e.throwIfAborted(),n())?{value:void 0}:(Vt=!0,A=setInterval(()=>{a()&&J(t,!0)},Nt),A.unref?.(),{value:void 0})})})(),Promise.allSettled([S.then(e=>e.wait())])});let $=async(e,t)=>{try{await t()}catch(t){process.emitWarning(`Doom-workflow cleanup could not ${e}: ${String(t)}`)}},Vn;return{dispose:(e=R)=>(Vn??=(async()=>{if(!h)return;h=!1,g+=1;let t=S;S=void 0,nt?.abort(),nt=void 0,await $(`stop the workflow observer`,hn);let n=w;if(w=void 0,n?await $(`dispose standalone workflow readiness`,()=>n.dispose()):t&&await Promise.allSettled([t.then(e=>e.wait())]),G.size>0){for(let e of[At,kt])await $(`interrupt an inline workflow`,()=>e.interrupt(`SIGTERM`,{phase:`workflow`,source:`pi-session-shutdown`}));await Promise.race([Promise.allSettled(G),new Promise(e=>setTimeout(e,1e4).unref?.())])}A&&clearInterval(A),A=void 0,K&&clearTimeout(K),K=void 0;let r=Bt;Bt=void 0,r&&await $(`unregister the background-work provider`,()=>r.dispose()),Vt=!1,B?.hide(),B=void 0,Ht=!1,Ut.clear(),vn.forget(new Set),gn(),P=0,e?Y(e):j&&clearInterval(j),j=void 0,M=void 0,qt.dispose(),F=!1,I=void 0;let i=L;L=void 0,i&&(await $(`publish the inactive workflow mode`,()=>i.publish(tt(!1,0))),await $(`dispose the workflow mode owner`,()=>i.dispose()));let a=Q;Q=void 0,a&&await $(`dispose workflow leader actions`,a);let o=G.size,s=Yt;Yt=void 0,s&&(await $(`record the workflow session finish`,()=>s.recordEvent(`doom_workflow.session_finished`,{"workflow.pending_count":o,outcome:o===0?`completed`:`interrupted`})),await $(`stop workflow telemetry`,()=>s.shutdown())),G.clear(),N.clear(),Rt.clear(),Gt.length=0,Jt=void 0,R=void 0})(),Vn)}}}export{St as compatibleRunners,wt as installWorkflowPiRuntime,ot as panelHint,st as panelHints,y as shortcutLabel};
11
9
  //# sourceMappingURL=piExtension.mjs.map