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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +46 -0
  3. package/dist/dispatcher.cjs +2 -0
  4. package/dist/dispatcher.cjs.map +1 -0
  5. package/dist/dispatcher.d.cts +6 -0
  6. package/dist/dispatcher.d.cts.map +1 -0
  7. package/dist/dispatcher.d.mts +6 -0
  8. package/dist/dispatcher.d.mts.map +1 -0
  9. package/dist/dispatcher.mjs +2 -0
  10. package/dist/dispatcher.mjs.map +1 -0
  11. package/dist/extension.cjs +2 -0
  12. package/dist/extension.cjs.map +1 -0
  13. package/dist/extension.d.cts +6 -0
  14. package/dist/extension.d.cts.map +1 -0
  15. package/dist/extension.d.mts +6 -0
  16. package/dist/extension.d.mts.map +1 -0
  17. package/dist/extension.mjs +2 -0
  18. package/dist/extension.mjs.map +1 -0
  19. package/dist/extensions/dispatcher.cjs +2 -0
  20. package/dist/extensions/dispatcher.cjs.map +1 -0
  21. package/dist/extensions/dispatcher.d.cts +2 -0
  22. package/dist/extensions/dispatcher.d.mts +2 -0
  23. package/dist/extensions/dispatcher.mjs +2 -0
  24. package/dist/extensions/dispatcher.mjs.map +1 -0
  25. package/dist/extensions/pi.cjs +2 -0
  26. package/dist/extensions/pi.cjs.map +1 -0
  27. package/dist/extensions/pi.d.cts +2 -0
  28. package/dist/extensions/pi.d.mts +2 -0
  29. package/dist/extensions/pi.mjs +2 -0
  30. package/dist/extensions/pi.mjs.map +1 -0
  31. package/dist/index.cjs +1 -0
  32. package/dist/index.d.cts +3 -0
  33. package/dist/index.d.mts +3 -0
  34. package/dist/index.mjs +1 -0
  35. package/dist/leader.cjs +2 -0
  36. package/dist/leader.cjs.map +1 -0
  37. package/dist/leader.mjs +2 -0
  38. package/dist/leader.mjs.map +1 -0
  39. package/dist/types.cjs +2 -0
  40. package/dist/types.cjs.map +1 -0
  41. package/dist/types.mjs +2 -0
  42. package/dist/types.mjs.map +1 -0
  43. package/dist/workflow/backgroundWork.cjs +2 -0
  44. package/dist/workflow/backgroundWork.cjs.map +1 -0
  45. package/dist/workflow/backgroundWork.mjs +2 -0
  46. package/dist/workflow/backgroundWork.mjs.map +1 -0
  47. package/dist/workflow/doomOverlay.cjs +2 -0
  48. package/dist/workflow/doomOverlay.cjs.map +1 -0
  49. package/dist/workflow/doomOverlay.mjs +2 -0
  50. package/dist/workflow/doomOverlay.mjs.map +1 -0
  51. package/dist/workflow/overlayText.cjs +2 -0
  52. package/dist/workflow/overlayText.cjs.map +1 -0
  53. package/dist/workflow/overlayText.mjs +2 -0
  54. package/dist/workflow/overlayText.mjs.map +1 -0
  55. package/dist/workflow/piExtension.cjs +10 -0
  56. package/dist/workflow/piExtension.cjs.map +1 -0
  57. package/dist/workflow/piExtension.mjs +10 -0
  58. package/dist/workflow/piExtension.mjs.map +1 -0
  59. package/dist/workflow/piToolBridge.cjs +11 -0
  60. package/dist/workflow/piToolBridge.cjs.map +1 -0
  61. package/dist/workflow/piToolBridge.mjs +11 -0
  62. package/dist/workflow/piToolBridge.mjs.map +1 -0
  63. package/dist/workflow/piTools.cjs +4 -0
  64. package/dist/workflow/piTools.cjs.map +1 -0
  65. package/dist/workflow/piTools.mjs +4 -0
  66. package/dist/workflow/piTools.mjs.map +1 -0
  67. package/dist/workflow/rendering.cjs +2 -0
  68. package/dist/workflow/rendering.cjs.map +1 -0
  69. package/dist/workflow/rendering.mjs +2 -0
  70. package/dist/workflow/rendering.mjs.map +1 -0
  71. package/dist/workflow/workflowFooterStatus.cjs +2 -0
  72. package/dist/workflow/workflowFooterStatus.cjs.map +1 -0
  73. package/dist/workflow/workflowFooterStatus.mjs +2 -0
  74. package/dist/workflow/workflowFooterStatus.mjs.map +1 -0
  75. package/dist/workflow/workflowInspector.cjs +2 -0
  76. package/dist/workflow/workflowInspector.cjs.map +1 -0
  77. package/dist/workflow/workflowInspector.mjs +2 -0
  78. package/dist/workflow/workflowInspector.mjs.map +1 -0
  79. package/dist/workflow/workflowLauncher.cjs +2 -0
  80. package/dist/workflow/workflowLauncher.cjs.map +1 -0
  81. package/dist/workflow/workflowLauncher.mjs +2 -0
  82. package/dist/workflow/workflowLauncher.mjs.map +1 -0
  83. package/dist/workflow/workflowOverlay.cjs +2 -0
  84. package/dist/workflow/workflowOverlay.cjs.map +1 -0
  85. package/dist/workflow/workflowOverlay.mjs +2 -0
  86. package/dist/workflow/workflowOverlay.mjs.map +1 -0
  87. package/dist/workflow/workflowPicker.cjs +2 -0
  88. package/dist/workflow/workflowPicker.cjs.map +1 -0
  89. package/dist/workflow/workflowPicker.mjs +2 -0
  90. package/dist/workflow/workflowPicker.mjs.map +1 -0
  91. package/dist/workflow/workflowPrompt.cjs +2 -0
  92. package/dist/workflow/workflowPrompt.cjs.map +1 -0
  93. package/dist/workflow/workflowPrompt.mjs +2 -0
  94. package/dist/workflow/workflowPrompt.mjs.map +1 -0
  95. package/dist/workflow/workflowRunPanel.cjs +2 -0
  96. package/dist/workflow/workflowRunPanel.cjs.map +1 -0
  97. package/dist/workflow/workflowRunPanel.mjs +2 -0
  98. package/dist/workflow/workflowRunPanel.mjs.map +1 -0
  99. package/dist/workflow/workflowStatusRow.cjs +2 -0
  100. package/dist/workflow/workflowStatusRow.cjs.map +1 -0
  101. package/dist/workflow/workflowStatusRow.mjs +2 -0
  102. package/dist/workflow/workflowStatusRow.mjs.map +1 -0
  103. package/dist/workflow/workflowStepMessage.cjs +2 -0
  104. package/dist/workflow/workflowStepMessage.cjs.map +1 -0
  105. package/dist/workflow/workflowStepMessage.mjs +2 -0
  106. package/dist/workflow/workflowStepMessage.mjs.map +1 -0
  107. package/package.json +82 -0
  108. package/skills/workflow-recovery/SKILL.md +73 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vuong Ngo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # @agimon-ai/doompi-workflow
2
+
3
+ Doompi workflow execution and monitoring integration.
4
+
5
+ Long autonomous work in the shape a GitHub Actions user already knows: `on:`, `jobs:`,
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.
8
+
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.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install @agimon-ai/doompi-workflow
17
+ ```
18
+
19
+ ## How it loads
20
+
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`
25
+
26
+ ## Leader
27
+
28
+ `SPC w w` launches, `SPC w l` manages, `SPC w r` recovers, once the extension is loaded.
29
+
30
+ ## Notes
31
+
32
+ The dispatcher exposes `list_workflows` to any session but scopes `launch_workflow` to the
33
+ root session, so a subagent can look but not spawn.
34
+
35
+ ## Entry points
36
+
37
+ | Import |
38
+ | -------------------------------------------------------------- |
39
+ | `@agimon-ai/doompi-workflow` |
40
+ | `@agimon-ai/doompi-workflow/extensions/dispatcher` |
41
+ | `@agimon-ai/doompi-workflow/extensions/pi` |
42
+ | `@agimon-ai/doompi-workflow/skills/workflow-recovery/SKILL.md` |
43
+
44
+ ## License
45
+
46
+ MIT
@@ -0,0 +1,2 @@
1
+ const e=require("./workflow/piExtension.cjs");let t=require("@agimon-ai/doompi-extension-contracts/child-process"),n=require("@agimon-ai/doompi-extension-contracts/protocol"),r=require("@agimon-ai/doompi-extension-contracts/workflow");const i=new Set([`list_workflows`,`launch_workflow`]),a=new Set([`launch_workflow`]);function o(e,t){let n=Reflect.get(e,t,e);return typeof n==`function`?n.bind(e):n}function s(e,t){let n=new Proxy(e.sessionManager,{get(e,n){return n===`getSessionId`?()=>t:o(e,n)}});return new Proxy(e,{get(e,t){return t===`sessionManager`?n:o(e,t)}})}function c(e,n,r=process.env){let c=n?(0,t.resolveRootSessionId)(n,r):void 0;return new Proxy(e,{get(e,t){return t===`setActiveTools`?t=>e.setActiveTools(t.filter(e=>e!==`workflow_run`)):t===`registerTool`?t=>{if(i.has(t.name)){if(!c||!a.has(t.name)){e.registerTool(t);return}e.registerTool({...t,execute:(e,n,r,i,a)=>t.execute(e,n,r,i,s(a,c))})}}:o(e,t)}})}function l(e){return(0,t.readChildProcessContext)(e)?.parentSessionId??(e[t.SUBAGENT_PARENT_SESSION_ENV]?.trim()||void 0)}function u(i){let a=l(process.env),o=a?(0,t.resolveRootSessionId)(a):void 0;e.createWorkflowPiExtension()(c(i,o)),i.on(`session_start`,(e,a)=>{let s=(0,t.resolveRootSessionId)(a.sessionManager.getSessionId());(0,n.createProtocolRuntime)({bus:i.events,source:`@agimon-ai/doompi-workflow/dispatcher`,sessionId:s}).notify(r.WorkflowModeChanged,{mode:`on`,...o?{parentSessionId:o}:{}})})}exports.createDispatcherBridge=c,exports.registerWorkflowDispatcherExtension=u,exports.resolveDispatcherParentSession=l;
2
+ //# sourceMappingURL=dispatcher.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatcher.cjs","names":["resolveRootSessionId","readChildProcessContext","SUBAGENT_PARENT_SESSION_ENV","WorkflowModeChanged"],"sources":["../src/dispatcher.ts"],"sourcesContent":["import {\n readChildProcessContext,\n resolveRootSessionId,\n SUBAGENT_PARENT_SESSION_ENV,\n} from '@agimon-ai/doompi-extension-contracts/child-process';\nimport { createProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport { WorkflowModeChanged } from '@agimon-ai/doompi-extension-contracts/workflow';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { createWorkflowPiExtension } from './workflow/piExtension.ts';\n\ntype RegisteredTool = Parameters<ExtensionAPI['registerTool']>[0];\nconst DISPATCHER_TOOL_NAMES = new Set(['list_workflows', 'launch_workflow']);\nconst ROOT_SCOPED_TOOL_NAMES = new Set(['launch_workflow']);\n\nfunction boundValue(target: object, property: PropertyKey): unknown {\n const value = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n}\n\nfunction withParentSession(ctx: ExtensionContext, parentSessionId: string): ExtensionContext {\n const sessionManager = new Proxy(ctx.sessionManager, {\n get(target, property) {\n if (property === 'getSessionId') return () => parentSessionId;\n return boundValue(target, property);\n },\n });\n return new Proxy(ctx, {\n get(target, property) {\n if (property === 'sessionManager') return sessionManager;\n return boundValue(target, property);\n },\n });\n}\n\nexport function createDispatcherBridge(\n pi: ExtensionAPI,\n parentSessionId: string | undefined,\n environment: Readonly<Record<string, string | undefined>> = process.env,\n): ExtensionAPI {\n const rootSessionId = parentSessionId ? resolveRootSessionId(parentSessionId, environment) : undefined;\n return new Proxy(pi, {\n get(target, property) {\n if (property === 'setActiveTools') {\n return (names: string[]): void => target.setActiveTools(names.filter((name) => name !== 'workflow_run'));\n }\n if (property !== 'registerTool') return boundValue(target, property);\n return (tool: RegisteredTool): void => {\n if (!DISPATCHER_TOOL_NAMES.has(tool.name)) return;\n if (!rootSessionId || !ROOT_SCOPED_TOOL_NAMES.has(tool.name)) {\n target.registerTool(tool);\n return;\n }\n target.registerTool({\n ...tool,\n execute: (toolCallId, params, signal, onUpdate, ctx) =>\n tool.execute(toolCallId, params, signal, onUpdate, withParentSession(ctx, rootSessionId)),\n });\n };\n },\n });\n}\n\nexport function resolveDispatcherParentSession(\n environment: Readonly<Record<string, string | undefined>>,\n): string | undefined {\n const childContext = readChildProcessContext(environment);\n return childContext?.parentSessionId ?? (environment[SUBAGENT_PARENT_SESSION_ENV]?.trim() || undefined);\n}\n\nexport function registerWorkflowDispatcherExtension(pi: ExtensionAPI): void {\n const parentSessionId = resolveDispatcherParentSession(process.env);\n const rootSessionId = parentSessionId ? resolveRootSessionId(parentSessionId) : undefined;\n createWorkflowPiExtension()(createDispatcherBridge(pi, rootSessionId));\n\n pi.on('session_start', (_event, ctx) => {\n const sessionId = resolveRootSessionId(ctx.sessionManager.getSessionId());\n createProtocolRuntime({\n bus: pi.events,\n source: '@agimon-ai/doompi-workflow/dispatcher',\n sessionId,\n }).notify(WorkflowModeChanged, { mode: 'on', ...(rootSessionId ? { parentSessionId: rootSessionId } : {}) });\n });\n}\n"],"mappings":"2OAWA,MAAM,EAAwB,IAAI,IAAI,CAAC,iBAAkB,iBAAiB,CAAC,EACrE,EAAyB,IAAI,IAAI,CAAC,iBAAiB,CAAC,EAE1D,SAAS,EAAW,EAAgB,EAAgC,CAClE,IAAM,EAAQ,QAAQ,IAAI,EAAQ,EAAU,CAAM,EAClD,OAAO,OAAO,GAAU,WAAa,EAAM,KAAK,CAAM,EAAI,CAC5D,CAEA,SAAS,EAAkB,EAAuB,EAA2C,CAC3F,IAAM,EAAiB,IAAI,MAAM,EAAI,eAAgB,CACnD,IAAI,EAAQ,EAAU,CAEpB,OADI,IAAa,mBAA6B,EACvC,EAAW,EAAQ,CAAQ,CACpC,CACF,CAAC,EACD,OAAO,IAAI,MAAM,EAAK,CACpB,IAAI,EAAQ,EAAU,CAEpB,OADI,IAAa,iBAAyB,EACnC,EAAW,EAAQ,CAAQ,CACpC,CACF,CAAC,CACH,CAEA,SAAgB,EACd,EACA,EACA,EAA4D,QAAQ,IACtD,CACd,IAAM,EAAgB,GAAA,EAAkBA,EAAAA,qBAAAA,CAAqB,EAAiB,CAAW,EAAI,IAAA,GAC7F,OAAO,IAAI,MAAM,EAAI,CACnB,IAAI,EAAQ,EAAU,CAKpB,OAJI,IAAa,iBACP,GAA0B,EAAO,eAAe,EAAM,OAAQ,GAAS,IAAS,cAAc,CAAC,EAErG,IAAa,eACT,GAA+B,CAChC,KAAsB,IAAI,EAAK,IAAI,EACxC,IAAI,CAAC,GAAiB,CAAC,EAAuB,IAAI,EAAK,IAAI,EAAG,CAC5D,EAAO,aAAa,CAAI,EACxB,MACF,CACA,EAAO,aAAa,CAClB,GAAG,EACH,SAAU,EAAY,EAAQ,EAAQ,EAAU,IAC9C,EAAK,QAAQ,EAAY,EAAQ,EAAQ,EAAU,EAAkB,EAAK,CAAa,CAAC,CAC5F,CAAC,CALD,CAMF,EAZwC,EAAW,EAAQ,CAAQ,CAarE,CACF,CAAC,CACH,CAEA,SAAgB,EACd,EACoB,CAEpB,OAAA,EADqBC,EAAAA,wBAAAA,CAAwB,CAC3B,CAAC,EAAE,kBAAoB,EAAYC,EAAAA,4BAA4B,EAAE,KAAK,GAAK,IAAA,GAC/F,CAEA,SAAgB,EAAoC,EAAwB,CAC1E,IAAM,EAAkB,EAA+B,QAAQ,GAAG,EAC5D,EAAgB,GAAA,EAAkBF,EAAAA,qBAAAA,CAAqB,CAAe,EAAI,IAAA,GAChF,EAAA,0BAA0B,CAAC,CAAC,EAAuB,EAAI,CAAa,CAAC,EAErE,EAAG,GAAG,iBAAkB,EAAQ,IAAQ,CACtC,IAAM,GAAA,EAAYA,EAAAA,qBAAAA,CAAqB,EAAI,eAAe,aAAa,CAAC,GACxE,EAAA,EAAA,sBAAA,CAAsB,CACpB,IAAK,EAAG,OACR,OAAQ,wCACR,WACF,CAAC,CAAC,CAAC,OAAOG,EAAAA,oBAAqB,CAAE,KAAM,KAAM,GAAI,EAAgB,CAAE,gBAAiB,CAAc,EAAI,CAAC,CAAG,CAAC,CAC7G,CAAC,CACH"}
@@ -0,0 +1,6 @@
1
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ //#region src/dispatcher.d.ts
3
+ declare function registerWorkflowDispatcherExtension(pi: ExtensionAPI): void;
4
+ //#endregion
5
+ export { registerWorkflowDispatcherExtension };
6
+ //# sourceMappingURL=dispatcher.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatcher.d.cts","names":[],"sources":["../src/dispatcher.ts"],"mappings":";;iBAqEgB,oCAAoC,IAAI"}
@@ -0,0 +1,6 @@
1
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ //#region src/dispatcher.d.ts
3
+ declare function registerWorkflowDispatcherExtension(pi: ExtensionAPI): void;
4
+ //#endregion
5
+ export { registerWorkflowDispatcherExtension };
6
+ //# sourceMappingURL=dispatcher.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatcher.d.mts","names":[],"sources":["../src/dispatcher.ts"],"mappings":";;iBAqEgB,oCAAoC,IAAI"}
@@ -0,0 +1,2 @@
1
+ import{createWorkflowPiExtension as e}from"./workflow/piExtension.mjs";import{SUBAGENT_PARENT_SESSION_ENV as t,readChildProcessContext as n,resolveRootSessionId as r}from"@agimon-ai/doompi-extension-contracts/child-process";import{createProtocolRuntime as i}from"@agimon-ai/doompi-extension-contracts/protocol";import{WorkflowModeChanged as a}from"@agimon-ai/doompi-extension-contracts/workflow";const o=new Set([`list_workflows`,`launch_workflow`]),s=new Set([`launch_workflow`]);function c(e,t){let n=Reflect.get(e,t,e);return typeof n==`function`?n.bind(e):n}function l(e,t){let n=new Proxy(e.sessionManager,{get(e,n){return n===`getSessionId`?()=>t:c(e,n)}});return new Proxy(e,{get(e,t){return t===`sessionManager`?n:c(e,t)}})}function u(e,t,n=process.env){let i=t?r(t,n):void 0;return new Proxy(e,{get(e,t){return t===`setActiveTools`?t=>e.setActiveTools(t.filter(e=>e!==`workflow_run`)):t===`registerTool`?t=>{if(o.has(t.name)){if(!i||!s.has(t.name)){e.registerTool(t);return}e.registerTool({...t,execute:(e,n,r,a,o)=>t.execute(e,n,r,a,l(o,i))})}}:c(e,t)}})}function d(e){return n(e)?.parentSessionId??(e[t]?.trim()||void 0)}function f(t){let n=d(process.env),o=n?r(n):void 0;e()(u(t,o)),t.on(`session_start`,(e,n)=>{let s=r(n.sessionManager.getSessionId());i({bus:t.events,source:`@agimon-ai/doompi-workflow/dispatcher`,sessionId:s}).notify(a,{mode:`on`,...o?{parentSessionId:o}:{}})})}export{u as createDispatcherBridge,f as registerWorkflowDispatcherExtension,d as resolveDispatcherParentSession};
2
+ //# sourceMappingURL=dispatcher.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatcher.mjs","names":[],"sources":["../src/dispatcher.ts"],"sourcesContent":["import {\n readChildProcessContext,\n resolveRootSessionId,\n SUBAGENT_PARENT_SESSION_ENV,\n} from '@agimon-ai/doompi-extension-contracts/child-process';\nimport { createProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport { WorkflowModeChanged } from '@agimon-ai/doompi-extension-contracts/workflow';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { createWorkflowPiExtension } from './workflow/piExtension.ts';\n\ntype RegisteredTool = Parameters<ExtensionAPI['registerTool']>[0];\nconst DISPATCHER_TOOL_NAMES = new Set(['list_workflows', 'launch_workflow']);\nconst ROOT_SCOPED_TOOL_NAMES = new Set(['launch_workflow']);\n\nfunction boundValue(target: object, property: PropertyKey): unknown {\n const value = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n}\n\nfunction withParentSession(ctx: ExtensionContext, parentSessionId: string): ExtensionContext {\n const sessionManager = new Proxy(ctx.sessionManager, {\n get(target, property) {\n if (property === 'getSessionId') return () => parentSessionId;\n return boundValue(target, property);\n },\n });\n return new Proxy(ctx, {\n get(target, property) {\n if (property === 'sessionManager') return sessionManager;\n return boundValue(target, property);\n },\n });\n}\n\nexport function createDispatcherBridge(\n pi: ExtensionAPI,\n parentSessionId: string | undefined,\n environment: Readonly<Record<string, string | undefined>> = process.env,\n): ExtensionAPI {\n const rootSessionId = parentSessionId ? resolveRootSessionId(parentSessionId, environment) : undefined;\n return new Proxy(pi, {\n get(target, property) {\n if (property === 'setActiveTools') {\n return (names: string[]): void => target.setActiveTools(names.filter((name) => name !== 'workflow_run'));\n }\n if (property !== 'registerTool') return boundValue(target, property);\n return (tool: RegisteredTool): void => {\n if (!DISPATCHER_TOOL_NAMES.has(tool.name)) return;\n if (!rootSessionId || !ROOT_SCOPED_TOOL_NAMES.has(tool.name)) {\n target.registerTool(tool);\n return;\n }\n target.registerTool({\n ...tool,\n execute: (toolCallId, params, signal, onUpdate, ctx) =>\n tool.execute(toolCallId, params, signal, onUpdate, withParentSession(ctx, rootSessionId)),\n });\n };\n },\n });\n}\n\nexport function resolveDispatcherParentSession(\n environment: Readonly<Record<string, string | undefined>>,\n): string | undefined {\n const childContext = readChildProcessContext(environment);\n return childContext?.parentSessionId ?? (environment[SUBAGENT_PARENT_SESSION_ENV]?.trim() || undefined);\n}\n\nexport function registerWorkflowDispatcherExtension(pi: ExtensionAPI): void {\n const parentSessionId = resolveDispatcherParentSession(process.env);\n const rootSessionId = parentSessionId ? resolveRootSessionId(parentSessionId) : undefined;\n createWorkflowPiExtension()(createDispatcherBridge(pi, rootSessionId));\n\n pi.on('session_start', (_event, ctx) => {\n const sessionId = resolveRootSessionId(ctx.sessionManager.getSessionId());\n createProtocolRuntime({\n bus: pi.events,\n source: '@agimon-ai/doompi-workflow/dispatcher',\n sessionId,\n }).notify(WorkflowModeChanged, { mode: 'on', ...(rootSessionId ? { parentSessionId: rootSessionId } : {}) });\n });\n}\n"],"mappings":"4YAWA,MAAM,EAAwB,IAAI,IAAI,CAAC,iBAAkB,iBAAiB,CAAC,EACrE,EAAyB,IAAI,IAAI,CAAC,iBAAiB,CAAC,EAE1D,SAAS,EAAW,EAAgB,EAAgC,CAClE,IAAM,EAAQ,QAAQ,IAAI,EAAQ,EAAU,CAAM,EAClD,OAAO,OAAO,GAAU,WAAa,EAAM,KAAK,CAAM,EAAI,CAC5D,CAEA,SAAS,EAAkB,EAAuB,EAA2C,CAC3F,IAAM,EAAiB,IAAI,MAAM,EAAI,eAAgB,CACnD,IAAI,EAAQ,EAAU,CAEpB,OADI,IAAa,mBAA6B,EACvC,EAAW,EAAQ,CAAQ,CACpC,CACF,CAAC,EACD,OAAO,IAAI,MAAM,EAAK,CACpB,IAAI,EAAQ,EAAU,CAEpB,OADI,IAAa,iBAAyB,EACnC,EAAW,EAAQ,CAAQ,CACpC,CACF,CAAC,CACH,CAEA,SAAgB,EACd,EACA,EACA,EAA4D,QAAQ,IACtD,CACd,IAAM,EAAgB,EAAkB,EAAqB,EAAiB,CAAW,EAAI,IAAA,GAC7F,OAAO,IAAI,MAAM,EAAI,CACnB,IAAI,EAAQ,EAAU,CAKpB,OAJI,IAAa,iBACP,GAA0B,EAAO,eAAe,EAAM,OAAQ,GAAS,IAAS,cAAc,CAAC,EAErG,IAAa,eACT,GAA+B,CAChC,KAAsB,IAAI,EAAK,IAAI,EACxC,IAAI,CAAC,GAAiB,CAAC,EAAuB,IAAI,EAAK,IAAI,EAAG,CAC5D,EAAO,aAAa,CAAI,EACxB,MACF,CACA,EAAO,aAAa,CAClB,GAAG,EACH,SAAU,EAAY,EAAQ,EAAQ,EAAU,IAC9C,EAAK,QAAQ,EAAY,EAAQ,EAAQ,EAAU,EAAkB,EAAK,CAAa,CAAC,CAC5F,CAAC,CALD,CAMF,EAZwC,EAAW,EAAQ,CAAQ,CAarE,CACF,CAAC,CACH,CAEA,SAAgB,EACd,EACoB,CAEpB,OADqB,EAAwB,CAC3B,CAAC,EAAE,kBAAoB,EAAY,EAA4B,EAAE,KAAK,GAAK,IAAA,GAC/F,CAEA,SAAgB,EAAoC,EAAwB,CAC1E,IAAM,EAAkB,EAA+B,QAAQ,GAAG,EAC5D,EAAgB,EAAkB,EAAqB,CAAe,EAAI,IAAA,GAChF,EAA0B,CAAC,CAAC,EAAuB,EAAI,CAAa,CAAC,EAErE,EAAG,GAAG,iBAAkB,EAAQ,IAAQ,CACtC,IAAM,EAAY,EAAqB,EAAI,eAAe,aAAa,CAAC,EACxE,EAAsB,CACpB,IAAK,EAAG,OACR,OAAQ,wCACR,WACF,CAAC,CAAC,CAAC,OAAO,EAAqB,CAAE,KAAM,KAAM,GAAI,EAAgB,CAAE,gBAAiB,CAAc,EAAI,CAAC,CAAG,CAAC,CAC7G,CAAC,CACH"}
@@ -0,0 +1,2 @@
1
+ const e=require("./leader.cjs"),t=require("./workflow/piExtension.cjs");function n(n){let r=e.registerLeaderContribution(n);t.createWorkflowPiExtension({onModeChange:e=>r.setMode(e)})(n)}exports.registerWorkflowExtension=n;
2
+ //# sourceMappingURL=extension.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.cjs","names":["registerLeaderContribution"],"sources":["../src/extension.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { registerLeaderContribution } from './leader.ts';\nimport { createWorkflowPiExtension } from './workflow/piExtension.ts';\n\nexport function registerWorkflowExtension(pi: ExtensionAPI): void {\n const leader = registerLeaderContribution(pi);\n createWorkflowPiExtension({ onModeChange: (enabled) => leader.setMode(enabled) })(pi);\n}\n"],"mappings":"wEAIA,SAAgB,EAA0B,EAAwB,CAChE,IAAM,EAASA,EAAAA,2BAA2B,CAAE,EAC5C,EAAA,0BAA0B,CAAE,aAAe,GAAY,EAAO,QAAQ,CAAO,CAAE,CAAC,CAAC,CAAC,CAAE,CACtF"}
@@ -0,0 +1,6 @@
1
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ //#region src/extension.d.ts
3
+ declare function registerWorkflowExtension(pi: ExtensionAPI): void;
4
+ //#endregion
5
+ export { registerWorkflowExtension };
6
+ //# sourceMappingURL=extension.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.d.cts","names":[],"sources":["../src/extension.ts"],"mappings":";;iBAIgB,0BAA0B,IAAI"}
@@ -0,0 +1,6 @@
1
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ //#region src/extension.d.ts
3
+ declare function registerWorkflowExtension(pi: ExtensionAPI): void;
4
+ //#endregion
5
+ export { registerWorkflowExtension };
6
+ //# sourceMappingURL=extension.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.d.mts","names":[],"sources":["../src/extension.ts"],"mappings":";;iBAIgB,0BAA0B,IAAI"}
@@ -0,0 +1,2 @@
1
+ import{registerLeaderContribution as e}from"./leader.mjs";import{createWorkflowPiExtension as t}from"./workflow/piExtension.mjs";function n(n){let r=e(n);t({onModeChange:e=>r.setMode(e)})(n)}export{n as registerWorkflowExtension};
2
+ //# sourceMappingURL=extension.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.mjs","names":[],"sources":["../src/extension.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { registerLeaderContribution } from './leader.ts';\nimport { createWorkflowPiExtension } from './workflow/piExtension.ts';\n\nexport function registerWorkflowExtension(pi: ExtensionAPI): void {\n const leader = registerLeaderContribution(pi);\n createWorkflowPiExtension({ onModeChange: (enabled) => leader.setMode(enabled) })(pi);\n}\n"],"mappings":"iIAIA,SAAgB,EAA0B,EAAwB,CAChE,IAAM,EAAS,EAA2B,CAAE,EAC5C,EAA0B,CAAE,aAAe,GAAY,EAAO,QAAQ,CAAO,CAAE,CAAC,CAAC,CAAC,CAAE,CACtF"}
@@ -0,0 +1,2 @@
1
+ var e=require("../dispatcher.cjs").registerWorkflowDispatcherExtension;module.exports=e;
2
+ //# sourceMappingURL=dispatcher.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatcher.cjs","names":["registerWorkflowDispatcherExtension"],"sources":["../../src/extensions/dispatcher.ts"],"sourcesContent":["import { registerWorkflowDispatcherExtension } from '../dispatcher.ts';\n\nexport default registerWorkflowDispatcherExtension;\n"],"mappings":"AAEA,IAAA,6BAAeA,CAAAA,CAAAA"}
@@ -0,0 +1,2 @@
1
+ import { registerWorkflowDispatcherExtension } from "../dispatcher.cjs";
2
+ export = registerWorkflowDispatcherExtension;
@@ -0,0 +1,2 @@
1
+ import { registerWorkflowDispatcherExtension } from "../dispatcher.mjs";
2
+ export { registerWorkflowDispatcherExtension as default };
@@ -0,0 +1,2 @@
1
+ import{registerWorkflowDispatcherExtension as e}from"../dispatcher.mjs";var t=e;export{t as default};
2
+ //# sourceMappingURL=dispatcher.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatcher.mjs","names":[],"sources":["../../src/extensions/dispatcher.ts"],"sourcesContent":["import { registerWorkflowDispatcherExtension } from '../dispatcher.ts';\n\nexport default registerWorkflowDispatcherExtension;\n"],"mappings":"wEAEA,IAAA,EAAe"}
@@ -0,0 +1,2 @@
1
+ var e=require("../extension.cjs").registerWorkflowExtension;module.exports=e;
2
+ //# sourceMappingURL=pi.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pi.cjs","names":["registerWorkflowExtension"],"sources":["../../src/extensions/pi.ts"],"sourcesContent":["import { registerWorkflowExtension } from '../extension.ts';\n\nexport default registerWorkflowExtension;\n"],"mappings":"AAEA,IAAA,4BAAeA,CAAAA,CAAAA"}
@@ -0,0 +1,2 @@
1
+ import { registerWorkflowExtension } from "../extension.cjs";
2
+ export = registerWorkflowExtension;
@@ -0,0 +1,2 @@
1
+ import { registerWorkflowExtension } from "../extension.mjs";
2
+ export { registerWorkflowExtension as default };
@@ -0,0 +1,2 @@
1
+ import{registerWorkflowExtension as e}from"../extension.mjs";var t=e;export{t as default};
2
+ //# sourceMappingURL=pi.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pi.mjs","names":[],"sources":["../../src/extensions/pi.ts"],"sourcesContent":["import { registerWorkflowExtension } from '../extension.ts';\n\nexport default registerWorkflowExtension;\n"],"mappings":"6DAEA,IAAA,EAAe"}
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./extension.cjs"),t=require("./dispatcher.cjs");exports.registerWorkflowDispatcherExtension=t.registerWorkflowDispatcherExtension,exports.registerWorkflowExtension=e.registerWorkflowExtension;
@@ -0,0 +1,3 @@
1
+ import { registerWorkflowDispatcherExtension } from "./dispatcher.cjs";
2
+ import { registerWorkflowExtension } from "./extension.cjs";
3
+ export { registerWorkflowDispatcherExtension, registerWorkflowExtension };
@@ -0,0 +1,3 @@
1
+ import { registerWorkflowDispatcherExtension } from "./dispatcher.mjs";
2
+ import { registerWorkflowExtension } from "./extension.mjs";
3
+ export { registerWorkflowDispatcherExtension, registerWorkflowExtension };
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import{registerWorkflowExtension as e}from"./extension.mjs";import{registerWorkflowDispatcherExtension as t}from"./dispatcher.mjs";export{t as registerWorkflowDispatcherExtension,e as registerWorkflowExtension};
@@ -0,0 +1,2 @@
1
+ const e=require("./types.cjs");let t=require("@agimon-ai/doompi-extension-contracts/leader");const n=`doom-workflow`,r={key:`w`,label:e.LEADER_LABEL,order:50};function i(t){return[{id:`${n}.manage`,path:[r,{key:`l`,label:`manage`,detail:`running workflows`}],action:{name:e.LEADER_MANAGE_ACTION}},{id:`${n}.launch`,path:[r,{key:`w`,label:`launch`,detail:`start a workflow`}],action:{name:e.LEADER_LAUNCH_ACTION}},t?{id:`${n}.disable`,path:[r,{key:`e`,label:`exit`,detail:`take back workflow tools`}],action:{name:e.LEADER_DISABLE_ACTION}}:{id:`${n}.enable`,path:[r,{key:`e`,label:`enter`,detail:`give the agent workflow tools`}],action:{name:e.LEADER_ENABLE_ACTION}},{id:`${n}.recover`,path:[r,{key:`r`,label:`recover`,detail:`failed workflow`}],action:{name:e.LEADER_RECOVER_ACTION}}]}function a(n){let r=(0,t.createDoomLeaderContribution)(n,{source:e.PACKAGE_SOURCE,bindings:i(!1)});return{setMode(e){r.update(i(e))},dispose(){r.dispose()}}}exports.registerLeaderContribution=a,exports.workflowLeaderBindings=i;
2
+ //# sourceMappingURL=leader.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leader.cjs","names":["LEADER_LABEL","LEADER_MANAGE_ACTION","LEADER_LAUNCH_ACTION","LEADER_DISABLE_ACTION","LEADER_ENABLE_ACTION","LEADER_RECOVER_ACTION","createDoomLeaderContribution","PACKAGE_SOURCE"],"sources":["../src/leader.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { createDoomLeaderContribution, type LeaderBinding } from '@agimon-ai/doompi-extension-contracts/leader';\nimport {\n LEADER_DISABLE_ACTION,\n LEADER_ENABLE_ACTION,\n LEADER_KEY,\n LEADER_LABEL,\n LEADER_LAUNCH_ACTION,\n LEADER_MANAGE_ACTION,\n LEADER_ORDER,\n LEADER_RECOVER_ACTION,\n PACKAGE_SOURCE,\n} from './types.ts';\n\nconst LEADER_BINDING_PREFIX = 'doom-workflow';\nconst GROUP_SEGMENT = { key: LEADER_KEY, label: LEADER_LABEL, order: LEADER_ORDER } as const;\n\nexport interface WorkflowLeaderHandle {\n /** Republish the menu for the mode's current state. */\n setMode(enabled: boolean): void;\n dispose(): void;\n}\n\n/**\n * The menu as it stands with the mode on or off.\n *\n * One entry on `e` rather than an enable row beside a disable row: only one of\n * the two ever does anything, and a menu that prints both makes the reader\n * check the mode line to find out which. The label says which way it flips.\n */\nexport function workflowLeaderBindings(mode: boolean): LeaderBinding[] {\n return [\n {\n id: `${LEADER_BINDING_PREFIX}.manage`,\n path: [GROUP_SEGMENT, { key: 'l', label: 'manage', detail: 'running workflows' }],\n action: { name: LEADER_MANAGE_ACTION },\n },\n {\n id: `${LEADER_BINDING_PREFIX}.launch`,\n path: [GROUP_SEGMENT, { key: 'w', label: 'launch', detail: 'start a workflow' }],\n action: { name: LEADER_LAUNCH_ACTION },\n },\n mode\n ? {\n id: `${LEADER_BINDING_PREFIX}.disable`,\n path: [GROUP_SEGMENT, { key: 'e', label: 'exit', detail: 'take back workflow tools' }],\n action: { name: LEADER_DISABLE_ACTION },\n }\n : {\n id: `${LEADER_BINDING_PREFIX}.enable`,\n path: [GROUP_SEGMENT, { key: 'e', label: 'enter', detail: 'give the agent workflow tools' }],\n action: { name: LEADER_ENABLE_ACTION },\n },\n {\n id: `${LEADER_BINDING_PREFIX}.recover`,\n path: [GROUP_SEGMENT, { key: 'r', label: 'recover', detail: 'failed workflow' }],\n action: { name: LEADER_RECOVER_ACTION },\n },\n ];\n}\n\nexport function registerLeaderContribution(pi: Pick<ExtensionAPI, 'events' | 'on'>): WorkflowLeaderHandle {\n const contribution = createDoomLeaderContribution(pi, {\n source: PACKAGE_SOURCE,\n bindings: workflowLeaderBindings(false),\n });\n return {\n setMode(enabled) {\n contribution.update(workflowLeaderBindings(enabled));\n },\n dispose() {\n contribution.dispose();\n },\n };\n}\n"],"mappings":"6FAcA,MAAM,EAAwB,gBACxB,EAAgB,CAAE,IAAA,IAAiB,MAAOA,EAAAA,aAAc,MAAA,EAAoB,EAelF,SAAgB,EAAuB,EAAgC,CACrE,MAAO,CACL,CACE,GAAI,GAAG,EAAsB,SAC7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,SAAU,OAAQ,mBAAoB,CAAC,EAChF,OAAQ,CAAE,KAAMC,EAAAA,oBAAqB,CACvC,EACA,CACE,GAAI,GAAG,EAAsB,SAC7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,SAAU,OAAQ,kBAAmB,CAAC,EAC/E,OAAQ,CAAE,KAAMC,EAAAA,oBAAqB,CACvC,EACA,EACI,CACE,GAAI,GAAG,EAAsB,UAC7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,0BAA2B,CAAC,EACrF,OAAQ,CAAE,KAAMC,EAAAA,qBAAsB,CACxC,EACA,CACE,GAAI,GAAG,EAAsB,SAC7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,QAAS,OAAQ,+BAAgC,CAAC,EAC3F,OAAQ,CAAE,KAAMC,EAAAA,oBAAqB,CACvC,EACJ,CACE,GAAI,GAAG,EAAsB,UAC7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,UAAW,OAAQ,iBAAkB,CAAC,EAC/E,OAAQ,CAAE,KAAMC,EAAAA,qBAAsB,CACxC,CACF,CACF,CAEA,SAAgB,EAA2B,EAA+D,CACxG,IAAM,GAAA,EAAeC,EAAAA,6BAAAA,CAA6B,EAAI,CACpD,OAAQC,EAAAA,eACR,SAAU,EAAuB,EAAK,CACxC,CAAC,EACD,MAAO,CACL,QAAQ,EAAS,CACf,EAAa,OAAO,EAAuB,CAAO,CAAC,CACrD,EACA,SAAU,CACR,EAAa,QAAQ,CACvB,CACF,CACF"}
@@ -0,0 +1,2 @@
1
+ import{LEADER_DISABLE_ACTION as e,LEADER_ENABLE_ACTION as t,LEADER_LABEL as n,LEADER_LAUNCH_ACTION as r,LEADER_MANAGE_ACTION as i,LEADER_RECOVER_ACTION as a,PACKAGE_SOURCE as o}from"./types.mjs";import{createDoomLeaderContribution as s}from"@agimon-ai/doompi-extension-contracts/leader";const c=`doom-workflow`,l={key:`w`,label:n,order:50};function u(n){return[{id:`${c}.manage`,path:[l,{key:`l`,label:`manage`,detail:`running workflows`}],action:{name:i}},{id:`${c}.launch`,path:[l,{key:`w`,label:`launch`,detail:`start a workflow`}],action:{name:r}},n?{id:`${c}.disable`,path:[l,{key:`e`,label:`exit`,detail:`take back workflow tools`}],action:{name:e}}:{id:`${c}.enable`,path:[l,{key:`e`,label:`enter`,detail:`give the agent workflow tools`}],action:{name:t}},{id:`${c}.recover`,path:[l,{key:`r`,label:`recover`,detail:`failed workflow`}],action:{name:a}}]}function d(e){let t=s(e,{source:o,bindings:u(!1)});return{setMode(e){t.update(u(e))},dispose(){t.dispose()}}}export{d as registerLeaderContribution,u as workflowLeaderBindings};
2
+ //# sourceMappingURL=leader.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leader.mjs","names":[],"sources":["../src/leader.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { createDoomLeaderContribution, type LeaderBinding } from '@agimon-ai/doompi-extension-contracts/leader';\nimport {\n LEADER_DISABLE_ACTION,\n LEADER_ENABLE_ACTION,\n LEADER_KEY,\n LEADER_LABEL,\n LEADER_LAUNCH_ACTION,\n LEADER_MANAGE_ACTION,\n LEADER_ORDER,\n LEADER_RECOVER_ACTION,\n PACKAGE_SOURCE,\n} from './types.ts';\n\nconst LEADER_BINDING_PREFIX = 'doom-workflow';\nconst GROUP_SEGMENT = { key: LEADER_KEY, label: LEADER_LABEL, order: LEADER_ORDER } as const;\n\nexport interface WorkflowLeaderHandle {\n /** Republish the menu for the mode's current state. */\n setMode(enabled: boolean): void;\n dispose(): void;\n}\n\n/**\n * The menu as it stands with the mode on or off.\n *\n * One entry on `e` rather than an enable row beside a disable row: only one of\n * the two ever does anything, and a menu that prints both makes the reader\n * check the mode line to find out which. The label says which way it flips.\n */\nexport function workflowLeaderBindings(mode: boolean): LeaderBinding[] {\n return [\n {\n id: `${LEADER_BINDING_PREFIX}.manage`,\n path: [GROUP_SEGMENT, { key: 'l', label: 'manage', detail: 'running workflows' }],\n action: { name: LEADER_MANAGE_ACTION },\n },\n {\n id: `${LEADER_BINDING_PREFIX}.launch`,\n path: [GROUP_SEGMENT, { key: 'w', label: 'launch', detail: 'start a workflow' }],\n action: { name: LEADER_LAUNCH_ACTION },\n },\n mode\n ? {\n id: `${LEADER_BINDING_PREFIX}.disable`,\n path: [GROUP_SEGMENT, { key: 'e', label: 'exit', detail: 'take back workflow tools' }],\n action: { name: LEADER_DISABLE_ACTION },\n }\n : {\n id: `${LEADER_BINDING_PREFIX}.enable`,\n path: [GROUP_SEGMENT, { key: 'e', label: 'enter', detail: 'give the agent workflow tools' }],\n action: { name: LEADER_ENABLE_ACTION },\n },\n {\n id: `${LEADER_BINDING_PREFIX}.recover`,\n path: [GROUP_SEGMENT, { key: 'r', label: 'recover', detail: 'failed workflow' }],\n action: { name: LEADER_RECOVER_ACTION },\n },\n ];\n}\n\nexport function registerLeaderContribution(pi: Pick<ExtensionAPI, 'events' | 'on'>): WorkflowLeaderHandle {\n const contribution = createDoomLeaderContribution(pi, {\n source: PACKAGE_SOURCE,\n bindings: workflowLeaderBindings(false),\n });\n return {\n setMode(enabled) {\n contribution.update(workflowLeaderBindings(enabled));\n },\n dispose() {\n contribution.dispose();\n },\n };\n}\n"],"mappings":"+RAcA,MAAM,EAAwB,gBACxB,EAAgB,CAAE,IAAA,IAAiB,MAAO,EAAc,MAAA,EAAoB,EAelF,SAAgB,EAAuB,EAAgC,CACrE,MAAO,CACL,CACE,GAAI,GAAG,EAAsB,SAC7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,SAAU,OAAQ,mBAAoB,CAAC,EAChF,OAAQ,CAAE,KAAM,CAAqB,CACvC,EACA,CACE,GAAI,GAAG,EAAsB,SAC7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,SAAU,OAAQ,kBAAmB,CAAC,EAC/E,OAAQ,CAAE,KAAM,CAAqB,CACvC,EACA,EACI,CACE,GAAI,GAAG,EAAsB,UAC7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,0BAA2B,CAAC,EACrF,OAAQ,CAAE,KAAM,CAAsB,CACxC,EACA,CACE,GAAI,GAAG,EAAsB,SAC7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,QAAS,OAAQ,+BAAgC,CAAC,EAC3F,OAAQ,CAAE,KAAM,CAAqB,CACvC,EACJ,CACE,GAAI,GAAG,EAAsB,UAC7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,UAAW,OAAQ,iBAAkB,CAAC,EAC/E,OAAQ,CAAE,KAAM,CAAsB,CACxC,CACF,CACF,CAEA,SAAgB,EAA2B,EAA+D,CACxG,IAAM,EAAe,EAA6B,EAAI,CACpD,OAAQ,EACR,SAAU,EAAuB,EAAK,CACxC,CAAC,EACD,MAAO,CACL,QAAQ,EAAS,CACf,EAAa,OAAO,EAAuB,CAAO,CAAC,CACrD,EACA,SAAU,CACR,EAAa,QAAQ,CACvB,CACF,CACF"}
package/dist/types.cjs ADDED
@@ -0,0 +1,2 @@
1
+ exports.LEADER_DISABLE_ACTION=`workflow.disable`,exports.LEADER_ENABLE_ACTION=`workflow.enable`,exports.LEADER_LABEL=`workflows`,exports.LEADER_LAUNCH_ACTION=`workflow.launch`,exports.LEADER_MANAGE_ACTION=`workflow.manage`,exports.LEADER_RECOVER_ACTION=`workflow.recover`,exports.PACKAGE_SOURCE=`@agimon-ai/doompi-workflow`;
2
+ //# sourceMappingURL=types.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["export const PACKAGE_SOURCE = '@agimon-ai/doompi-workflow';\nexport const COMMAND_DESCRIPTION = 'Doom Pi workflow execution and monitoring integration';\nexport const LEADER_KEY = 'w';\nexport const LEADER_LABEL = 'workflows';\nexport const LEADER_ORDER = 50;\nexport const LEADER_ENABLE_ACTION = 'workflow.enable';\nexport const LEADER_DISABLE_ACTION = 'workflow.disable';\nexport const LEADER_MANAGE_ACTION = 'workflow.manage';\nexport const LEADER_LAUNCH_ACTION = 'workflow.launch';\nexport const LEADER_RECOVER_ACTION = 'workflow.recover';\n"],"mappings":""}
package/dist/types.mjs ADDED
@@ -0,0 +1,2 @@
1
+ const e=`@agimon-ai/doompi-workflow`,t=`workflows`,n=`workflow.enable`,r=`workflow.disable`,i=`workflow.manage`,a=`workflow.launch`,o=`workflow.recover`;export{r as LEADER_DISABLE_ACTION,n as LEADER_ENABLE_ACTION,t as LEADER_LABEL,a as LEADER_LAUNCH_ACTION,i as LEADER_MANAGE_ACTION,o as LEADER_RECOVER_ACTION,e as PACKAGE_SOURCE};
2
+ //# sourceMappingURL=types.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.mjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["export const PACKAGE_SOURCE = '@agimon-ai/doompi-workflow';\nexport const COMMAND_DESCRIPTION = 'Doom Pi workflow execution and monitoring integration';\nexport const LEADER_KEY = 'w';\nexport const LEADER_LABEL = 'workflows';\nexport const LEADER_ORDER = 50;\nexport const LEADER_ENABLE_ACTION = 'workflow.enable';\nexport const LEADER_DISABLE_ACTION = 'workflow.disable';\nexport const LEADER_MANAGE_ACTION = 'workflow.manage';\nexport const LEADER_LAUNCH_ACTION = 'workflow.launch';\nexport const LEADER_RECOVER_ACTION = 'workflow.recover';\n"],"mappings":"AAAA,MAAa,EAAiB,6BAGjB,EAAe,YAEf,EAAuB,kBACvB,EAAwB,mBACxB,EAAuB,kBACvB,EAAuB,kBACvB,EAAwB"}
@@ -0,0 +1,2 @@
1
+ let e=require("@agimon-ai/doompi-extension-contracts/background-work");function t(t){let n=[],r=(0,e.registerBackgroundWorkProvider)(t,{provider:`workflow-mcp`,listActiveWork:()=>n});return{update(e){n=e,r.update()},dispose(){n=[],r.dispose()}}}exports.registerRunProvider=t;
2
+ //# sourceMappingURL=backgroundWork.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backgroundWork.cjs","names":["registerBackgroundWorkProvider"],"sources":["../../src/workflow/backgroundWork.ts"],"sourcesContent":["/**\n * Background-work registration for workflow runs.\n *\n * DESIGN PATTERNS:\n * - Pi event-bus integration through the shared typed protocol\n * - Snapshot pattern: the extension pushes the current run set, the provider\n * only reads it\n *\n * CODING STANDARDS:\n * - Named exports only\n * - Explicit return types on exported functions\n *\n * AVOID:\n * - Process-global symbols or dynamic imports for extension communication\n * - Reporting runs owned by another Pi session\n */\n\nimport {\n registerBackgroundWorkProvider,\n type BackgroundWorkProviderHandle,\n} from '@agimon-ai/doompi-extension-contracts/background-work';\nimport type { ProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\n\nexport interface RunProviderItem {\n id: string;\n sessionId: string;\n}\n\nexport interface RunProviderHandle {\n /** Replace the reported run set. */\n update(items: RunProviderItem[]): void;\n /** Remove the registration. Safe to call when registration never happened. */\n dispose(): void;\n}\n\nconst PROVIDER_NAME = 'workflow-mcp';\n\n/**\n * Register a background-work provider for this session's workflow runs.\n *\n * The runtime pins all announcements to the active Pi session and validates\n * each snapshot before it reaches another extension.\n */\nexport function registerRunProvider(runtime: ProtocolRuntime): RunProviderHandle {\n let items: RunProviderItem[] = [];\n const provider: BackgroundWorkProviderHandle = registerBackgroundWorkProvider(runtime, {\n provider: PROVIDER_NAME,\n listActiveWork: () => items,\n });\n const handle: RunProviderHandle = {\n update(next) {\n items = next;\n provider.update();\n },\n dispose() {\n items = [];\n provider.dispose();\n },\n };\n return handle;\n}\n"],"mappings":"uEA2CA,SAAgB,EAAoB,EAA6C,CAC/E,IAAI,EAA2B,CAAC,EAC1B,GAAA,EAAyCA,EAAAA,+BAAAA,CAA+B,EAAS,CACrF,SAAU,eACV,mBAAsB,CACxB,CAAC,EAWD,MAAO,CATL,OAAO,EAAM,CACX,EAAQ,EACR,EAAS,OAAO,CAClB,EACA,SAAU,CACR,EAAQ,CAAC,EACT,EAAS,QAAQ,CACnB,CAEU,CACd"}
@@ -0,0 +1,2 @@
1
+ import{registerBackgroundWorkProvider as e}from"@agimon-ai/doompi-extension-contracts/background-work";function t(t){let n=[],r=e(t,{provider:`workflow-mcp`,listActiveWork:()=>n});return{update(e){n=e,r.update()},dispose(){n=[],r.dispose()}}}export{t as registerRunProvider};
2
+ //# sourceMappingURL=backgroundWork.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backgroundWork.mjs","names":[],"sources":["../../src/workflow/backgroundWork.ts"],"sourcesContent":["/**\n * Background-work registration for workflow runs.\n *\n * DESIGN PATTERNS:\n * - Pi event-bus integration through the shared typed protocol\n * - Snapshot pattern: the extension pushes the current run set, the provider\n * only reads it\n *\n * CODING STANDARDS:\n * - Named exports only\n * - Explicit return types on exported functions\n *\n * AVOID:\n * - Process-global symbols or dynamic imports for extension communication\n * - Reporting runs owned by another Pi session\n */\n\nimport {\n registerBackgroundWorkProvider,\n type BackgroundWorkProviderHandle,\n} from '@agimon-ai/doompi-extension-contracts/background-work';\nimport type { ProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\n\nexport interface RunProviderItem {\n id: string;\n sessionId: string;\n}\n\nexport interface RunProviderHandle {\n /** Replace the reported run set. */\n update(items: RunProviderItem[]): void;\n /** Remove the registration. Safe to call when registration never happened. */\n dispose(): void;\n}\n\nconst PROVIDER_NAME = 'workflow-mcp';\n\n/**\n * Register a background-work provider for this session's workflow runs.\n *\n * The runtime pins all announcements to the active Pi session and validates\n * each snapshot before it reaches another extension.\n */\nexport function registerRunProvider(runtime: ProtocolRuntime): RunProviderHandle {\n let items: RunProviderItem[] = [];\n const provider: BackgroundWorkProviderHandle = registerBackgroundWorkProvider(runtime, {\n provider: PROVIDER_NAME,\n listActiveWork: () => items,\n });\n const handle: RunProviderHandle = {\n update(next) {\n items = next;\n provider.update();\n },\n dispose() {\n items = [];\n provider.dispose();\n },\n };\n return handle;\n}\n"],"mappings":"uGA2CA,SAAgB,EAAoB,EAA6C,CAC/E,IAAI,EAA2B,CAAC,EAC1B,EAAyC,EAA+B,EAAS,CACrF,SAAU,eACV,mBAAsB,CACxB,CAAC,EAWD,MAAO,CATL,OAAO,EAAM,CACX,EAAQ,EACR,EAAS,OAAO,CAClB,EACA,SAAU,CACR,EAAQ,CAAC,EACT,EAAS,QAAQ,CACnB,CAEU,CACd"}
@@ -0,0 +1,2 @@
1
+ const e=require("./rendering.cjs");let t=require("@earendil-works/pi-tui");const n=`borderMuted`,r=`accent`,i=[`╭`,`╮`],a=[`├`,`┤`],o=[`╰`,`╯`],s={overlay:!0,overlayOptions:{anchor:`top-left`,width:`100%`,maxHeight:`100%`,margin:0}};function c(e,n){return!e||n<=0||n<Math.min((0,t.visibleWidth)(e),8)?``:(0,t.truncateToWidth)(e,n,`…`)}var l=class{tui;theme;constructor(e,t){this.tui=e,this.theme=t}invalidate(){this.tui.requestRender()}render(t){let r=Math.max(0,Math.floor(t)),s=Math.max(0,Math.floor(this.tui.terminal?.rows??24));if(r===0||s===0)return[];let c=this.getChrome();if(s<6||r<8)return this.renderCompact(c,r,s);let l=r-2,u=l-2,d=s-6,f=this.renderBody(u,d).slice(0,d);for(;f.length<d;)f.push(``);let p=c.accent??n;return[this.borderRow(i,l,p),this.contentRow(this.headerRow(c,u),u,r,p),this.borderRow(a,l,p,n),...f.map(e=>this.contentRow(e,u,r,p)),this.borderRow(a,l,p,n),this.contentRow(this.footerRow(c,u),u,r,p),this.borderRow(o,l,p)].map(t=>e.padLine(t,r))}borderRow([e,t],r,i=n,a=i){let o=i??n;return[this.theme.fg(o,e),this.theme.fg(a??o,`─`.repeat(r)),this.theme.fg(o,t)].join(``)}contentRow(t,r,i,a=n){let o=this.theme.fg(a??n,`│`);return e.frameLine(` ${e.padLine(t,r)} `,i,o,o)}headerRow(e,n){let i=e.accent,a=i?n-2:n,o=(0,t.truncateToWidth)(e.title,Math.max(0,a),`…`),s=i?this.theme.inverse(this.theme.bold(this.theme.fg(i,` ${o} `))):this.theme.bold(this.theme.fg(r,o)),l=(0,t.visibleWidth)(o)+(i?2:0),u=c(e.breadcrumb,n-l-2);return u&&(s+=`${` `.repeat(2)}${this.theme.fg(`dim`,u)}`,l+=2+(0,t.visibleWidth)(u)),this.withRightStatus(s,l,e.headerRight,n)}footerRow(e,n){let r=e.footerHints?.length?this.hintLegend(e.footerHints,n):this.theme.fg(`dim`,(0,t.truncateToWidth)(e.footer,n,`…`));return this.withRightStatus(r,(0,t.visibleWidth)(r),e.footerRight,n)}hintLegend(e,n){let r=``,i=0;for(let[a,o]of e){let e=(0,t.visibleWidth)(a)+(0,t.visibleWidth)(o)+6;if(i+e>n)break;let s=this.theme.bg(`selectedBg`,this.theme.fg(`text`,` ${a} `));r+=`${r?` `:``}${s}${this.theme.fg(`dim`,` ${o}`)}`,i+=e}return r}withRightStatus(e,n,r,i){let a=r?(0,t.visibleWidth)(r):0;return!r||a===0||n+2+a>i?e:`${e}${` `.repeat(i-n-a)}${this.theme.fg(`dim`,r)}`}renderCompact(t,n,i){let a=Array.from({length:i},()=>``);return a[0]=this.theme.bold(this.theme.fg(r,t.title)),i>1&&(a[i-1]=this.theme.fg(`dim`,t.footer)),a.map(t=>e.padLine(e.fitLine(t,n),n))}};exports.DOOM_FULLSCREEN_UI_OPTIONS=s,exports.DOOM_OVERLAY_ACCENT=`mdHeading`,exports.DoomOverlay=l;
2
+ //# sourceMappingURL=doomOverlay.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doomOverlay.cjs","names":["visibleWidth","truncateToWidth","padLine","frameLine","fitLine"],"sources":["../../src/workflow/doomOverlay.ts"],"sourcesContent":["import type { Theme } from '@earendil-works/pi-coding-agent';\nimport { type Component, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport { fitLine, frameLine, padLine } from './rendering.ts';\n\nconst DEFAULT_TERMINAL_ROWS = 24;\nconst FULL_CHROME_ROWS = 6;\n/** Two frame columns and two gutter columns, leaving content worth framing. */\nconst MIN_FRAMED_WIDTH = 8;\nconst GUTTER = ' ';\nconst GUTTER_COLUMNS = 2;\n/** Blank columns kept between a row's left cluster and its right-aligned status. */\nconst ROW_GAP = 2;\n/** A secondary segment thinner than this is dropped rather than shredded into a stub. */\nconst MIN_SEGMENT_WIDTH = 8;\nconst ELLIPSIS = '…';\nconst BORDER_COLOR = 'borderMuted';\nconst TEXT_COLOR = 'text';\n/** Filled key caps read as pressable, matching the doom mockups' footers. */\nconst HINT_CAP_BACKGROUND = 'selectedBg';\nconst HINT_SEPARATOR = ' ';\n/** A cap's two pad columns plus the space before its label. */\nconst HINT_CHROME_COLUMNS = 6;\nconst TITLE_COLOR = 'accent';\nconst SECONDARY_COLOR = 'dim';\nconst HORIZONTAL_BORDER = '─';\nconst VERTICAL_BORDER = '│';\nconst TOP_CORNERS = ['╭', '╮'] as const;\nconst DIVIDER_JOINTS = ['├', '┤'] as const;\nconst BOTTOM_CORNERS = ['╰', '╯'] as const;\n\nexport interface DoomOverlayTui {\n terminal?: {\n rows: number;\n columns?: number;\n };\n requestRender(force?: boolean): void;\n}\n\nexport interface DoomOverlayChrome {\n title: string;\n breadcrumb?: string;\n headerRight?: string;\n footer: string;\n footerRight?: string;\n /**\n * Key/label pairs rendered as filled caps, e.g. `[ enter ] edit subject`.\n * Takes precedence over `footer`, which stays for surfaces that want a plain\n * sentence rather than a key legend.\n */\n footerHints?: readonly (readonly [string, string])[];\n /**\n * Signature colour for the surface. When set, the frame takes it and the\n * title renders as a filled badge rather than plain accent text, matching the\n * doom mockups. Left unset, the frame stays muted and the title plain.\n */\n accent?: Parameters<Theme['fg']>[0];\n}\n\n/**\n * One signature colour for every doom overlay: the frame and the title badge.\n * Per-surface colours were tried and read as arbitrary, and they also borrowed\n * tokens that mean something else (`warning`, `syntaxNumber`), so a theme edit\n * elsewhere would have repainted a frame. Change it here to change them all.\n */\nexport const DOOM_OVERLAY_ACCENT = 'mdHeading' satisfies DoomOverlayChrome['accent'];\n\nexport const DOOM_FULLSCREEN_UI_OPTIONS = {\n overlay: true,\n overlayOptions: {\n anchor: 'top-left',\n width: '100%',\n maxHeight: '100%',\n margin: 0,\n },\n} as const;\n\n/**\n * Fits a secondary segment into `budget`, or drops it entirely when too few\n * columns remain for it to stay readable. Truncating everything to fit is what\n * makes a tight header read as one run-on string.\n */\nfunction fitSegment(text: string | undefined, budget: number): string {\n if (!text || budget <= 0) return '';\n return budget < Math.min(visibleWidth(text), MIN_SEGMENT_WIDTH) ? '' : truncateToWidth(text, budget, ELLIPSIS);\n}\n\nexport abstract class DoomOverlay implements Component {\n protected constructor(\n protected readonly tui: DoomOverlayTui,\n protected readonly theme: Theme,\n ) {}\n\n invalidate(): void {\n this.tui.requestRender();\n }\n\n render(width: number): string[] {\n const safeWidth = Math.max(0, Math.floor(width));\n const height = Math.max(0, Math.floor(this.tui.terminal?.rows ?? DEFAULT_TERMINAL_ROWS));\n if (safeWidth === 0 || height === 0) return [];\n\n const chrome = this.getChrome();\n if (height < FULL_CHROME_ROWS || safeWidth < MIN_FRAMED_WIDTH) {\n return this.renderCompact(chrome, safeWidth, height);\n }\n\n const innerWidth = safeWidth - GUTTER_COLUMNS;\n const contentWidth = innerWidth - GUTTER_COLUMNS;\n const bodyHeight = height - FULL_CHROME_ROWS;\n const body = this.renderBody(contentWidth, bodyHeight).slice(0, bodyHeight);\n while (body.length < bodyHeight) body.push('');\n\n const frame = chrome.accent ?? BORDER_COLOR;\n return [\n this.borderRow(TOP_CORNERS, innerWidth, frame),\n this.contentRow(this.headerRow(chrome, contentWidth), contentWidth, safeWidth, frame),\n this.borderRow(DIVIDER_JOINTS, innerWidth, frame, BORDER_COLOR),\n ...body.map((line) => this.contentRow(line, contentWidth, safeWidth, frame)),\n this.borderRow(DIVIDER_JOINTS, innerWidth, frame, BORDER_COLOR),\n this.contentRow(this.footerRow(chrome, contentWidth), contentWidth, safeWidth, frame),\n this.borderRow(BOTTOM_CORNERS, innerWidth, frame),\n ].map((line) => padLine(line, safeWidth));\n }\n\n protected abstract getChrome(): DoomOverlayChrome;\n\n protected abstract renderBody(width: number, height: number): string[];\n\n /**\n * Edge glyphs and the rule between them are coloured separately: the outer\n * rectangle carries the signature colour while an internal separator stays\n * muted, meeting the frame at accent-coloured junctions.\n */\n private borderRow(\n [left, right]: readonly [string, string],\n innerWidth: number,\n edge: DoomOverlayChrome['accent'] = BORDER_COLOR,\n fill: DoomOverlayChrome['accent'] = edge,\n ): string {\n const edgeColour = edge ?? BORDER_COLOR;\n return [\n this.theme.fg(edgeColour, left),\n this.theme.fg(fill ?? edgeColour, HORIZONTAL_BORDER.repeat(innerWidth)),\n this.theme.fg(edgeColour, right),\n ].join('');\n }\n\n private contentRow(\n content: string,\n contentWidth: number,\n width: number,\n colour: DoomOverlayChrome['accent'] = BORDER_COLOR,\n ): string {\n const edge = this.theme.fg(colour ?? BORDER_COLOR, VERTICAL_BORDER);\n return frameLine(`${GUTTER}${padLine(content, contentWidth)}${GUTTER}`, width, edge, edge);\n }\n\n private headerRow(chrome: DoomOverlayChrome, width: number): string {\n // A signature colour turns the title into a filled badge, as the mockups\n // show; without one it stays plain accent text.\n const accent = chrome.accent;\n const titleBudget = accent ? width - GUTTER_COLUMNS : width;\n const title = truncateToWidth(chrome.title, Math.max(0, titleBudget), ELLIPSIS);\n let left = accent\n ? this.theme.inverse(this.theme.bold(this.theme.fg(accent, ` ${title} `)))\n : this.theme.bold(this.theme.fg(TITLE_COLOR, title));\n let leftWidth = visibleWidth(title) + (accent ? GUTTER_COLUMNS : 0);\n\n // The breadcrumb claims room before the right status: it says where you are,\n // which outranks ambient context when the terminal is narrow.\n const breadcrumb = fitSegment(chrome.breadcrumb, width - leftWidth - ROW_GAP);\n if (breadcrumb) {\n left += `${' '.repeat(ROW_GAP)}${this.theme.fg(SECONDARY_COLOR, breadcrumb)}`;\n leftWidth += ROW_GAP + visibleWidth(breadcrumb);\n }\n return this.withRightStatus(left, leftWidth, chrome.headerRight, width);\n }\n\n private footerRow(chrome: DoomOverlayChrome, width: number): string {\n const footer = chrome.footerHints?.length\n ? this.hintLegend(chrome.footerHints, width)\n : this.theme.fg(SECONDARY_COLOR, truncateToWidth(chrome.footer, width, ELLIPSIS));\n return this.withRightStatus(footer, visibleWidth(footer), chrome.footerRight, width);\n }\n\n /**\n * Key legend with each key in a filled cap. Every segment sets its own colour\n * so the caps survive: a cap's reset would otherwise strip the surrounding\n * dim from the labels that follow it.\n */\n private hintLegend(hints: readonly (readonly [string, string])[], width: number): string {\n let legend = '';\n let used = 0;\n for (const [key, label] of hints) {\n const segmentWidth = visibleWidth(key) + visibleWidth(label) + HINT_CHROME_COLUMNS;\n if (used + segmentWidth > width) break;\n const cap = this.theme.bg(HINT_CAP_BACKGROUND, this.theme.fg(TEXT_COLOR, ` ${key} `));\n legend += `${legend ? HINT_SEPARATOR : ''}${cap}${this.theme.fg(SECONDARY_COLOR, ` ${label}`)}`;\n used += segmentWidth;\n }\n return legend;\n }\n\n /**\n * Right-aligns a status cluster, dropping it whole when the row cannot spare\n * the columns. A clipped status reads as a stub (`doom-log…`) that carries\n * less than the blank space it costs.\n */\n private withRightStatus(left: string, leftWidth: number, right: string | undefined, width: number): string {\n const statusWidth = right ? visibleWidth(right) : 0;\n if (!right || statusWidth === 0 || leftWidth + ROW_GAP + statusWidth > width) return left;\n return `${left}${' '.repeat(width - leftWidth - statusWidth)}${this.theme.fg(SECONDARY_COLOR, right)}`;\n }\n\n private renderCompact(chrome: DoomOverlayChrome, width: number, height: number): string[] {\n const lines = Array.from({ length: height }, () => '');\n lines[0] = this.theme.bold(this.theme.fg(TITLE_COLOR, chrome.title));\n if (height > 1) lines[height - 1] = this.theme.fg(SECONDARY_COLOR, chrome.footer);\n return lines.map((line) => padLine(fitLine(line, width), width));\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{fitLine as e,frameLine as t,padLine as n}from"./rendering.mjs";import{truncateToWidth as r,visibleWidth as i}from"@earendil-works/pi-tui";const a=`borderMuted`,o=`accent`,s=[`╭`,`╮`],c=[`├`,`┤`],l=[`╰`,`╯`],u=`mdHeading`,d={overlay:!0,overlayOptions:{anchor:`top-left`,width:`100%`,maxHeight:`100%`,margin:0}};function f(e,t){return!e||t<=0||t<Math.min(i(e),8)?``:r(e,t,`…`)}var p=class{tui;theme;constructor(e,t){this.tui=e,this.theme=t}invalidate(){this.tui.requestRender()}render(e){let t=Math.max(0,Math.floor(e)),r=Math.max(0,Math.floor(this.tui.terminal?.rows??24));if(t===0||r===0)return[];let i=this.getChrome();if(r<6||t<8)return this.renderCompact(i,t,r);let o=t-2,u=o-2,d=r-6,f=this.renderBody(u,d).slice(0,d);for(;f.length<d;)f.push(``);let p=i.accent??a;return[this.borderRow(s,o,p),this.contentRow(this.headerRow(i,u),u,t,p),this.borderRow(c,o,p,a),...f.map(e=>this.contentRow(e,u,t,p)),this.borderRow(c,o,p,a),this.contentRow(this.footerRow(i,u),u,t,p),this.borderRow(l,o,p)].map(e=>n(e,t))}borderRow([e,t],n,r=a,i=r){let o=r??a;return[this.theme.fg(o,e),this.theme.fg(i??o,`─`.repeat(n)),this.theme.fg(o,t)].join(``)}contentRow(e,r,i,o=a){let s=this.theme.fg(o??a,`│`);return t(` ${n(e,r)} `,i,s,s)}headerRow(e,t){let n=e.accent,a=n?t-2:t,s=r(e.title,Math.max(0,a),`…`),c=n?this.theme.inverse(this.theme.bold(this.theme.fg(n,` ${s} `))):this.theme.bold(this.theme.fg(o,s)),l=i(s)+(n?2:0),u=f(e.breadcrumb,t-l-2);return u&&(c+=`${` `.repeat(2)}${this.theme.fg(`dim`,u)}`,l+=2+i(u)),this.withRightStatus(c,l,e.headerRight,t)}footerRow(e,t){let n=e.footerHints?.length?this.hintLegend(e.footerHints,t):this.theme.fg(`dim`,r(e.footer,t,`…`));return this.withRightStatus(n,i(n),e.footerRight,t)}hintLegend(e,t){let n=``,r=0;for(let[a,o]of e){let e=i(a)+i(o)+6;if(r+e>t)break;let s=this.theme.bg(`selectedBg`,this.theme.fg(`text`,` ${a} `));n+=`${n?` `:``}${s}${this.theme.fg(`dim`,` ${o}`)}`,r+=e}return n}withRightStatus(e,t,n,r){let a=n?i(n):0;return!n||a===0||t+2+a>r?e:`${e}${` `.repeat(r-t-a)}${this.theme.fg(`dim`,n)}`}renderCompact(t,r,i){let a=Array.from({length:i},()=>``);return a[0]=this.theme.bold(this.theme.fg(o,t.title)),i>1&&(a[i-1]=this.theme.fg(`dim`,t.footer)),a.map(t=>n(e(t,r),r))}};export{d as DOOM_FULLSCREEN_UI_OPTIONS,u as DOOM_OVERLAY_ACCENT,p as DoomOverlay};
2
+ //# sourceMappingURL=doomOverlay.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doomOverlay.mjs","names":[],"sources":["../../src/workflow/doomOverlay.ts"],"sourcesContent":["import type { Theme } from '@earendil-works/pi-coding-agent';\nimport { type Component, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport { fitLine, frameLine, padLine } from './rendering.ts';\n\nconst DEFAULT_TERMINAL_ROWS = 24;\nconst FULL_CHROME_ROWS = 6;\n/** Two frame columns and two gutter columns, leaving content worth framing. */\nconst MIN_FRAMED_WIDTH = 8;\nconst GUTTER = ' ';\nconst GUTTER_COLUMNS = 2;\n/** Blank columns kept between a row's left cluster and its right-aligned status. */\nconst ROW_GAP = 2;\n/** A secondary segment thinner than this is dropped rather than shredded into a stub. */\nconst MIN_SEGMENT_WIDTH = 8;\nconst ELLIPSIS = '…';\nconst BORDER_COLOR = 'borderMuted';\nconst TEXT_COLOR = 'text';\n/** Filled key caps read as pressable, matching the doom mockups' footers. */\nconst HINT_CAP_BACKGROUND = 'selectedBg';\nconst HINT_SEPARATOR = ' ';\n/** A cap's two pad columns plus the space before its label. */\nconst HINT_CHROME_COLUMNS = 6;\nconst TITLE_COLOR = 'accent';\nconst SECONDARY_COLOR = 'dim';\nconst HORIZONTAL_BORDER = '─';\nconst VERTICAL_BORDER = '│';\nconst TOP_CORNERS = ['╭', '╮'] as const;\nconst DIVIDER_JOINTS = ['├', '┤'] as const;\nconst BOTTOM_CORNERS = ['╰', '╯'] as const;\n\nexport interface DoomOverlayTui {\n terminal?: {\n rows: number;\n columns?: number;\n };\n requestRender(force?: boolean): void;\n}\n\nexport interface DoomOverlayChrome {\n title: string;\n breadcrumb?: string;\n headerRight?: string;\n footer: string;\n footerRight?: string;\n /**\n * Key/label pairs rendered as filled caps, e.g. `[ enter ] edit subject`.\n * Takes precedence over `footer`, which stays for surfaces that want a plain\n * sentence rather than a key legend.\n */\n footerHints?: readonly (readonly [string, string])[];\n /**\n * Signature colour for the surface. When set, the frame takes it and the\n * title renders as a filled badge rather than plain accent text, matching the\n * doom mockups. Left unset, the frame stays muted and the title plain.\n */\n accent?: Parameters<Theme['fg']>[0];\n}\n\n/**\n * One signature colour for every doom overlay: the frame and the title badge.\n * Per-surface colours were tried and read as arbitrary, and they also borrowed\n * tokens that mean something else (`warning`, `syntaxNumber`), so a theme edit\n * elsewhere would have repainted a frame. Change it here to change them all.\n */\nexport const DOOM_OVERLAY_ACCENT = 'mdHeading' satisfies DoomOverlayChrome['accent'];\n\nexport const DOOM_FULLSCREEN_UI_OPTIONS = {\n overlay: true,\n overlayOptions: {\n anchor: 'top-left',\n width: '100%',\n maxHeight: '100%',\n margin: 0,\n },\n} as const;\n\n/**\n * Fits a secondary segment into `budget`, or drops it entirely when too few\n * columns remain for it to stay readable. Truncating everything to fit is what\n * makes a tight header read as one run-on string.\n */\nfunction fitSegment(text: string | undefined, budget: number): string {\n if (!text || budget <= 0) return '';\n return budget < Math.min(visibleWidth(text), MIN_SEGMENT_WIDTH) ? '' : truncateToWidth(text, budget, ELLIPSIS);\n}\n\nexport abstract class DoomOverlay implements Component {\n protected constructor(\n protected readonly tui: DoomOverlayTui,\n protected readonly theme: Theme,\n ) {}\n\n invalidate(): void {\n this.tui.requestRender();\n }\n\n render(width: number): string[] {\n const safeWidth = Math.max(0, Math.floor(width));\n const height = Math.max(0, Math.floor(this.tui.terminal?.rows ?? DEFAULT_TERMINAL_ROWS));\n if (safeWidth === 0 || height === 0) return [];\n\n const chrome = this.getChrome();\n if (height < FULL_CHROME_ROWS || safeWidth < MIN_FRAMED_WIDTH) {\n return this.renderCompact(chrome, safeWidth, height);\n }\n\n const innerWidth = safeWidth - GUTTER_COLUMNS;\n const contentWidth = innerWidth - GUTTER_COLUMNS;\n const bodyHeight = height - FULL_CHROME_ROWS;\n const body = this.renderBody(contentWidth, bodyHeight).slice(0, bodyHeight);\n while (body.length < bodyHeight) body.push('');\n\n const frame = chrome.accent ?? BORDER_COLOR;\n return [\n this.borderRow(TOP_CORNERS, innerWidth, frame),\n this.contentRow(this.headerRow(chrome, contentWidth), contentWidth, safeWidth, frame),\n this.borderRow(DIVIDER_JOINTS, innerWidth, frame, BORDER_COLOR),\n ...body.map((line) => this.contentRow(line, contentWidth, safeWidth, frame)),\n this.borderRow(DIVIDER_JOINTS, innerWidth, frame, BORDER_COLOR),\n this.contentRow(this.footerRow(chrome, contentWidth), contentWidth, safeWidth, frame),\n this.borderRow(BOTTOM_CORNERS, innerWidth, frame),\n ].map((line) => padLine(line, safeWidth));\n }\n\n protected abstract getChrome(): DoomOverlayChrome;\n\n protected abstract renderBody(width: number, height: number): string[];\n\n /**\n * Edge glyphs and the rule between them are coloured separately: the outer\n * rectangle carries the signature colour while an internal separator stays\n * muted, meeting the frame at accent-coloured junctions.\n */\n private borderRow(\n [left, right]: readonly [string, string],\n innerWidth: number,\n edge: DoomOverlayChrome['accent'] = BORDER_COLOR,\n fill: DoomOverlayChrome['accent'] = edge,\n ): string {\n const edgeColour = edge ?? BORDER_COLOR;\n return [\n this.theme.fg(edgeColour, left),\n this.theme.fg(fill ?? edgeColour, HORIZONTAL_BORDER.repeat(innerWidth)),\n this.theme.fg(edgeColour, right),\n ].join('');\n }\n\n private contentRow(\n content: string,\n contentWidth: number,\n width: number,\n colour: DoomOverlayChrome['accent'] = BORDER_COLOR,\n ): string {\n const edge = this.theme.fg(colour ?? BORDER_COLOR, VERTICAL_BORDER);\n return frameLine(`${GUTTER}${padLine(content, contentWidth)}${GUTTER}`, width, edge, edge);\n }\n\n private headerRow(chrome: DoomOverlayChrome, width: number): string {\n // A signature colour turns the title into a filled badge, as the mockups\n // show; without one it stays plain accent text.\n const accent = chrome.accent;\n const titleBudget = accent ? width - GUTTER_COLUMNS : width;\n const title = truncateToWidth(chrome.title, Math.max(0, titleBudget), ELLIPSIS);\n let left = accent\n ? this.theme.inverse(this.theme.bold(this.theme.fg(accent, ` ${title} `)))\n : this.theme.bold(this.theme.fg(TITLE_COLOR, title));\n let leftWidth = visibleWidth(title) + (accent ? GUTTER_COLUMNS : 0);\n\n // The breadcrumb claims room before the right status: it says where you are,\n // which outranks ambient context when the terminal is narrow.\n const breadcrumb = fitSegment(chrome.breadcrumb, width - leftWidth - ROW_GAP);\n if (breadcrumb) {\n left += `${' '.repeat(ROW_GAP)}${this.theme.fg(SECONDARY_COLOR, breadcrumb)}`;\n leftWidth += ROW_GAP + visibleWidth(breadcrumb);\n }\n return this.withRightStatus(left, leftWidth, chrome.headerRight, width);\n }\n\n private footerRow(chrome: DoomOverlayChrome, width: number): string {\n const footer = chrome.footerHints?.length\n ? this.hintLegend(chrome.footerHints, width)\n : this.theme.fg(SECONDARY_COLOR, truncateToWidth(chrome.footer, width, ELLIPSIS));\n return this.withRightStatus(footer, visibleWidth(footer), chrome.footerRight, width);\n }\n\n /**\n * Key legend with each key in a filled cap. Every segment sets its own colour\n * so the caps survive: a cap's reset would otherwise strip the surrounding\n * dim from the labels that follow it.\n */\n private hintLegend(hints: readonly (readonly [string, string])[], width: number): string {\n let legend = '';\n let used = 0;\n for (const [key, label] of hints) {\n const segmentWidth = visibleWidth(key) + visibleWidth(label) + HINT_CHROME_COLUMNS;\n if (used + segmentWidth > width) break;\n const cap = this.theme.bg(HINT_CAP_BACKGROUND, this.theme.fg(TEXT_COLOR, ` ${key} `));\n legend += `${legend ? HINT_SEPARATOR : ''}${cap}${this.theme.fg(SECONDARY_COLOR, ` ${label}`)}`;\n used += segmentWidth;\n }\n return legend;\n }\n\n /**\n * Right-aligns a status cluster, dropping it whole when the row cannot spare\n * the columns. A clipped status reads as a stub (`doom-log…`) that carries\n * less than the blank space it costs.\n */\n private withRightStatus(left: string, leftWidth: number, right: string | undefined, width: number): string {\n const statusWidth = right ? visibleWidth(right) : 0;\n if (!right || statusWidth === 0 || leftWidth + ROW_GAP + statusWidth > width) return left;\n return `${left}${' '.repeat(width - leftWidth - statusWidth)}${this.theme.fg(SECONDARY_COLOR, right)}`;\n }\n\n private renderCompact(chrome: DoomOverlayChrome, width: number, height: number): string[] {\n const lines = Array.from({ length: height }, () => '');\n lines[0] = this.theme.bold(this.theme.fg(TITLE_COLOR, chrome.title));\n if (height > 1) lines[height - 1] = this.theme.fg(SECONDARY_COLOR, chrome.footer);\n return lines.map((line) => padLine(fitLine(line, width), width));\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ let e=require("@earendil-works/pi-tui");function t(t,n){let r=(0,e.truncateToWidth)(t,Math.max(0,n),`…`).replaceAll(`\x1B[0m`,``);return r+` `.repeat(Math.max(0,n-(0,e.visibleWidth)(r)))}function n(n,r,i){let a=(0,e.visibleWidth)(r),o=Math.max(0,i-a-1);return t(n,o)+` `.repeat(Math.max(1,i-o-a))+t(r,a)}function r(t,n){return n<=0?``:(0,e.visibleWidth)(t)<=n?t:`…${t.slice(t.length-Math.max(0,n-1))}`}function i(e){for(let t=0;t<e.length;t++){let n=e.charCodeAt(t);if(n<32||n===127)return!0}return!1}function a(e,t){let n=t.toLowerCase().split(/\s+/).filter(Boolean);if(n.length===0)return!0;let r=e.toLowerCase();return n.every(e=>r.includes(e))}exports.fit=t,exports.isControlInput=i,exports.matchesQuery=a,exports.pathTail=r,exports.rightAligned=n;
2
+ //# sourceMappingURL=overlayText.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlayText.cjs","names":["truncateToWidth","visibleWidth"],"sources":["../../src/workflow/overlayText.ts"],"sourcesContent":["/**\n * Text primitives shared by every doom overlay in this extension.\n *\n * Kept in one place because each of them encodes a decision that has to hold on\n * every surface: how a cell is clipped, which end of a path survives, and what\n * counts as a keystroke that carries text.\n */\n\nimport { truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\n\nexport const ELLIPSIS = '…';\nexport const CURSOR_BLOCK = '█';\nexport const SELECTION_MARKER = '›';\n/** pi-tui brackets a truncation ellipsis with this; see `fit`. */\nconst HARD_RESET = '\\x1b[0m';\nconst FIRST_PRINTABLE_CODE_POINT = 0x20;\nconst DELETE_CODE_POINT = 0x7f;\n\n/**\n * Truncates and pads to an exact column count.\n *\n * `truncateToWidth` wraps its ellipsis in a hard `\\x1b[0m`, which would strip a\n * row's background for everything after a clipped cell. Nothing here sets a\n * colour that a full reset is the correct end for -- the theme closes its own\n * with `\\x1b[39m` and `\\x1b[49m` -- so the injected resets are dropped.\n */\nexport function fit(text: string, width: number): string {\n const clipped = truncateToWidth(text, Math.max(0, width), ELLIPSIS).replaceAll(HARD_RESET, '');\n return clipped + ' '.repeat(Math.max(0, width - visibleWidth(clipped)));\n}\n\n/** Right-aligns a trailing cluster against a left one, both clipped to fit. */\nexport function rightAligned(left: string, right: string, width: number): string {\n const rightWidth = visibleWidth(right);\n const leftWidth = Math.max(0, width - rightWidth - 1);\n return fit(left, leftWidth) + ' '.repeat(Math.max(1, width - leftWidth - rightWidth)) + fit(right, rightWidth);\n}\n\n/**\n * Trims from the front rather than the tail.\n *\n * For a path the file name identifies the thing, while the\n * `automations/marketing-workflows/` prefix every entry shares does not.\n */\nexport function pathTail(text: string, width: number): string {\n if (width <= 0) return '';\n if (visibleWidth(text) <= width) return text;\n return `${ELLIPSIS}${text.slice(text.length - Math.max(0, width - 1))}`;\n}\n\n/**\n * Keystrokes that carry no text: escape sequences, control keys, delete.\n *\n * Indexed rather than iterated so surrogate pairs stay intact; both halves sit\n * far above the control range, so no emoji is ever mistaken for one.\n */\nexport function isControlInput(data: string): boolean {\n for (let index = 0; index < data.length; index++) {\n const code = data.charCodeAt(index);\n if (code < FIRST_PRINTABLE_CODE_POINT || code === DELETE_CODE_POINT) return true;\n }\n return false;\n}\n\n/**\n * Whitespace-separated terms, each of which must appear somewhere in the row's\n * searchable text. Splitting on whitespace is what lets `short video` match a\n * row whose words are divided between its title and its file name.\n */\nexport function matchesQuery(haystack: string, query: string): boolean {\n const terms = query.toLowerCase().split(/\\s+/).filter(Boolean);\n if (terms.length === 0) return true;\n const lowered = haystack.toLowerCase();\n return terms.every((term) => lowered.includes(term));\n}\n"],"mappings":"wCA0BA,SAAgB,EAAI,EAAc,EAAuB,CACvD,IAAM,GAAA,EAAUA,EAAAA,gBAAAA,CAAgB,EAAM,KAAK,IAAI,EAAG,CAAK,EAAA,GAAW,CAAC,CAAC,WAAW,UAAY,EAAE,EAC7F,OAAO,EAAU,IAAI,OAAO,KAAK,IAAI,EAAG,GAAA,EAAQC,EAAAA,aAAAA,CAAa,CAAO,CAAC,CAAC,CACxE,CAGA,SAAgB,EAAa,EAAc,EAAe,EAAuB,CAC/E,IAAM,GAAA,EAAaA,EAAAA,aAAAA,CAAa,CAAK,EAC/B,EAAY,KAAK,IAAI,EAAG,EAAQ,EAAa,CAAC,EACpD,OAAO,EAAI,EAAM,CAAS,EAAI,IAAI,OAAO,KAAK,IAAI,EAAG,EAAQ,EAAY,CAAU,CAAC,EAAI,EAAI,EAAO,CAAU,CAC/G,CAQA,SAAgB,EAAS,EAAc,EAAuB,CAG5D,OAFI,GAAS,EAAU,IACvB,EAAIA,EAAAA,aAAAA,CAAa,CAAI,GAAK,EAAc,EACjC,IAAc,EAAK,MAAM,EAAK,OAAS,KAAK,IAAI,EAAG,EAAQ,CAAC,CAAC,GACtE,CAQA,SAAgB,EAAe,EAAuB,CACpD,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAK,OAAQ,IAAS,CAChD,IAAM,EAAO,EAAK,WAAW,CAAK,EAClC,GAAI,EAAO,IAA8B,IAAS,IAAmB,MAAO,EAC9E,CACA,MAAO,EACT,CAOA,SAAgB,EAAa,EAAkB,EAAwB,CACrE,IAAM,EAAQ,EAAM,YAAY,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,OAAO,OAAO,EAC7D,GAAI,EAAM,SAAW,EAAG,MAAO,GAC/B,IAAM,EAAU,EAAS,YAAY,EACrC,OAAO,EAAM,MAAO,GAAS,EAAQ,SAAS,CAAI,CAAC,CACrD"}
@@ -0,0 +1,2 @@
1
+ import{truncateToWidth as e,visibleWidth as t}from"@earendil-works/pi-tui";function n(n,r){let i=e(n,Math.max(0,r),`…`).replaceAll(`\x1B[0m`,``);return i+` `.repeat(Math.max(0,r-t(i)))}function r(e,r,i){let a=t(r),o=Math.max(0,i-a-1);return n(e,o)+` `.repeat(Math.max(1,i-o-a))+n(r,a)}function i(e,n){return n<=0?``:t(e)<=n?e:`…${e.slice(e.length-Math.max(0,n-1))}`}function a(e){for(let t=0;t<e.length;t++){let n=e.charCodeAt(t);if(n<32||n===127)return!0}return!1}function o(e,t){let n=t.toLowerCase().split(/\s+/).filter(Boolean);if(n.length===0)return!0;let r=e.toLowerCase();return n.every(e=>r.includes(e))}export{n as fit,a as isControlInput,o as matchesQuery,i as pathTail,r as rightAligned};
2
+ //# sourceMappingURL=overlayText.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overlayText.mjs","names":[],"sources":["../../src/workflow/overlayText.ts"],"sourcesContent":["/**\n * Text primitives shared by every doom overlay in this extension.\n *\n * Kept in one place because each of them encodes a decision that has to hold on\n * every surface: how a cell is clipped, which end of a path survives, and what\n * counts as a keystroke that carries text.\n */\n\nimport { truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\n\nexport const ELLIPSIS = '…';\nexport const CURSOR_BLOCK = '█';\nexport const SELECTION_MARKER = '›';\n/** pi-tui brackets a truncation ellipsis with this; see `fit`. */\nconst HARD_RESET = '\\x1b[0m';\nconst FIRST_PRINTABLE_CODE_POINT = 0x20;\nconst DELETE_CODE_POINT = 0x7f;\n\n/**\n * Truncates and pads to an exact column count.\n *\n * `truncateToWidth` wraps its ellipsis in a hard `\\x1b[0m`, which would strip a\n * row's background for everything after a clipped cell. Nothing here sets a\n * colour that a full reset is the correct end for -- the theme closes its own\n * with `\\x1b[39m` and `\\x1b[49m` -- so the injected resets are dropped.\n */\nexport function fit(text: string, width: number): string {\n const clipped = truncateToWidth(text, Math.max(0, width), ELLIPSIS).replaceAll(HARD_RESET, '');\n return clipped + ' '.repeat(Math.max(0, width - visibleWidth(clipped)));\n}\n\n/** Right-aligns a trailing cluster against a left one, both clipped to fit. */\nexport function rightAligned(left: string, right: string, width: number): string {\n const rightWidth = visibleWidth(right);\n const leftWidth = Math.max(0, width - rightWidth - 1);\n return fit(left, leftWidth) + ' '.repeat(Math.max(1, width - leftWidth - rightWidth)) + fit(right, rightWidth);\n}\n\n/**\n * Trims from the front rather than the tail.\n *\n * For a path the file name identifies the thing, while the\n * `automations/marketing-workflows/` prefix every entry shares does not.\n */\nexport function pathTail(text: string, width: number): string {\n if (width <= 0) return '';\n if (visibleWidth(text) <= width) return text;\n return `${ELLIPSIS}${text.slice(text.length - Math.max(0, width - 1))}`;\n}\n\n/**\n * Keystrokes that carry no text: escape sequences, control keys, delete.\n *\n * Indexed rather than iterated so surrogate pairs stay intact; both halves sit\n * far above the control range, so no emoji is ever mistaken for one.\n */\nexport function isControlInput(data: string): boolean {\n for (let index = 0; index < data.length; index++) {\n const code = data.charCodeAt(index);\n if (code < FIRST_PRINTABLE_CODE_POINT || code === DELETE_CODE_POINT) return true;\n }\n return false;\n}\n\n/**\n * Whitespace-separated terms, each of which must appear somewhere in the row's\n * searchable text. Splitting on whitespace is what lets `short video` match a\n * row whose words are divided between its title and its file name.\n */\nexport function matchesQuery(haystack: string, query: string): boolean {\n const terms = query.toLowerCase().split(/\\s+/).filter(Boolean);\n if (terms.length === 0) return true;\n const lowered = haystack.toLowerCase();\n return terms.every((term) => lowered.includes(term));\n}\n"],"mappings":"2EA0BA,SAAgB,EAAI,EAAc,EAAuB,CACvD,IAAM,EAAU,EAAgB,EAAM,KAAK,IAAI,EAAG,CAAK,EAAA,GAAW,CAAC,CAAC,WAAW,UAAY,EAAE,EAC7F,OAAO,EAAU,IAAI,OAAO,KAAK,IAAI,EAAG,EAAQ,EAAa,CAAO,CAAC,CAAC,CACxE,CAGA,SAAgB,EAAa,EAAc,EAAe,EAAuB,CAC/E,IAAM,EAAa,EAAa,CAAK,EAC/B,EAAY,KAAK,IAAI,EAAG,EAAQ,EAAa,CAAC,EACpD,OAAO,EAAI,EAAM,CAAS,EAAI,IAAI,OAAO,KAAK,IAAI,EAAG,EAAQ,EAAY,CAAU,CAAC,EAAI,EAAI,EAAO,CAAU,CAC/G,CAQA,SAAgB,EAAS,EAAc,EAAuB,CAG5D,OAFI,GAAS,EAAU,GACnB,EAAa,CAAI,GAAK,EAAc,EACjC,IAAc,EAAK,MAAM,EAAK,OAAS,KAAK,IAAI,EAAG,EAAQ,CAAC,CAAC,GACtE,CAQA,SAAgB,EAAe,EAAuB,CACpD,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAK,OAAQ,IAAS,CAChD,IAAM,EAAO,EAAK,WAAW,CAAK,EAClC,GAAI,EAAO,IAA8B,IAAS,IAAmB,MAAO,EAC9E,CACA,MAAO,EACT,CAOA,SAAgB,EAAa,EAAkB,EAAwB,CACrE,IAAM,EAAQ,EAAM,YAAY,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,OAAO,OAAO,EAC7D,GAAI,EAAM,SAAW,EAAG,MAAO,GAC/B,IAAM,EAAU,EAAS,YAAY,EACrC,OAAO,EAAM,MAAO,GAAS,EAAQ,SAAS,CAAI,CAAC,CACrD"}
@@ -0,0 +1,10 @@
1
+ const e=require("../types.cjs"),t=require("./backgroundWork.cjs"),n=require("./piToolBridge.cjs"),r=require("./piTools.cjs"),i=require("./workflowLauncher.cjs"),a=require("./workflowPicker.cjs"),o=require("./workflowPrompt.cjs"),s=require("./workflowOverlay.cjs"),c=require("./workflowRunPanel.cjs"),l=require("./workflowStatusRow.cjs"),u=require("./workflowInspector.cjs"),d=require("./workflowFooterStatus.cjs"),f=require("./workflowStepMessage.cjs");let ee=require("@agimon-ai/doompi-extension-contracts/leader"),p=require("node:child_process"),te=require("node:fs"),m=require("node:module"),h=require("node:path"),ne=require("node:url"),re=require("@agimon-ai/doompi-telemetry"),g=require("@agimon-ai/doompi-extension-contracts/child-process"),ie=require("@agimon-ai/doompi-extension-contracts/footer"),ae=require("@agimon-ai/doompi-extension-contracts/mode"),oe=require("@agimon-ai/doompi-extension-contracts/skills"),_=require("@earendil-works/pi-tui"),se=require("@agimon-ai/doompi-extension-contracts/protocol"),ce=require("@agimon-ai/doompi-extension-contracts/workflow"),v=require("@agimon-ai/workflow-mcp");const le={overlay:!0,overlayOptions:{anchor:`top-left`,width:`100%`,maxHeight:`100%`,margin:0}},ue=`agiflow-workflow`,de=`workflow-step`,fe=`workflow-mcp-follow`,pe=`SPC › w / workflows › w / launch`,me=`paused`,y=`ctrl+alt+w`,b=`ctrl+alt+q`,he={alt:`⌥`,cmd:`⌘`,ctrl:`⌃`,meta:`⌘`,shift:`⇧`};function x(e,t=process.platform){let n=e.split(`+`);return t===`darwin`?n.map(e=>he[e]??e.toUpperCase()).join(``):n.map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join(`+`)}const S=1e4,C=`running`,w=`completed`,T=`failed`,ge=`error`,E=`error`,_e=`doom_workflow.launch_requested`,ve=`../../skills`,D={running:`▸`,repair:`⚠`,completed:`✔`,failed:`✖`,separator:`·`};function ye(e,t=process.platform){let n=x(b,t);return e?[`Esc Esc closes this view`,`${x(y,t)} switches typing`,`${n} closes`].join(` ${D.separator} `):`view only ${D.separator} ${n} closes this view`}function be(e,t=process.platform){let n=[x(b,t),`close`];return e?[[`Esc Esc`,`close`],[x(y,t),`switch typing`],n]:[n]}function xe(e,t,n,r){let i=`${D.running} ${e.runKey}`,a=[e.workspace,we(e),e.runner].filter(Boolean).join(` ${D.separator} `),o=`${x(y)} to background`,s=n.fg(`accent`,(0,_.truncateToWidth)(i,r));return(0,_.visibleWidth)(i)+3+(0,_.visibleWidth)(a)<=r&&(s+=` `+n.fg(`muted`,a),(0,_.visibleWidth)(i)+3+(0,_.visibleWidth)(a)+3+(0,_.visibleWidth)(o)<=r&&(s+=` `+n.fg(`dim`,o))),[s,...t.map(e=>(0,_.truncateToWidth)(e,r))]}function Se(e,t,n){let r=[];for(let i of e){let e=i.status===w?D.completed:i.status===T?D.failed:D.running,a=i.status===w?`success`:i.status===T?E:`accent`,o=i.total?` ${D.separator} ${(i.index??0)+1}/${i.total}`:``;if(r.push((0,_.truncateToWidth)(t.fg(a,`${e} ${i.name}`)+t.fg(`dim`,o),n)),i.status===C)for(let e of i.steps){let i=e.status===w?D.completed:e.status===T?D.failed:D.running,a=e.status===C?`muted`:e.status===T?E:`dim`;r.push((0,_.truncateToWidth)(t.fg(a,` ${i} ${e.name}`),n))}}return r}function Ce(e,t){let n=Date.parse(e.startedAt);return Number.isFinite(n)?t.filter(e=>{let t=Date.parse(e.at);return!Number.isFinite(t)||t>=n}):t}function we(e){return e.stale?`stale`:e.stage===ge?T:e.stage}const Te=[`CMUX_SURFACE_ID`,`CMUX_WORKSPACE_ID`,`WORKFLOW_LAUNCHER_TYPE`,`WORKFLOW_TMUX_SESSION_NAME`,`WORKFLOW_STATUS_FILE`],O=`tmux`,k=`cmux`,Ee=`native`,De=`No durable launcher identity is recorded for this run.`;function Oe(e){return(0,h.basename)(e,(0,h.extname)(e))}function ke(){let e={...process.env};for(let t of Te)delete e[t];return e}function Ae(){let e=process.env.WORKFLOW_LAUNCHER;return e===O||e===k?e:void 0}function je(e){return e.map(e=>`'${e.replaceAll(`'`,`'\\''`)}'`).join(` `)}function Me(){try{return(0,m.createRequire)(require("url").pathToFileURL(__filename).href).resolve(`@agimon-ai/workflow-mcp/cli`)}catch{return}}const Ne=(e,t,n)=>{(0,p.spawn)(e,[...t],{...n,detached:!0,stdio:`ignore`}).unref()};function A(e,t){if(Array.isArray(e)){for(let n of e)A(n,t);return}if(!(!e||typeof e!=`object`))for(let[n,r]of Object.entries(e))(n===`interactiveRun`||n===`run`)&&r&&typeof r==`object`&&!Array.isArray(r)?t.push(Object.keys(r)):A(r,t)}function Pe(e){let t=[];if(A(e,t),t.length!==0)return t[0].filter(e=>t.every(t=>t.includes(e))).sort()}async function j(e,t){let n=[],r=1,i=!0;for(;i;){let a=await e.listRunsPage({page:r,pageSize:100,workspace:t});n.push(...a.items),i=a.hasNextPage,r+=1}return n}function Fe(e){return[e.runKey,e.workspace,we(e),e.runner].filter(Boolean).join(` ${D.separator} `)}function Ie(p={}){return m=>{let _=p.featureFactory?.()??(0,v.createEmbeddedWorkflowFeature)(),{parser:he,recoverService:T,registry:E,runService:Te,runTool:A}=_,Ie=p.spawnDetached??Ne,Le=p.monitorIntervalMs??3e4,Re=p.followIntervalMs??2e3,M,ze=!1,N,Be=!1,P,F=new Map,I=new Map,Ve=new Set,He=0,L,Ue,We,Ge=0,R=!1,z=!1,Ke=(0,ae.registerDoomModeContribution)(m,{source:e.PACKAGE_SOURCE,id:`workflow`,order:20}),qe={runs:[],failed:0},Je,Ye=0,B,Xe=(0,ie.registerDoomFooterContribution)(m,{source:e.PACKAGE_SOURCE,id:`workflow-runs`,order:50}),Ze,V,Qe,$e=!1,et=new Map,H,tt=[],U=new Map,nt=new Map,W=new Map,rt=[],it=new Map,at,G,K=e=>(G??=(0,re.createDoomTelemetry)({serviceName:`doom-workflow`,packageName:`@agimon-ai/doompi-workflow`,cwd:e?.cwd,env:process.env,enableLogs:!0,enableTraces:!0}),G),ot=e=>{e&&(at=(0,g.resolveRootSessionId)(e))},st=()=>{let e=at??Ze?.sessionManager.getSessionId();return e?(0,g.resolveRootSessionId)(e):void 0},ct=(e,t)=>e.sessionManager.getSessionId().trim()===t,lt=_.createRecoverTool({envOverrides:()=>{let e=st();return e?{[n.PI_SESSION_ENV]:e}:void 0}}),ut=_.createControlTool(),q=new Set,dt=e=>{let t=Date.now();return q.add(e),K().recordEvent(`doom_workflow.run_started`,{outcome:`started`}),e.then(()=>(q.delete(e),K().recordEvent(`doom_workflow.run_finished`,{duration_ms:Date.now()-t,outcome:`completed`})),n=>(q.delete(e),K().recordError(`doom_workflow.run_failed`,n,{duration_ms:Date.now()-t}))),e},ft=(e,t)=>{let n=Pe(he.parseWorkflowFile(e));if(!(!n||n.includes(t)))return`Runner ${JSON.stringify(t)} is not available across this workflow. Compatible runners: ${n.join(`, `)||`none`}.`},pt=r.createWorkflowLaunchExecutor({activeRunCount:async()=>Ge,observeSession:ot,rejectRunner:ft,runTool:A,trackPendingRun:dt,...p.launchAckPollMs===void 0?{}:{launchAckPollMs:p.launchAckPollMs},...p.launchAckTimeoutMs===void 0?{}:{launchAckTimeoutMs:p.launchAckTimeoutMs},findLaunchedRun:async({sessionId:e,since:t,workflowPath:r})=>{let i=Oe(r),a=r=>{if(!n.isSessionRun(r,e)||r.stale||r.workflowId&&r.workflowId!==i)return!1;let a=Date.parse(r.startedAt);return Number.isFinite(a)&&a>=t-2e3};return H?(await H.refresh(),rt.find(a)):(await j(E)).find(a)},onLateFailure:(e,t)=>{let r=e instanceof Error?e.message:String(e);t.ui.notify(n.launchNotice(`Workflow launch failed after it was reported started.\n${r}`),`warning`)},onLaunch:async e=>(await K(e).recordEvent(_e,{outcome:`requested`}),X(e,!1))}),mt=(e,t)=>{if(!e.isIdle())return;let n=Y(t),r=it.get(n);r||(r=new Set,it.set(n,r));for(let e of l.stepReportLines(t.runKey,W.get(n)??[])){if(r.has(e.key))continue;r.add(e.key);let n=f.workflowStepMessageDetails(t.displayName,e);m.sendMessage({customType:de,content:f.formatWorkflowStepMessage(n),details:n,display:!0},{triggerTurn:!1})}},ht=e=>e.filter(e=>e.stage===C&&!e.stale).map(e=>{let t=Y(e),n=U.get(t),r=n?[...n.values()].includes(C):!1,i=e.executionState===me?`paused`:r?`running`:`starting`,a=nt.get(t);return{key:t,state:i,...a===void 0?{}:{progress:a}}}),gt=(e,t,n,r)=>{let i=U.get(e)??new Map;(r||!i.has(t))&&i.set(t,n),U.set(e,i)},_t=(e,t)=>{t.phase===`job`&&t.status===C&&(t.index===void 0||!t.total||nt.set(e,Math.min(1,Math.max(0,t.index)/t.total)))},J,vt=e=>{J||(J=setTimeout(()=>{J=void 0,X(e,!0)},120),J.unref?.())},yt=e=>{W.delete(e),U.delete(e),nt.delete(e),it.delete(e)},bt=async e=>{let t=(0,g.resolveRootSessionId)(e.sessionManager.getSessionId()),r=_.createRunControl({recordFilter:e=>n.isSessionRun(e,t)});H=r,tt.push(r.on(`step`,t=>{let n=Y(t.record),r=W.get(n)??[];r.push({at:t.at,job:t.job,status:t.status,step:t.step,type:`step`}),W.set(n,r),gt(n,t.job,C,!1),mt(e,t.record),vt(e)}),r.on(`job`,t=>{let n=Y(t.record);gt(n,t.job,t.status,!0),_t(n,t),vt(e)}),r.on(`runStarted`,t=>{rt.unshift(t.record),rt.length>32&&(rt.length=32),vt(e)}),r.on(`runUpdated`,()=>void X(e,!0)),r.on(`runFinished`,()=>void X(e,!0))),await r.start()},xt=()=>{J&&clearTimeout(J),J=void 0;for(let e of tt.splice(0))e();H?.dispose(),H=void 0,W.clear(),U.clear(),nt.clear(),it.clear()},St=()=>{V?.teardown(),V?.handle?.hide(),V=void 0},Y=e=>e.runId??`${e.workspace}/${e.runKey}`,Ct=(e,t=!1)=>{let n=d.workflowFooterStatus(qe,Ye);!t&&n?.fingerprint===Je||(Je=n?.fingerprint,Xe.update(n?.footer),e.ui.setStatus(ue,n?.text),Tt(e,n?.pulsing??!1))},wt=()=>{R!==z&&(z=R,Ke.update(R?{label:`WORKFLOW`,color:`accent`}:void 0))},Tt=(e,t)=>{if(!t){B&&clearInterval(B),B=void 0;return}B||(B=setInterval(()=>{Ye=(Ye+1)%d.WORKFLOW_PULSE_FRAMES.length,Ct(e)},250),B.unref?.())},X=async(e,t)=>{let r=K(e);if(ze){r.recordEvent(`doom_workflow.monitor_skipped`,{outcome:`already_running`});return}let i=Date.now();ze=!0;try{let a=await j(E),o=(0,g.resolveRootSessionId)(e.sessionManager.getSessionId()),s=ct(e,o),c=a.filter(e=>n.isSessionRun(e,o)),l=c.filter(e=>e.stage===C&&!e.stale),u=c.filter(e=>e.stage===ge);r.recordEvent(`doom_workflow.monitor_counts`,{"workflow.run_count":c.length,"workflow.running_count":l.length,"workflow.failed_count":u.length,mode:R?`on`:`off`});let d=n.runsForSession(a,o).filter(e=>!e.stale);Ge=d.length,L?.update(d.map(e=>({id:e.runId??e.runKey,sessionId:o}))),qe={runs:ht(c),failed:u.length},Ct(e,!0),kt(new Set(c.map(e=>Y(e))));let f=new Set(c.filter(e=>e.stage===C&&!e.stale).map(e=>Y(e)));for(let e of[...W.keys(),...U.keys()])!f.has(e)&&!I.has(e)&&yt(e);let ee=new Map(c.map(e=>[Y(e),e.stage]));if(t&&s)for(let e of c){if(e.stage===C)continue;let t=Y(e),n=F.get(t),r=Date.parse(e.startedAt),i=n===void 0&&(He===0||!Number.isFinite(r)||r>=He);!Ve.has(t)&&(n===C||i)&&I.set(t,e)}if(t)for(let t of c){if(F.get(Y(t))!==C||t.stage===C)continue;let n=t.stage===w,i=t.failedJob?` at ${t.failedJob}`:``;e.ui.notify(n?`${D.completed} ${t.runKey} completed`:`${D.failed} ${t.runKey} ${we(t)}${i}`,n?`info`:`warning`),r.recordEvent(`doom_workflow.run_transition`,{outcome:n?`completed`:we(t)})}if(s&&I.size>0)try{let e=[...I.values()],t=await Promise.all(e.map(async e=>{if(e.stage===w)return n.finishedRunSummary(e,[]);let t=Y(e),r=W.get(t)??Ce(e,await(0,v.readWorkflowProgress)(E.runDirectoryFor(e)));return n.finishedRunSummary(e,(0,v.summarizeWorkflowProgress)(r))}));m.sendMessage({customType:`workflow-run-finished`,content:t.join(`
2
+
3
+ `),display:!0,details:{runIds:e.map(e=>e.runId??Y(e))}},{triggerTurn:!0,deliverAs:`steer`});for(let t of e){let e=Y(t);Ve.add(e),I.delete(e),yt(e)}}catch(e){await r.recordError(`doom_workflow.terminal_delivery_failed`,e,{"workflow.pending_terminal_count":I.size})}F=ee,r.recordEvent(`doom_workflow.monitor_finished`,{duration_ms:Date.now()-i,outcome:`completed`})}catch(t){await r.recordError(`doom_workflow.monitor_failed`,t,{duration_ms:Date.now()-i}),e.ui.setStatus(ue,void 0),e.ui.notify(`Workflow status refresh failed: ${t instanceof Error?t.message:String(t)}`,`warning`)}finally{ze=!1}},Et=e=>{N&&clearInterval(N),N=void 0,P=void 0,e.ui.setWidget(fe,void 0)},Dt=async e=>{let t=e.launcher;if(!t)return[De];if(t.type===O){let n=t.paneId??t.sessionId??t.sessionName,r=await m.exec(O,[`capture-pane`,`-p`,`-t`,n,`-S`,`-24`],{timeout:S});return r.code===0?r.stdout.trimEnd().split(`
4
+ `).slice(-24):[`Unable to read ${e.runKey}: ${r.stderr.trim()||`tmux exited ${r.code}`}`]}if(t.type===Ee)return[`This run is hosted natively and has no launcher screen to read.`];let n=await m.exec(k,[`read-screen`,`--workspace`,t.workspaceId,`--scrollback`,`--lines`,`24`],{timeout:S});return n.code===0?n.stdout.trimEnd().split(`
5
+ `).slice(-24):[`Unable to read ${e.runKey}: ${n.stderr.trim()||`cmux exited ${n.code}`}`]},Z=new Map,Ot=async e=>{let t=Y(e),n=Z.get(t)??{at:0,lines:[]};if(Z.set(t,n),n.inFlight||Date.now()-n.at<750)return n.lines;n.inFlight=Dt(e);try{n.lines=await n.inFlight}finally{n.at=Date.now(),n.inFlight=void 0}return n.lines},kt=e=>{for(let t of Z.keys())e.has(t)||Z.delete(t)},At=async e=>{let t=e.launcher;if(!t)return De;if(t.type===Ee)return`This run is hosted natively rather than in a multiplexer, so there is no separate window to bring forward.`;if(t.type===k)return await m.exec(k,[`select-workspace`,`--workspace`,t.workspaceId],{timeout:S}),`Opened ${e.runKey} in cmux.`;if(!process.env.TMUX)return`Pi is not attached to tmux. Follow the output instead, or attach to the recorded tmux session manually.`;let n=t.sessionId??t.sessionName,r=await m.exec(O,[`switch-client`,`-t`,n],{timeout:S});return r.code===0?(t.paneId&&await m.exec(O,[`select-pane`,`-t`,t.paneId],{timeout:S}),`Opened ${e.runKey} in tmux.`):r.stderr.trim()||`Unable to open tmux session ${n}.`},jt=async(e,t)=>{if(!Be){Be=!0;try{let n=(0,v.summarizeWorkflowProgress)(Ce(e,await(0,v.readWorkflowProgress)(E.runDirectoryFor(e)))),r=n.length>0?void 0:await Ot(e);t.ui.setWidget(fe,(t,i)=>({render:t=>xe(e,r??Se(n,i,t),i,t),invalidate:()=>{}}),{placement:`aboveEditor`})}finally{Be=!1}}},Mt=async(e,t)=>{Et(t),P=e,await jt(e,t),N=setInterval(()=>{P&&jt(P,t)},Re),N.unref?.()},Nt=async(e,t)=>{let n=e.launcher;if(!(!n||n.type===Ee)){if(n.type===O){let e=n.paneId??n.sessionId??n.sessionName;await m.exec(O,[`send-keys`,`-t`,e,`-l`,t],{timeout:S});return}await m.exec(k,[`send`,`--workspace`,n.workspaceId,t],{timeout:S})}},Pt=async(e,t)=>{St();let n=e.launcher?.type===O||e.launcher?.type===k,r=new s.TerminalInputBatcher(t=>Nt(e,t)),i=[],a=[],o,l=!1,u=async()=>{i=(0,v.summarizeWorkflowProgress)(Ce(e,await(0,v.readWorkflowProgress)(E.runDirectoryFor(e)))),a=await Ot(e)};await u();let d=()=>{o&&clearInterval(o),o=void 0,r.dispose()},f={interactive:n,runKey:e.runKey,teardown:d};V=f;let ee=()=>{St(),t.ui.notify(`Closed the ${e.runKey} view. The run keeps going.`,`info`)};return t.ui.custom((s,p,te,m)=>(o=setInterval(()=>{l||(l=!0,u().finally(()=>{l=!1,s.requestRender()}))},200),o.unref?.(),new c.WorkflowRunPanelComponent(s,p,{runKey:e.runKey,label:`${D.running} ${Fe(e)}`,breadcrumb:`SPC › w / workflows › run`,interactive:n,footer:ye(n),hints:be(n),snapshot:e=>({progress:Se(i,p,e),output:a}),onUnfollow:()=>{f.handle?.unfocus(),t.ui.notify(`Typing goes to this session. The run keeps going.`,`info`)},onClose:ee,sendInput:e=>r.write(e),flushInput:()=>void r.flush(),unfollowShortcut:y,closeShortcut:b,onDispose:()=>{d(),V===f&&(V=void 0),m()}})),{...c.runPanelUiOptions(n),onHandle:e=>{f.handle=e}}),t.ui.notify(n?`${e.runKey}: typing goes to the run. Press Esc twice to close, ${x(y)} to type here instead.`:`${e.runKey}: view only, because this run is hosted natively. ${x(b)} closes it.`,`info`),n?[`Showing ${e.runKey} over Pi. Typing goes to the run.`,`Pressing Escape twice quickly closes the view, and a single Escape still reaches the run.`,`${x(y)} moves typing between the run and this session,`,`and ${x(b)} closes the view. The run keeps going either way.`].join(` `):[`Showing ${e.runKey} over Pi, as a view only:`,`the run is hosted natively rather than in a multiplexer, so there is no terminal to type into.`,`Typing stays with this session, and ${x(b)} closes the view.`].join(` `)},Ft=async(e,t=!1)=>{if(!($e||V||P)){$e=!0;try{let r=await e.ui.custom((e,t,r,i)=>new u.WorkflowInspectorComponent(e,t,{list:async()=>{let e=st(),t=(await j(E)).filter(t=>t.stage===C&&!t.stale&&n.isSessionRun(t,e));return et=new Map(t.map(e=>[Y(e),e])),Promise.all(t.map(async e=>({displayName:e.displayName,executionState:e.executionState,jobs:(0,v.summarizeWorkflowProgress)(Ce(e,await(0,v.readWorkflowProgress)(E.runDirectoryFor(e)))),key:Y(e),output:[],runKey:e.runKey,startedAt:e.startedAt,workspace:e.workspace})))},output:async e=>{let t=et.get(e.key);return t?Ot(t):[`Workflow is no longer active.`]}},i),{...le,onHandle:e=>{Qe=e}});if(r){let n=await Q(r);t?await Lt(n,e):await Pt(n,e)}}finally{Qe=void 0,$e=!1,et.clear()}}},Q=async(e,t=st())=>{ot(t);let r=(await j(E,e.workspace)).find(t=>t.runKey===e.runKey);if(!r||!n.isSessionRun(r,t))throw Error(n.withOptions(`No workflow run matches ${JSON.stringify(e.runKey)} in this session.`,[`workflow_run with action status: use a run key from this session before retrying.`,`Tell the user this session did not launch that run rather than guessing a similar key. A run launched by another Pi session, or from the command line, is managed there or with the workflow-mcp CLI.`]));return r},It=async(e,t)=>{let n=Me();if(!n)return;let r=await Q(e,(0,g.resolveRootSessionId)(t.sessionManager.getSessionId())),i;try{i=he.parseWorkflowFile(r.workflowPath)}catch{return}if(!i[`launch-command`])return;let a=r.originalRepoPath??t.cwd,o=je([process.execPath,n,`recover-workflow`,r.runKey,`--workspace`,r.workspace,...e.runner?[`--runner`,e.runner]:[]]),s=Ae();return Ie(process.execPath,[n,`launch-process`,...s?[`--launcher`,s]:[],`--name`,`${r.displayName} recover`,`--cwd`,a,`--command`,o],{cwd:a,env:ke()}),await K(t).recordEvent(`doom_workflow.recover_delegated`,{launcher:s??`auto`,outcome:`delegated`}),`Recovering ${r.runKey} in a launcher of its own, so this session keeps its terminal. It reports back here when it ends.`},Lt=async(e,t)=>{let r=e;for(;;){let e=[`Open output`],i=r.executionState??C;i===C&&e.push(`Pause`),i===me&&e.push(`Resume`),r.stage===C&&e.push(`Stop`),e.push(`Back`);let a=await o.openWorkflowChoice(t,{title:`Manage ${r.runKey}`,breadcrumb:`SPC › w / workflows › l / manage`,choices:e});if(!a||a===`Back`)return;if(r=await Q(r),r.stage!==C){t.ui.notify(`${r.runKey} is no longer running.`,`warning`);return}if(a===`Open output`){await Pt(r,t);return}if(a===`Stop`){if(!await t.ui.confirm(`Stop ${r.runKey}?`,`The workflow will be interrupted and will not be restarted automatically.`))continue;if(r=await Q(r),r.stage!==C){t.ui.notify(`${r.runKey} is no longer running.`,`warning`);return}}if(a!==`Pause`&&a!==`Resume`&&a!==`Stop`)continue;let s=await ut.execute({action:a.toLowerCase(),...a===`Stop`?{reason:`Stopped from the workflow manager.`}:{},runKey:r.runKey,workspace:r.workspace});if(s.isError)throw Error(n.toolResultText(s)||`Unable to ${a.toLowerCase()} workflow.`);t.ui.notify(n.toolResultText(s)||`${a} requested for ${r.runKey}.`,`info`),await X(t,!1),r=await Q(r)}},Rt=(e,t)=>({editor:(t,n)=>e.ui.editor(t,n),input:(n,r)=>o.openWorkflowInput(e,{title:n,breadcrumb:t,...r?{fallback:r}:{}}),notify:(t,n)=>e.ui.notify(t,n),select:(n,r)=>o.openWorkflowChoice(e,{title:n,breadcrumb:t,choices:r})}),zt=async e=>{if(!e.hasUI){e.ui.notify(`Workflow launch needs an interactive Pi session.`,`warning`);return}let t=await i.openWorkflowLaunchWizard({compatibleRunners:Pe,cwd:e.cwd,launch:(e,t)=>pt.execute(e,t),listWorkflowsTool:_.listWorkflowsTool,parseWorkflow:e=>he.parseWorkflowFile(e),pickWorkflow:t=>a.openWorkflowPickerOverlay(e,{title:`WORKFLOW LAUNCHER`,breadcrumb:pe,unit:`workflows`,action:`launch`,filterPlaceholder:`type to filter by name, path or tag`,emptyMessage:`No workflow definitions were found in this repository.`},t.map(e=>({key:e.path,name:e.name,detail:e.relativePath,search:[e.description,...e.tags].join(` `),value:e}))),ui:Rt(e,pe)},e);t&&e.ui.notify(n.launchNotice(n.toolResultText(t)),`info`)},Bt=async e=>{let t=st(),r=(await j(E)).filter(e=>e.stage===ge&&n.isSessionRun(e,t));if(r.length===0){e.ui.notify(`No failed workflows belong to this session.`,`info`);return}let i=await a.openWorkflowPickerOverlay(e,{title:`WORKFLOW RECOVERY`,breadcrumb:`SPC › w / workflows › r / recover`,unit:`failed runs`,action:`recover`,filterPlaceholder:`type to filter by name, run key or failed job`,emptyMessage:`No failed workflows belong to this session.`},r.map(e=>({key:`${e.workspace}/${e.runKey}`,name:e.displayName,detail:`${e.workspace}/${e.runKey}${e.failedJob?` · ${e.failedJob}`:``}`,...e.failedJob?{search:e.failedJob}:{},value:e})));if(!i)return;let o=await Q(i);if(o.stage!==ge)throw Error(`${o.runKey} is no longer failed.`);let s=(0,h.resolve)((0,h.dirname)((0,ne.fileURLToPath)(require("url").pathToFileURL(__filename).href)),ve,`workflow-recovery`,`SKILL.md`);if(!(0,te.existsSync)(s))throw Error(`Workflow recovery skill is unavailable at ${s}.`);Vt(!0),e.ui.notify(`Workflow tools enabled. The agent will investigate this failure and choose the safe next step.`,`info`),m.sendUserMessage([`Investigate recovery for this failed workflow run. This selection is permission to investigate, not permission to recover blindly.`,`Run identity: ${JSON.stringify({runKey:o.runKey,workspace:o.workspace})}`,`Read the package-owned recovery skill with the read tool at ${JSON.stringify(s)} before acting.`,`Inspect status, every relevant log source, process state, the staged run.json, and only the repair referenced by activeRepairId.`,`Choose exactly one outcome: recover, launch fresh, or defer. Never edit issue.md or repair.json, never scan historical repairs, and never pass recovery dryRun or a job override.`,`Do not recover terminal, approval-pending, blocked, or otherwise unsafe work. If evidence is incomplete, defer and explain what is missing.`,`If recovery is justified, preserve the recorded worktree and repair metadata, then verify real process and registry progress before reporting success.`].join(`
6
+ `),{deliverAs:`followUp`})},Vt=e=>{let t=new Set(m.getActiveTools());for(let n of r.WORKFLOW_PI_TOOL_NAMES)e?t.add(n):t.delete(n);m.setActiveTools([...t]),R=e,wt(),p.onModeChange?.(e)},Ht=()=>R?[`${D.completed} Workflow mode on`,`Ask for workflows in plain language: the agent can list, launch, inspect, pause, resume, stop, and recover runs.`,`${x(y)} stops following a run without stopping it.`].join(`
7
+ `):[`Workflow mode off`,`Press SPC w e to give the agent its workflow tools. Runs already going are unaffected.`].join(`
8
+ `),Ut=async(e,t)=>{Vt(e),R||Et(t),t.ui.notify(Ht(),`info`),await X(t,!1)};m.registerShortcut(y,{description:`Open workflows or move typing between a workflow run and this session`,handler:async e=>{let t=V?.handle;if(t){if(!V?.interactive){e.ui.notify(`This run is hosted natively, so the view takes no typing. Your keys stay here.`,`info`);return}t.isFocused()?(t.unfocus(),e.ui.notify(`Typing goes to this session. The run keeps going.`,`info`)):(t.focus(),e.ui.notify(`Typing goes to ${V?.runKey}.`,`info`));return}if(P){Et(e),e.ui.notify(`Workflow continues in the background.`,`info`);return}await Ft(e)}}),m.registerShortcut(b,{description:`Close the workflow run view`,handler:e=>{if(!V)return;let t=V.runKey;St(),e.ui.notify(`Closed the ${t} view. The run keeps going.`,`info`)}}),m.on(`resources_discover`,()=>{let e=(0,h.resolve)((0,h.dirname)((0,ne.fileURLToPath)(require("url").pathToFileURL(__filename).href)),ve);return{skillPaths:(0,te.existsSync)(e)?[e]:[]}});let Wt=(0,h.resolve)((0,h.dirname)((0,ne.fileURLToPath)(require("url").pathToFileURL(__filename).href)),ve);(0,te.existsSync)(Wt)&&(0,oe.registerDoomSkillSource)(m,{source:`@agimon-ai/doompi-workflow`,directories:[Wt]}),m.registerMessageRenderer(de,(e,{outputPad:t},n)=>{let r=f.isWorkflowStepMessageDetails(e.details)?e.details:void 0,i=typeof e.content==`string`?e.content:JSON.stringify(e.content);return f.renderWorkflowStepMessage(r,i,t,n)}),r.registerWorkflowPiTools(m,{runTool:A,recoverTool:lt,launchExecutor:pt,controlTool:ut,requireSessionRun:Q,delegateRecovery:It,trackPendingRun:dt,observeSession:ot,activeRunCount:async()=>Ge,onLaunch:async e=>(await K(e).recordEvent(_e,{outcome:`requested`}),X(e,!1)),followRun:async(e,t)=>{let n=await Q(e,t.sessionManager.getSessionId());return await K(t).recordEvent(`doom_workflow.follow_started`,{outcome:`started`}),await Mt(n,t),`Following ${Fe(n)}. Press ${x(y)} to stop following; the run keeps going.`},tailRun:async(e,t)=>{let n=await Q(e,t.sessionManager.getSessionId());return await K(t).recordEvent(`doom_workflow.tail_requested`,{outcome:`requested`}),[`Workflow: ${Fe(n)}`,...await Dt(n)].join(`
9
+ `)},openRun:async(e,t)=>{let n=await Q(e,t.sessionManager.getSessionId());return await K(t).recordEvent(`doom_workflow.open_requested`,{outcome:`requested`}),t.hasUI?Pt(n,t):At(n)},rejectRunner:ft});let $=e=>e;(0,ee.registerDoomLeaderActionHandlers)(m,{source:`@agimon-ai/doompi-workflow`,handlers:{[e.LEADER_ENABLE_ACTION]:e=>Ut(!0,$(e)),[e.LEADER_DISABLE_ACTION]:e=>Ut(!1,$(e)),[e.LEADER_MANAGE_ACTION]:e=>Ft($(e),!0),[e.LEADER_LAUNCH_ACTION]:e=>zt($(e)),[e.LEADER_RECOVER_ACTION]:e=>Bt($(e))},onError:(e,t,n)=>{$(n).ui.notify(e instanceof Error?e.message:String(e),`error`)}}),m.on(`session_start`,async(e,n)=>{let r=K(n);await r.recordEvent(`doom_workflow.session_started`,{mode:process.env.WORKFLOW_MCP_MODE===`on`?`on`:`off`,outcome:`started`}),Ze=n;let i=(0,g.resolveRootSessionId)(n.sessionManager.getSessionId());ot(i),He=Date.now(),F=new Map,I.clear(),Ve.clear(),Ue=(0,se.createProtocolRuntime)({bus:m.events,source:`@agimon-ai/doompi-workflow`,sessionId:i}),We?.(),We=Ue.onNotification(ce.WorkflowModeChanged,({mode:e})=>{let t=e===`on`;t!==R&&(Vt(t),r.recordEvent(`doom_workflow.mode_changed`,{mode:e}),X(n,!1))}),Vt(process.env.WORKFLOW_MCP_MODE===`on`),L?.dispose(),L=t.registerRunProvider(Ue),await bt(n),await X(n,!1),M&&clearInterval(M),M=setInterval(()=>void X(n,!0),Le),M.unref?.()}),m.on(`session_shutdown`,async(e,t)=>{if(q.size>0){for(let e of[Te,T])e.interrupt(`SIGTERM`,{phase:`workflow`,source:`pi-session-shutdown`});await Promise.race([Promise.allSettled(q),new Promise(e=>setTimeout(e,1e4).unref?.())])}xt(),M&&clearInterval(M),M=void 0,B&&clearInterval(B),B=void 0,qe={runs:[],failed:0},Je=void 0,Xe.update(void 0),L?.dispose(),L=void 0,We?.(),We=void 0,Ue=void 0,Qe?.hide(),Qe=void 0,$e=!1,et.clear(),Z.clear(),St(),Ge=0,Et(t),t.ui.setStatus(ue,void 0),z&&(z=!1,Ke.update(void 0)),await G?.recordEvent(`doom_workflow.session_finished`,{"workflow.pending_count":q.size,outcome:q.size===0?`completed`:`interrupted`}),await G?.shutdown(),G=void 0})}}exports.compatibleRunners=Pe,exports.createWorkflowPiExtension=Ie,exports.panelHint=ye,exports.panelHints=be,exports.shortcutLabel=x;
10
+ //# sourceMappingURL=piExtension.cjs.map