@agimon-ai/doompi-workflow 0.0.1-alpha.14 → 0.0.1-alpha.16

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
@@ -1,14 +1,17 @@
1
1
  # @agimon-ai/doompi-workflow
2
2
 
3
- Doompi workflow execution and monitoring integration.
3
+ **Long work needs edges.**
4
4
 
5
- Long autonomous work in the shape a GitHub Actions user already knows: `on:`, `jobs:`,
5
+ This package gives Doompi the job graph GitHub Actions users already know: `on:`, `jobs:`,
6
6
  `needs:`, `steps:`, timeouts, and declared artifacts. Every step names the session it wants,
7
- so one run can hand each job exactly the context that job needs.
7
+ so implementation can use coding tools while a later release note gets marketing context
8
+ and a brand profile. One run does not mean one swollen context window.
8
9
 
9
- Part of [Doompi](https://www.npmjs.com/package/@agimon-ai/doompi), a coding agent that loads
10
- only the skills and tools you name. This package is composed by Doompi and is not usually
11
- installed on its own.
10
+ Workflow is a minor mode. Its tools can be enabled or removed during the session, while
11
+ running jobs continue under the boundaries declared by the workflow.
12
+
13
+ This is part of [Doompi](https://www.npmjs.com/package/@agimon-ai/doompi). Most users get
14
+ it with the distribution.
12
15
 
13
16
  ## Install
14
17
 
@@ -18,35 +21,46 @@ npm install @agimon-ai/doompi-workflow
18
21
 
19
22
  ## How it loads
20
23
 
21
- Doompi loads this unconditionally. It is core, not a layer, so it does not appear in
22
- `.doom/modes.yaml`: making it optional would only create broken configurations.
23
-
24
- Extension entry: `@agimon-ai/doompi-workflow/extensions/pi`
24
+ Doompi loads the workflow integration as core. It is not a major-mode layer and does not
25
+ appear in `.doom/modes.yaml`. The bare package name follows its Pi manifest and loads the
26
+ Pi adapter.
25
27
 
26
28
  ## Leader
27
29
 
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.
30
+ - `SPC w w` chooses and launches a workflow.
31
+ - `SPC w l` opens running workflows and their controls.
32
+ - `SPC w r` recovers a failed workflow.
33
+ - `SPC w e` gives the agent workflow tools or takes them back.
34
+
35
+ ## Recovery
36
+
37
+ The recovery picker can see terminal failures from earlier Pi sessions. The
38
+ `workflow_run` action `recovery-evidence` reads only terminal registry evidence; status,
39
+ tail, and every live control remain scoped to the session that owns the run.
40
+
41
+ Replay claims the failed record atomically, transfers only `PI_SESSION_ID`, and binds new
42
+ controls to the exact run generation that was authorized. Recovery resumes the failed
43
+ work; it does not quietly launch a second copy beside it.
30
44
 
31
- ## Notes
45
+ ## Progress without transcript noise
32
46
 
33
- Running workflows appear in a transient task-like list above the editor with an animated spinner
34
- and structured current-step status. Step updates and raw launcher frames never enter the chat transcript,
35
- completed workflows leave the list immediately,
36
- and only terminal success or failure emits an agent-chat message. Raw launcher output remains
37
- available through the explicit follow and open views. Workflow progress does not occupy the footer.
47
+ Running workflows appear in a transient task-like list above the editor with the current
48
+ step and an animated spinner. Step updates and raw launcher frames stay out of chat. The
49
+ list disappears on completion, and only terminal success or failure emits an agent
50
+ message. Raw output remains available through the follow and open views; it does not take
51
+ over the footer.
38
52
 
39
- The dispatcher exposes `list_workflows` to any session but scopes `launch_workflow` to the
40
- root session, so a subagent can look but not spawn.
53
+ The dispatcher exposes `list_workflows` to any session, but only the root session gets
54
+ `launch_workflow`. A subagent may inspect the catalog; it may not spawn a new factory.
41
55
 
42
56
  ## Entry points
43
57
 
44
- | Import |
45
- | -------------------------------------------------------------- |
46
- | `@agimon-ai/doompi-workflow` |
47
- | `@agimon-ai/doompi-workflow/extensions/dispatcher` |
48
- | `@agimon-ai/doompi-workflow/extensions/pi` |
49
- | `@agimon-ai/doompi-workflow/skills/workflow-recovery/SKILL.md` |
58
+ | Import | Purpose |
59
+ | -------------------------------------------------------------- | ----------------------------- |
60
+ | `@agimon-ai/doompi-workflow` | Library API |
61
+ | `@agimon-ai/doompi-workflow/extensions/dispatcher` | Workflow dispatcher extension |
62
+ | `@agimon-ai/doompi-workflow/extensions/pi` | Pi extension entry point |
63
+ | `@agimon-ai/doompi-workflow/skills/workflow-recovery/SKILL.md` | Recovery skill |
50
64
 
51
65
  ## License
52
66
 
@@ -1,4 +1,4 @@
1
- const e=require("../../../services/workflow/backgroundWork.cjs"),t=require("../../../services/workflow/workflowNarration.cjs"),n=require("../../../tui/workflow/workflowInspector.cjs"),r=require("../../../tui/workflow/workflowOverlay.cjs"),i=require("../../../tui/workflow/workflowPicker.cjs"),a=require("../../../tui/workflow/workflowProgressOverlay.cjs"),o=require("../../../tui/workflow/workflowPrompt.cjs"),s=require("../../../tui/workflow/workflowRunPanel.cjs"),c=require("../../../tui/workflow/workflowStepMessage.cjs"),l=require("../../../types/index.cjs"),u=require("./piToolBridge.cjs"),d=require("./piTools.cjs"),f=require("./workflowLauncher.cjs");let p=require("@agimon-ai/doompi-extension-contracts/child-process"),m=require("@agimon-ai/doompi-extension-contracts/protocol"),ee=require("@agimon-ai/doompi-extension-contracts/workflow"),h=require("node:child_process"),te=require("node:fs"),ne=require("node:fs/promises"),g=require("node:module"),_=require("node:path"),re=require("node:url"),ie=require("@agimon-ai/doompi-extension-contracts/leader"),ae=require("@agimon-ai/doompi-extension-contracts/mode"),oe=require("@agimon-ai/doompi-extension-contracts/narration"),se=require("@agimon-ai/doompi-extension-contracts/skills"),ce=require("@agimon-ai/doompi-telemetry"),v=require("@agimon-ai/workflow-mcp"),y=require("@earendil-works/pi-tui");const le={overlay:!0,overlayOptions:{anchor:`top-left`,width:`100%`,maxHeight:`100%`,margin:0}},ue={narrate:()=>void 0},de=`workflow-mcp-follow`,fe=`activate`,pe=`deactivate`;function me(e,t){return{activation:e?`active`:`inactive`,condition:`ready`,...e?{detail:t>0?`${t} active runs`:`tools enabled`,color:`accent`}:{},actions:[...e?[{id:fe,enabled:!1,disabledReason:`Workflow mode is active.`}]:[{id:fe,enabled:!0}],...e?[{id:pe,enabled:!0}]:[{id:pe,enabled:!1,disabledReason:`Workflow mode is inactive.`}]]}}const he=`SPC › w / workflows › w / launch`,b=`ctrl+alt+w`,x=`ctrl+alt+q`,ge={alt:`⌥`,cmd:`⌘`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`};function S(e,t=process.platform){let n=e.split(`+`);return t===`darwin`?n.map(e=>ge[e]??e.toUpperCase()).join(``):n.map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(`+`)}const C=1e4,w=`running`,T=`completed`,E=`failed`,D=`error`,O=`error`,_e=`doom_workflow.launch_requested`,ve=`../../../../skills`,k={running:`▸`,repair:`⚠`,completed:`✔`,failed:`✖`,separator:`·`};function ye(e,t=process.platform){let n=S(x,t);return e?[`Esc Esc closes this view`,`${S(b,t)} switches typing`,`${n} closes`].join(` ${k.separator} `):`view only ${k.separator} ${n} closes this view`}function be(e,t=process.platform){let n=[S(x,t),`close`];return e?[[`Esc Esc`,`close`],[S(b,t),`switch typing`],n]:[n]}function xe(e,t,n,r){let i=`${k.running} ${e.runKey}`,a=[e.workspace,A(e),e.runner].filter(Boolean).join(` ${k.separator} `),o=`${S(b)} to background`,s=n.fg(`accent`,(0,y.truncateToWidth)(i,r));return(0,y.visibleWidth)(i)+3+(0,y.visibleWidth)(a)<=r&&(s+=` `+n.fg(`muted`,a),(0,y.visibleWidth)(i)+3+(0,y.visibleWidth)(a)+3+(0,y.visibleWidth)(o)<=r&&(s+=` `+n.fg(`dim`,o))),[s,...t.map(e=>(0,y.truncateToWidth)(e,r))]}function Se(e,t,n){let r=[];for(let i of e){let e=i.status===T?k.completed:i.status===E?k.failed:k.running,a=i.status===T?`success`:i.status===E?O:`accent`,o=i.total?` ${k.separator} ${(i.index??0)+1}/${i.total}`:``;if(r.push((0,y.truncateToWidth)(t.fg(a,`${e} ${i.name}`)+t.fg(`dim`,o),n)),i.status===w)for(let e of i.steps){let i=e.status===T?k.completed:e.status===E?k.failed:k.running,a=e.status===w?`muted`:e.status===E?O:`dim`;r.push((0,y.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 A(e){return e.stale?`stale`:e.stage===D?E:e.stage}const we=`CMUX_WORKSPACE_ID`,Te=`TMUX_PANE`,Ee=`WORKFLOW_TMUX_SESSION_NAME`,De=[`CMUX_SURFACE_ID`,p.SUBAGENT_ROOT_SESSION_ENV,we,Te,`WORKFLOW_LAUNCHER_TYPE`,Ee,`WORKFLOW_STATUS_FILE`],j=`tmux`,M=`cmux`,Oe=`native`,ke=`This run points at Pi's own terminal, so its launcher screen is hidden. Use workflow status instead.`;function Ae(e){let t=e.launcher;if(t?.type===M){let e=process.env[we];return!!e&&t.workspaceId===e}if(t?.type!==j)return!1;let n=process.env[Te],r=process.env[Ee];return!!n&&t.paneId===n||!!r&&t.sessionName===r}const je=`No durable launcher identity is recorded for this run.`;function Me(e){return(0,_.basename)(e,(0,_.extname)(e))}function Ne(){let e={...process.env};for(let t of De)delete e[t];return e}function Pe(){let e=process.env.WORKFLOW_LAUNCHER;return e===j||e===M?e:void 0}function Fe(e){return e.map(e=>`'${e.replaceAll(`'`,`'\\''`)}'`).join(` `)}function Ie(){try{return(0,g.createRequire)(require("url").pathToFileURL(__filename).href).resolve(`@agimon-ai/workflow-mcp/cli`)}catch{return}}const Le=async(e,t,n)=>{await new Promise((r,i)=>{let a=(0,h.spawn)(e,[...t],{...n,detached:!0,stdio:`ignore`});a.once(`error`,i),a.once(`spawn`,()=>{a.off(`error`,i),a.unref(),r()})})};function Re(e,t){if(Array.isArray(e)){for(let n of e)Re(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)):Re(r,t)}function ze(e){let t=[];if(Re(e,t),t.length!==0)return t[0].filter(e=>t.every(t=>t.includes(e))).sort()}async function N(e,t){let n=[],r=1,i=!0;for(;i;){let a=await e.listRunsPage({page:r,pageSize:100,workspace:t});n.push(...a.items),i=a.hasNextPage,r+=1}return n}function Be(e){return[e.runKey,e.workspace,A(e),e.runner].filter(Boolean).join(` ${k.separator} `)}function P(h={}){return g=>{let y=h.featureFactory?.()??(0,v.createEmbeddedWorkflowFeature)(),{parser:ge,recoverService:E,registry:O,runService:we,runTool:Te}=y,Ee=h.spawnDetached??Le,De=h.monitorIntervalMs??3e4,Re=h.followIntervalMs??2e3,P,Ve=!1,F,He=!1,I,Ue=new Map,L=new Map,We=new Set,Ge=0,R,Ke,qe=ue,Je=!1,Ye,z=0,B=!1,Xe,Ze=(0,ae.registerMinorModeOwner)(g,{descriptor:{source:l.PACKAGE_SOURCE,id:`workflow`,label:`WORKFLOW`,description:`Workflow discovery, launch, inspection, control, and recovery tools.`,order:20,actions:[{id:fe,label:`Activate`,description:`Enable workflow tools for this session.`,contexts:[`tui`,`headless`],parameters:[]},{id:pe,label:`Deactivate`,description:`Disable workflow tools without stopping active runs.`,contexts:[`tui`,`headless`],parameters:[]}]},initialState:me(!1,0),async handleAction(e,t,n){if(e===fe)return await Gt(!0,n.context),{message:`Workflow mode activated.`};if(e===pe)return await Gt(!1,n.context),{message:`Workflow mode deactivated.`};throw Error(`Unknown workflow mode action: ${e}`)}}),Qe,V,$e,et=!1,tt=new Map,H,nt=[],U=new Map,rt=[],W=new Map,it=0,at=new a.WorkflowProgressOverlay,ot,G,K=e=>(G??=(0,ce.createDoomTelemetry)({serviceName:`doom-workflow`,packageName:`@agimon-ai/doompi-workflow`,cwd:e?.cwd,env:process.env,enableLogs:!0,enableTraces:!0}),G),st=e=>{e&&(ot=(0,p.resolveRootSessionId)(e))},ct=()=>{let e=ot??Qe?.sessionManager.getSessionId();return e?(0,p.resolveRootSessionId)(e):void 0},lt=(e,t)=>e.sessionManager.getSessionId().trim()===t,ut=y.createRecoverTool({ownerSessionId:ct}),dt=y.createControlTool(),q=new Set,ft=e=>{let t=Date.now();return q.add(e),K().recordEvent(`doom_workflow.run_started`,{outcome:`started`}),e.then(()=>(q.delete(e),K().recordEvent(`doom_workflow.run_finished`,{duration_ms:Date.now()-t,outcome:`completed`})),n=>(q.delete(e),K().recordError(`doom_workflow.run_failed`,n,{duration_ms:Date.now()-t}))),e},pt=(e,t)=>{let n=ze(ge.parseWorkflowFile(e));if(!(!n||n.includes(t)))return`Runner ${JSON.stringify(t)} is not available across this workflow. Compatible runners: ${n.join(`, `)||`none`}.`},mt=d.createWorkflowLaunchExecutor({activeRunCount:async()=>z,observeSession:st,rejectRunner:pt,runTool:Te,trackPendingRun:ft,...h.launchAckPollMs===void 0?{}:{launchAckPollMs:h.launchAckPollMs},...h.launchAckTimeoutMs===void 0?{}:{launchAckTimeoutMs:h.launchAckTimeoutMs},findLaunchedRun:async({sessionId:e,since:t,workflowPath:n})=>{let r=Me(n),i=n=>{if(!u.isSessionRun(n,e)||n.stale||n.workflowId&&n.workflowId!==r)return!1;let i=Date.parse(n.startedAt);return Number.isFinite(i)&&i>=t-2e3};return H?(await H.refresh(),rt.find(i)):(await N(O)).find(i)},onLateFailure:(e,t)=>{let n=e instanceof Error?e.message:String(e);t.ui.notify(u.launchNotice(`Workflow launch failed after it was reported started.\n${n}`),`warning`)},onLaunch:async e=>(await K(e).recordEvent(_e,{outcome:`requested`}),Z(e,!1))}),ht=()=>{let e=[...W.values()].sort((e,t)=>Date.parse(e.startedAt)-Date.parse(t.startedAt)||X(e).localeCompare(X(t)));at.update(e.map(e=>a.workflowProgressRow(e,U.get(X(e))??[])))},J=e=>{it+=1;let t=X(e);e.stage===w&&!e.stale?W.set(t,e):W.delete(t),ht()},gt=e=>{W.clear();for(let t of e)W.set(X(t),t);ht()},_t=(e,t)=>{let n=U.get(e)??[];n.push(t),U.set(e,n)},Y,vt=e=>{Y||(Y=setTimeout(()=>{Y=void 0,Z(e,!0)},120),Y.unref?.())},yt=e=>{U.delete(e)},bt=async e=>{let t=(0,p.resolveRootSessionId)(e.sessionManager.getSessionId()),n=y.createRunControl({recordFilter:e=>u.isSessionRun(e,t)});H=n,nt.push(n.on(`step`,t=>{let n=X(t.record);_t(n,{at:t.at,job:t.job,status:t.status,step:t.step,type:`step`,...t.reason===void 0?{}:{reason:t.reason}}),J(t.record),vt(e)}),n.on(`job`,t=>{let n=X(t.record);_t(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}}),J(t.record),vt(e)}),n.on(`runStarted`,t=>{rt.unshift(t.record),rt.length>32&&(rt.length=32),J(t.record),vt(e)}),n.on(`runUpdated`,t=>{J(t.record),Z(e,!0)}),n.on(`runFinished`,t=>{J(t.record),Z(e,!0)})),await n.start()},xt=()=>{Y&&clearTimeout(Y),Y=void 0;for(let e of nt.splice(0))e();H?.dispose(),H=void 0,U.clear(),it+=1,W.clear(),ht()},St=()=>{V?.teardown(),V?.handle?.hide(),V=void 0},X=e=>e.runId??`${e.workspace}/${e.runKey}`,Ct=()=>{let e=me(B,z),t=JSON.stringify(e);t!==Xe&&(Xe=t,Ze.publish(e))},Z=async(e,n)=>{let r=K(e);if(Ve){r.recordEvent(`doom_workflow.monitor_skipped`,{outcome:`already_running`});return}let i=Date.now(),a=it;Ve=!0;try{let o=await N(O),s=(0,p.resolveRootSessionId)(e.sessionManager.getSessionId()),c=lt(e,s),l=o.filter(e=>u.isSessionRun(e,s)),d=l.filter(e=>e.stage===w&&!e.stale),f=l.filter(e=>e.stage===D);a===it&&gt(d),r.recordEvent(`doom_workflow.monitor_counts`,{"workflow.run_count":l.length,"workflow.running_count":d.length,"workflow.failed_count":f.length,mode:B?`on`:`off`});let m=u.runsForSession(o,s).filter(e=>!e.stale);z=m.length,Ct(),R?.update(m.map(e=>({id:e.runId??e.runKey,sessionId:s}))),Ot(new Set(l.map(e=>X(e))));let ee=new Map(l.map(e=>[X(e),e.stage]));for(let e of l){let r=X(e),i=Ue.get(r),a=Date.parse(e.startedAt),o=i===void 0&&(Ge===0||!Number.isFinite(a)||a>=Ge);Je&&t.narrateWorkflowTransition(qe,e,i===w||i===T||i===D?i:void 0,o),!(!n||!c||e.stage===w)&&!We.has(r)&&(i===w||o)&&L.set(r,e)}let h=new Set([...l.filter(e=>e.stage===w&&!e.stale).map(e=>X(e)),...L.keys()]);for(let e of U.keys())h.has(e)||yt(e);if(n)for(let t of l){if(Ue.get(X(t))!==w||t.stage===w)continue;let n=t.stage===T,i=t.failedJob?` at ${t.failedJob}`:``;e.ui.notify(n?`${k.completed} ${t.runKey} completed`:`${k.failed} ${t.runKey} ${A(t)}${i}`,n?`info`:`warning`),r.recordEvent(`doom_workflow.run_transition`,{outcome:n?`completed`:A(t)})}if(c&&L.size>0)try{let e=[...L.values()],t=await Promise.all(e.map(async e=>{if(e.stage===T)return u.finishedRunSummary(e,[]);let t=X(e),n=U.get(t)??Ce(e,await(0,v.readWorkflowProgress)(O.runDirectoryFor(e)));return u.finishedRunSummary(e,(0,v.summarizeWorkflowProgress)(n))}));g.sendMessage({customType:`workflow-run-finished`,content:t.join(`
1
+ const e=require("../../../services/workflow/backgroundWork.cjs"),t=require("../../../services/workflow/workflowNarration.cjs"),n=require("../../../tui/workflow/workflowInspector.cjs"),r=require("../../../tui/workflow/workflowOverlay.cjs"),i=require("../../../tui/workflow/workflowPicker.cjs"),a=require("../../../tui/workflow/workflowProgressOverlay.cjs"),o=require("../../../tui/workflow/workflowPrompt.cjs"),s=require("../../../tui/workflow/workflowRunPanel.cjs"),c=require("../../../tui/workflow/workflowStepMessage.cjs"),l=require("../../../types/index.cjs"),u=require("./piToolBridge.cjs"),d=require("./piTools.cjs"),f=require("./workflowLauncher.cjs");let p=require("@agimon-ai/doompi-extension-contracts/child-process"),m=require("@agimon-ai/doompi-extension-contracts/protocol"),ee=require("@agimon-ai/doompi-extension-contracts/workflow"),h=require("node:child_process"),te=require("node:fs"),ne=require("node:fs/promises"),g=require("node:module"),_=require("node:path"),re=require("node:url"),ie=require("@agimon-ai/doompi-extension-contracts/leader"),ae=require("@agimon-ai/doompi-extension-contracts/mode"),oe=require("@agimon-ai/doompi-extension-contracts/narration"),se=require("@agimon-ai/doompi-extension-contracts/skills"),ce=require("@agimon-ai/doompi-telemetry"),v=require("@agimon-ai/workflow-mcp"),y=require("@earendil-works/pi-tui");const le={overlay:!0,overlayOptions:{anchor:`top-left`,width:`100%`,maxHeight:`100%`,margin:0}},ue={narrate:()=>void 0},de=`workflow-mcp-follow`,fe=`activate`,pe=`deactivate`;function me(e,t){return{activation:e?`active`:`inactive`,condition:`ready`,...e?{detail:t>0?`${t} active runs`:`tools enabled`,color:`accent`}:{},actions:[...e?[{id:fe,enabled:!1,disabledReason:`Workflow mode is active.`}]:[{id:fe,enabled:!0}],...e?[{id:pe,enabled:!0}]:[{id:pe,enabled:!1,disabledReason:`Workflow mode is inactive.`}]]}}const he=`SPC › w / workflows › w / launch`,b=`ctrl+alt+w`,x=`ctrl+alt+q`,ge={alt:`⌥`,cmd:`⌘`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`};function S(e,t=process.platform){let n=e.split(`+`);return t===`darwin`?n.map(e=>ge[e]??e.toUpperCase()).join(``):n.map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(`+`)}const C=1e4,w=`running`,T=`completed`,E=`failed`,D=`error`,O=`error`,_e=`doom_workflow.launch_requested`,ve=`../../../../skills`,k={running:`▸`,repair:`⚠`,completed:`✔`,failed:`✖`,separator:`·`};function ye(e,t=process.platform){let n=S(x,t);return e?[`Esc Esc closes this view`,`${S(b,t)} switches typing`,`${n} closes`].join(` ${k.separator} `):`view only ${k.separator} ${n} closes this view`}function be(e,t=process.platform){let n=[S(x,t),`close`];return e?[[`Esc Esc`,`close`],[S(b,t),`switch typing`],n]:[n]}function xe(e,t,n,r){let i=`${k.running} ${e.runKey}`,a=[e.workspace,A(e),e.runner].filter(Boolean).join(` ${k.separator} `),o=`${S(b)} to background`,s=n.fg(`accent`,(0,y.truncateToWidth)(i,r));return(0,y.visibleWidth)(i)+3+(0,y.visibleWidth)(a)<=r&&(s+=` `+n.fg(`muted`,a),(0,y.visibleWidth)(i)+3+(0,y.visibleWidth)(a)+3+(0,y.visibleWidth)(o)<=r&&(s+=` `+n.fg(`dim`,o))),[s,...t.map(e=>(0,y.truncateToWidth)(e,r))]}function Se(e,t,n){let r=[];for(let i of e){let e=i.status===T?k.completed:i.status===E?k.failed:k.running,a=i.status===T?`success`:i.status===E?O:`accent`,o=i.total?` ${k.separator} ${(i.index??0)+1}/${i.total}`:``;if(r.push((0,y.truncateToWidth)(t.fg(a,`${e} ${i.name}`)+t.fg(`dim`,o),n)),i.status===w)for(let e of i.steps){let i=e.status===T?k.completed:e.status===E?k.failed:k.running,a=e.status===w?`muted`:e.status===E?O:`dim`;r.push((0,y.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 A(e){return e.stale?`stale`:e.stage===D?E:e.stage}const we=`CMUX_WORKSPACE_ID`,Te=`TMUX_PANE`,Ee=`WORKFLOW_TMUX_SESSION_NAME`,De=[`CMUX_SURFACE_ID`,p.SUBAGENT_ROOT_SESSION_ENV,we,Te,`WORKFLOW_LAUNCHER_TYPE`,Ee,`WORKFLOW_STATUS_FILE`],j=`tmux`,M=`cmux`,Oe=`native`,ke=`This run points at Pi's own terminal, so its launcher screen is hidden. Use workflow status instead.`;function Ae(e){let t=e.launcher;if(t?.type===M){let e=process.env[we];return!!e&&t.workspaceId===e}if(t?.type!==j)return!1;let n=process.env[Te],r=process.env[Ee];return!!n&&t.paneId===n||!!r&&t.sessionName===r}const je=`No durable launcher identity is recorded for this run.`;function Me(e){return(0,_.basename)(e,(0,_.extname)(e))}function Ne(){let e={...process.env};for(let t of De)delete e[t];return e}function Pe(){let e=process.env.WORKFLOW_LAUNCHER;return e===j||e===M?e:void 0}function Fe(e){return e.map(e=>`'${e.replaceAll(`'`,`'\\''`)}'`).join(` `)}function Ie(){try{return(0,g.createRequire)(require("url").pathToFileURL(__filename).href).resolve(`@agimon-ai/workflow-mcp/cli`)}catch{return}}const Le=async(e,t,n)=>{await new Promise((r,i)=>{let a=(0,h.spawn)(e,[...t],{...n,detached:!0,stdio:`ignore`});a.once(`error`,i),a.once(`spawn`,()=>{a.off(`error`,i),a.unref(),r()})})};function Re(e,t){if(Array.isArray(e)){for(let n of e)Re(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)):Re(r,t)}function ze(e){let t=[];if(Re(e,t),t.length!==0)return t[0].filter(e=>t.every(t=>t.includes(e))).sort()}async function N(e,t){let n=[],r=1,i=!0;for(;i;){let a=await e.listRunsPage({page:r,pageSize:100,workspace:t});n.push(...a.items),i=a.hasNextPage,r+=1}return n}function Be(e){return[e.runKey,e.workspace,A(e),e.runner].filter(Boolean).join(` ${k.separator} `)}function P(h={}){return g=>{let y=h.featureFactory?.()??(0,v.createEmbeddedWorkflowFeature)(),{parser:ge,recoverService:E,registry:O,runService:we,runTool:Te}=y,Ee=h.spawnDetached??Le,De=h.monitorIntervalMs??3e4,Re=h.followIntervalMs??2e3,P,Ve=!1,F,He=!1,I,Ue=new Map,L=new Map,We=new Set,Ge=0,R,Ke,qe=ue,Je=!1,Ye,z=0,B=!1,Xe,Ze=(0,ae.registerMinorModeOwner)(g,{descriptor:{source:l.PACKAGE_SOURCE,id:`workflow`,label:`Workflow`,description:`Workflow discovery, launch, inspection, control, and recovery tools.`,order:20,actions:[{id:fe,label:`Activate`,description:`Enable workflow tools for this session.`,contexts:[`tui`,`headless`],parameters:[]},{id:pe,label:`Deactivate`,description:`Disable workflow tools without stopping active runs.`,contexts:[`tui`,`headless`],parameters:[]}]},initialState:me(!1,0),async handleAction(e,t,n){if(e===fe)return await Gt(!0,n.context),{message:`Workflow mode activated.`};if(e===pe)return await Gt(!1,n.context),{message:`Workflow mode deactivated.`};throw Error(`Unknown workflow mode action: ${e}`)}}),Qe,V,$e,et=!1,tt=new Map,H,nt=[],U=new Map,rt=[],W=new Map,it=0,at=new a.WorkflowProgressOverlay,ot,G,K=e=>(G??=(0,ce.createDoomTelemetry)({serviceName:`doom-workflow`,packageName:`@agimon-ai/doompi-workflow`,cwd:e?.cwd,env:process.env,enableLogs:!0,enableTraces:!0}),G),st=e=>{e&&(ot=(0,p.resolveRootSessionId)(e))},ct=()=>{let e=ot??Qe?.sessionManager.getSessionId();return e?(0,p.resolveRootSessionId)(e):void 0},lt=(e,t)=>e.sessionManager.getSessionId().trim()===t,ut=y.createRecoverTool({ownerSessionId:ct}),dt=y.createControlTool(),q=new Set,ft=e=>{let t=Date.now();return q.add(e),K().recordEvent(`doom_workflow.run_started`,{outcome:`started`}),e.then(()=>(q.delete(e),K().recordEvent(`doom_workflow.run_finished`,{duration_ms:Date.now()-t,outcome:`completed`})),n=>(q.delete(e),K().recordError(`doom_workflow.run_failed`,n,{duration_ms:Date.now()-t}))),e},pt=(e,t)=>{let n=ze(ge.parseWorkflowFile(e));if(!(!n||n.includes(t)))return`Runner ${JSON.stringify(t)} is not available across this workflow. Compatible runners: ${n.join(`, `)||`none`}.`},mt=d.createWorkflowLaunchExecutor({activeRunCount:async()=>z,observeSession:st,rejectRunner:pt,runTool:Te,trackPendingRun:ft,...h.launchAckPollMs===void 0?{}:{launchAckPollMs:h.launchAckPollMs},...h.launchAckTimeoutMs===void 0?{}:{launchAckTimeoutMs:h.launchAckTimeoutMs},findLaunchedRun:async({sessionId:e,since:t,workflowPath:n})=>{let r=Me(n),i=n=>{if(!u.isSessionRun(n,e)||n.stale||n.workflowId&&n.workflowId!==r)return!1;let i=Date.parse(n.startedAt);return Number.isFinite(i)&&i>=t-2e3};return H?(await H.refresh(),rt.find(i)):(await N(O)).find(i)},onLateFailure:(e,t)=>{let n=e instanceof Error?e.message:String(e);t.ui.notify(u.launchNotice(`Workflow launch failed after it was reported started.\n${n}`),`warning`)},onLaunch:async e=>(await K(e).recordEvent(_e,{outcome:`requested`}),Z(e,!1))}),ht=()=>{let e=[...W.values()].sort((e,t)=>Date.parse(e.startedAt)-Date.parse(t.startedAt)||X(e).localeCompare(X(t)));at.update(e.map(e=>a.workflowProgressRow(e,U.get(X(e))??[])))},J=e=>{it+=1;let t=X(e);e.stage===w&&!e.stale?W.set(t,e):W.delete(t),ht()},gt=e=>{W.clear();for(let t of e)W.set(X(t),t);ht()},_t=(e,t)=>{let n=U.get(e)??[];n.push(t),U.set(e,n)},Y,vt=e=>{Y||(Y=setTimeout(()=>{Y=void 0,Z(e,!0)},120),Y.unref?.())},yt=e=>{U.delete(e)},bt=async e=>{let t=(0,p.resolveRootSessionId)(e.sessionManager.getSessionId()),n=y.createRunControl({recordFilter:e=>u.isSessionRun(e,t)});H=n,nt.push(n.on(`step`,t=>{let n=X(t.record);_t(n,{at:t.at,job:t.job,status:t.status,step:t.step,type:`step`,...t.reason===void 0?{}:{reason:t.reason}}),J(t.record),vt(e)}),n.on(`job`,t=>{let n=X(t.record);_t(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}}),J(t.record),vt(e)}),n.on(`runStarted`,t=>{rt.unshift(t.record),rt.length>32&&(rt.length=32),J(t.record),vt(e)}),n.on(`runUpdated`,t=>{J(t.record),Z(e,!0)}),n.on(`runFinished`,t=>{J(t.record),Z(e,!0)})),await n.start()},xt=()=>{Y&&clearTimeout(Y),Y=void 0;for(let e of nt.splice(0))e();H?.dispose(),H=void 0,U.clear(),it+=1,W.clear(),ht()},St=()=>{V?.teardown(),V?.handle?.hide(),V=void 0},X=e=>e.runId??`${e.workspace}/${e.runKey}`,Ct=()=>{let e=me(B,z),t=JSON.stringify(e);t!==Xe&&(Xe=t,Ze.publish(e))},Z=async(e,n)=>{let r=K(e);if(Ve){r.recordEvent(`doom_workflow.monitor_skipped`,{outcome:`already_running`});return}let i=Date.now(),a=it;Ve=!0;try{let o=await N(O),s=(0,p.resolveRootSessionId)(e.sessionManager.getSessionId()),c=lt(e,s),l=o.filter(e=>u.isSessionRun(e,s)),d=l.filter(e=>e.stage===w&&!e.stale),f=l.filter(e=>e.stage===D);a===it&&gt(d),r.recordEvent(`doom_workflow.monitor_counts`,{"workflow.run_count":l.length,"workflow.running_count":d.length,"workflow.failed_count":f.length,mode:B?`on`:`off`});let m=u.runsForSession(o,s).filter(e=>!e.stale);z=m.length,Ct(),R?.update(m.map(e=>({id:e.runId??e.runKey,sessionId:s}))),Ot(new Set(l.map(e=>X(e))));let ee=new Map(l.map(e=>[X(e),e.stage]));for(let e of l){let r=X(e),i=Ue.get(r),a=Date.parse(e.startedAt),o=i===void 0&&(Ge===0||!Number.isFinite(a)||a>=Ge);Je&&t.narrateWorkflowTransition(qe,e,i===w||i===T||i===D?i:void 0,o),!(!n||!c||e.stage===w)&&!We.has(r)&&(i===w||o)&&L.set(r,e)}let h=new Set([...l.filter(e=>e.stage===w&&!e.stale).map(e=>X(e)),...L.keys()]);for(let e of U.keys())h.has(e)||yt(e);if(n)for(let t of l){if(Ue.get(X(t))!==w||t.stage===w)continue;let n=t.stage===T,i=t.failedJob?` at ${t.failedJob}`:``;e.ui.notify(n?`${k.completed} ${t.runKey} completed`:`${k.failed} ${t.runKey} ${A(t)}${i}`,n?`info`:`warning`),r.recordEvent(`doom_workflow.run_transition`,{outcome:n?`completed`:A(t)})}if(c&&L.size>0)try{let e=[...L.values()],t=await Promise.all(e.map(async e=>{if(e.stage===T)return u.finishedRunSummary(e,[]);let t=X(e),n=U.get(t)??Ce(e,await(0,v.readWorkflowProgress)(O.runDirectoryFor(e)));return u.finishedRunSummary(e,(0,v.summarizeWorkflowProgress)(n))}));g.sendMessage({customType:`workflow-run-finished`,content:t.join(`
2
2
 
3
3
  `),display:!0,details:{runIds:e.map(e=>e.runId??X(e))}},{triggerTurn:!0,deliverAs:`steer`});for(let t of e){let e=X(t);We.add(e),L.delete(e),yt(e)}}catch(e){await r.recordError(`doom_workflow.terminal_delivery_failed`,e,{"workflow.pending_terminal_count":L.size})}Ue=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.notify(`Workflow status refresh failed: ${t instanceof Error?t.message:String(t)}`,`warning`)}finally{Ve=!1}},wt=e=>{F&&clearInterval(F),F=void 0,I=void 0,e.ui.setWidget(de,void 0)},Tt=async e=>{let t=e.launcher;if(!t)return[je];if(Ae(e))return[ke];if(t.type===j){let n=t.paneId??t.sessionId??t.sessionName,r=await g.exec(j,[`capture-pane`,`-p`,`-t`,n,`-S`,`-24`],{timeout:C});return r.code===0?r.stdout.trimEnd().split(`
4
4
  `).slice(-24):[`Unable to read ${e.runKey}: ${r.stderr.trim()||`tmux exited ${r.code}`}`]}if(t.type===Oe)return[`This run is hosted natively and has no launcher screen to read.`];let n=await g.exec(M,[`read-screen`,`--workspace`,t.workspaceId,`--scrollback`,`--lines`,`24`],{timeout:C});return n.code===0?n.stdout.trimEnd().split(`