@agimon-ai/doompi-workflow 0.0.1-alpha.73 → 0.0.1-alpha.76

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 (218) hide show
  1. package/LICENSE +4 -2
  2. package/README.md +8 -5
  3. package/package.json +19 -18
  4. package/src/types/webWorkflowTerminal.ts +7 -6
  5. package/dist/_virtual/_rolldown/runtime.cjs +0 -1
  6. package/dist/api-contracts.cjs +0 -1
  7. package/dist/api-contracts.d.cts +0 -2
  8. package/dist/api-contracts.d.mts +0 -2
  9. package/dist/api-contracts.mjs +0 -1
  10. package/dist/extensions/pi.cjs +0 -2
  11. package/dist/extensions/pi.cjs.map +0 -1
  12. package/dist/extensions/pi.d.cts +0 -6
  13. package/dist/extensions/pi.d.cts.map +0 -1
  14. package/dist/extensions/pi.d.mts +0 -6
  15. package/dist/extensions/pi.d.mts.map +0 -1
  16. package/dist/extensions/pi.mjs +0 -2
  17. package/dist/extensions/pi.mjs.map +0 -1
  18. package/dist/extensions/server.cjs +0 -2
  19. package/dist/extensions/server.cjs.map +0 -1
  20. package/dist/extensions/server.d.cts +0 -5
  21. package/dist/extensions/server.d.cts.map +0 -1
  22. package/dist/extensions/server.d.mts +0 -5
  23. package/dist/extensions/server.d.mts.map +0 -1
  24. package/dist/extensions/server.mjs +0 -2
  25. package/dist/extensions/server.mjs.map +0 -1
  26. package/dist/index.cjs +0 -1
  27. package/dist/index.d.cts +0 -2
  28. package/dist/index.d.mts +0 -2
  29. package/dist/index.mjs +0 -1
  30. package/dist/src/constants/workflow.cjs +0 -2
  31. package/dist/src/constants/workflow.cjs.map +0 -1
  32. package/dist/src/constants/workflow.mjs +0 -2
  33. package/dist/src/constants/workflow.mjs.map +0 -1
  34. package/dist/src/controllers/serverRuntime.cjs +0 -2
  35. package/dist/src/controllers/serverRuntime.cjs.map +0 -1
  36. package/dist/src/controllers/serverRuntime.mjs +0 -2
  37. package/dist/src/controllers/serverRuntime.mjs.map +0 -1
  38. package/dist/src/controllers/workflowCatalogChannel.cjs +0 -2
  39. package/dist/src/controllers/workflowCatalogChannel.cjs.map +0 -1
  40. package/dist/src/controllers/workflowCatalogChannel.mjs +0 -2
  41. package/dist/src/controllers/workflowCatalogChannel.mjs.map +0 -1
  42. package/dist/src/controllers/workflowHubApi.cjs +0 -2
  43. package/dist/src/controllers/workflowHubApi.cjs.map +0 -1
  44. package/dist/src/controllers/workflowHubApi.mjs +0 -2
  45. package/dist/src/controllers/workflowHubApi.mjs.map +0 -1
  46. package/dist/src/controllers/workflowsHubChannel.cjs +0 -2
  47. package/dist/src/controllers/workflowsHubChannel.cjs.map +0 -1
  48. package/dist/src/controllers/workflowsHubChannel.mjs +0 -2
  49. package/dist/src/controllers/workflowsHubChannel.mjs.map +0 -1
  50. package/dist/src/schemas/apiContracts.cjs +0 -2
  51. package/dist/src/schemas/apiContracts.cjs.map +0 -1
  52. package/dist/src/schemas/apiContracts.d.cts +0 -53
  53. package/dist/src/schemas/apiContracts.d.cts.map +0 -1
  54. package/dist/src/schemas/apiContracts.d.mts +0 -53
  55. package/dist/src/schemas/apiContracts.d.mts.map +0 -1
  56. package/dist/src/schemas/apiContracts.mjs +0 -2
  57. package/dist/src/schemas/apiContracts.mjs.map +0 -1
  58. package/dist/src/schemas/workflowPi.cjs +0 -2
  59. package/dist/src/schemas/workflowPi.cjs.map +0 -1
  60. package/dist/src/schemas/workflowPi.d.cts +0 -1
  61. package/dist/src/schemas/workflowPi.d.mts +0 -1
  62. package/dist/src/schemas/workflowPi.mjs +0 -2
  63. package/dist/src/schemas/workflowPi.mjs.map +0 -1
  64. package/dist/src/services/backgroundWork/index.cjs +0 -2
  65. package/dist/src/services/backgroundWork/index.cjs.map +0 -1
  66. package/dist/src/services/backgroundWork/index.mjs +0 -2
  67. package/dist/src/services/backgroundWork/index.mjs.map +0 -1
  68. package/dist/src/services/piToolBridge/index.cjs +0 -11
  69. package/dist/src/services/piToolBridge/index.cjs.map +0 -1
  70. package/dist/src/services/piToolBridge/index.mjs +0 -11
  71. package/dist/src/services/piToolBridge/index.mjs.map +0 -1
  72. package/dist/src/services/webWorkflowCatalog/index.cjs +0 -2
  73. package/dist/src/services/webWorkflowCatalog/index.cjs.map +0 -1
  74. package/dist/src/services/webWorkflowCatalog/index.mjs +0 -2
  75. package/dist/src/services/webWorkflowCatalog/index.mjs.map +0 -1
  76. package/dist/src/services/workflowCatalogDeps/index.cjs +0 -2
  77. package/dist/src/services/workflowCatalogDeps/index.cjs.map +0 -1
  78. package/dist/src/services/workflowCatalogDeps/index.mjs +0 -2
  79. package/dist/src/services/workflowCatalogDeps/index.mjs.map +0 -1
  80. package/dist/src/services/workflowExecution/index.cjs +0 -2
  81. package/dist/src/services/workflowExecution/index.cjs.map +0 -1
  82. package/dist/src/services/workflowExecution/index.d.cts +0 -6
  83. package/dist/src/services/workflowExecution/index.d.mts +0 -6
  84. package/dist/src/services/workflowExecution/index.mjs +0 -2
  85. package/dist/src/services/workflowExecution/index.mjs.map +0 -1
  86. package/dist/src/services/workflowFence/index.cjs +0 -2
  87. package/dist/src/services/workflowFence/index.cjs.map +0 -1
  88. package/dist/src/services/workflowFence/index.mjs +0 -2
  89. package/dist/src/services/workflowFence/index.mjs.map +0 -1
  90. package/dist/src/services/workflowLaunchCommand/index.cjs +0 -2
  91. package/dist/src/services/workflowLaunchCommand/index.cjs.map +0 -1
  92. package/dist/src/services/workflowLaunchCommand/index.d.cts +0 -52
  93. package/dist/src/services/workflowLaunchCommand/index.d.cts.map +0 -1
  94. package/dist/src/services/workflowLaunchCommand/index.d.mts +0 -52
  95. package/dist/src/services/workflowLaunchCommand/index.d.mts.map +0 -1
  96. package/dist/src/services/workflowLaunchCommand/index.mjs +0 -2
  97. package/dist/src/services/workflowLaunchCommand/index.mjs.map +0 -1
  98. package/dist/src/services/workflowLauncher/index.cjs +0 -2
  99. package/dist/src/services/workflowLauncher/index.cjs.map +0 -1
  100. package/dist/src/services/workflowLauncher/index.mjs +0 -2
  101. package/dist/src/services/workflowLauncher/index.mjs.map +0 -1
  102. package/dist/src/services/workflowNarration/index.cjs +0 -2
  103. package/dist/src/services/workflowNarration/index.cjs.map +0 -1
  104. package/dist/src/services/workflowNarration/index.mjs +0 -2
  105. package/dist/src/services/workflowNarration/index.mjs.map +0 -1
  106. package/dist/src/services/workflowResource/index.cjs +0 -2
  107. package/dist/src/services/workflowResource/index.cjs.map +0 -1
  108. package/dist/src/services/workflowResource/index.mjs +0 -2
  109. package/dist/src/services/workflowResource/index.mjs.map +0 -1
  110. package/dist/src/services/workflowRuns/index.cjs +0 -3
  111. package/dist/src/services/workflowRuns/index.cjs.map +0 -1
  112. package/dist/src/services/workflowRuns/index.mjs +0 -3
  113. package/dist/src/services/workflowRuns/index.mjs.map +0 -1
  114. package/dist/src/services/workflowTerminal/index.cjs +0 -2
  115. package/dist/src/services/workflowTerminal/index.cjs.map +0 -1
  116. package/dist/src/services/workflowTerminal/index.mjs +0 -2
  117. package/dist/src/services/workflowTerminal/index.mjs.map +0 -1
  118. package/dist/src/services/workflowWatcher/index.cjs +0 -2
  119. package/dist/src/services/workflowWatcher/index.cjs.map +0 -1
  120. package/dist/src/services/workflowWatcher/index.mjs +0 -2
  121. package/dist/src/services/workflowWatcher/index.mjs.map +0 -1
  122. package/dist/src/tools/workflowTools.cjs +0 -5
  123. package/dist/src/tools/workflowTools.cjs.map +0 -1
  124. package/dist/src/tools/workflowTools.mjs +0 -5
  125. package/dist/src/tools/workflowTools.mjs.map +0 -1
  126. package/dist/src/tui/leader.cjs +0 -2
  127. package/dist/src/tui/leader.cjs.map +0 -1
  128. package/dist/src/tui/leader.mjs +0 -2
  129. package/dist/src/tui/leader.mjs.map +0 -1
  130. package/dist/src/tui/workflow/doomOverlay.cjs +0 -2
  131. package/dist/src/tui/workflow/doomOverlay.cjs.map +0 -1
  132. package/dist/src/tui/workflow/doomOverlay.mjs +0 -2
  133. package/dist/src/tui/workflow/doomOverlay.mjs.map +0 -1
  134. package/dist/src/tui/workflow/overlayText.cjs +0 -2
  135. package/dist/src/tui/workflow/overlayText.cjs.map +0 -1
  136. package/dist/src/tui/workflow/overlayText.mjs +0 -2
  137. package/dist/src/tui/workflow/overlayText.mjs.map +0 -1
  138. package/dist/src/tui/workflow/rendering.cjs +0 -2
  139. package/dist/src/tui/workflow/rendering.cjs.map +0 -1
  140. package/dist/src/tui/workflow/rendering.mjs +0 -2
  141. package/dist/src/tui/workflow/rendering.mjs.map +0 -1
  142. package/dist/src/tui/workflow/workflowCatalog.cjs +0 -2
  143. package/dist/src/tui/workflow/workflowCatalog.cjs.map +0 -1
  144. package/dist/src/tui/workflow/workflowCatalog.mjs +0 -2
  145. package/dist/src/tui/workflow/workflowCatalog.mjs.map +0 -1
  146. package/dist/src/tui/workflow/workflowFinishedMessage.cjs +0 -3
  147. package/dist/src/tui/workflow/workflowFinishedMessage.cjs.map +0 -1
  148. package/dist/src/tui/workflow/workflowFinishedMessage.mjs +0 -3
  149. package/dist/src/tui/workflow/workflowFinishedMessage.mjs.map +0 -1
  150. package/dist/src/tui/workflow/workflowInspector.cjs +0 -2
  151. package/dist/src/tui/workflow/workflowInspector.cjs.map +0 -1
  152. package/dist/src/tui/workflow/workflowInspector.mjs +0 -2
  153. package/dist/src/tui/workflow/workflowInspector.mjs.map +0 -1
  154. package/dist/src/tui/workflow/workflowOverlay.cjs +0 -2
  155. package/dist/src/tui/workflow/workflowOverlay.cjs.map +0 -1
  156. package/dist/src/tui/workflow/workflowOverlay.mjs +0 -2
  157. package/dist/src/tui/workflow/workflowOverlay.mjs.map +0 -1
  158. package/dist/src/tui/workflow/workflowPicker.cjs +0 -2
  159. package/dist/src/tui/workflow/workflowPicker.cjs.map +0 -1
  160. package/dist/src/tui/workflow/workflowPicker.mjs +0 -2
  161. package/dist/src/tui/workflow/workflowPicker.mjs.map +0 -1
  162. package/dist/src/tui/workflow/workflowProgressOverlay.cjs +0 -2
  163. package/dist/src/tui/workflow/workflowProgressOverlay.cjs.map +0 -1
  164. package/dist/src/tui/workflow/workflowProgressOverlay.mjs +0 -2
  165. package/dist/src/tui/workflow/workflowProgressOverlay.mjs.map +0 -1
  166. package/dist/src/tui/workflow/workflowPrompt.cjs +0 -2
  167. package/dist/src/tui/workflow/workflowPrompt.cjs.map +0 -1
  168. package/dist/src/tui/workflow/workflowPrompt.mjs +0 -2
  169. package/dist/src/tui/workflow/workflowPrompt.mjs.map +0 -1
  170. package/dist/src/tui/workflow/workflowRunPanel.cjs +0 -2
  171. package/dist/src/tui/workflow/workflowRunPanel.cjs.map +0 -1
  172. package/dist/src/tui/workflow/workflowRunPanel.mjs +0 -2
  173. package/dist/src/tui/workflow/workflowRunPanel.mjs.map +0 -1
  174. package/dist/src/tui/workflow/workflowStatusRow.cjs +0 -2
  175. package/dist/src/tui/workflow/workflowStatusRow.cjs.map +0 -1
  176. package/dist/src/tui/workflow/workflowStatusRow.mjs +0 -2
  177. package/dist/src/tui/workflow/workflowStatusRow.mjs.map +0 -1
  178. package/dist/src/tui/workflow/workflowStepMessage.cjs +0 -2
  179. package/dist/src/tui/workflow/workflowStepMessage.cjs.map +0 -1
  180. package/dist/src/tui/workflow/workflowStepMessage.mjs +0 -2
  181. package/dist/src/tui/workflow/workflowStepMessage.mjs.map +0 -1
  182. package/dist/src/tui/workflow/workflowToolRender.cjs +0 -7
  183. package/dist/src/tui/workflow/workflowToolRender.cjs.map +0 -1
  184. package/dist/src/tui/workflow/workflowToolRender.mjs +0 -7
  185. package/dist/src/tui/workflow/workflowToolRender.mjs.map +0 -1
  186. package/dist/src/tui/workflowRuntime.cjs +0 -9
  187. package/dist/src/tui/workflowRuntime.cjs.map +0 -1
  188. package/dist/src/tui/workflowRuntime.d.cts +0 -33
  189. package/dist/src/tui/workflowRuntime.d.cts.map +0 -1
  190. package/dist/src/tui/workflowRuntime.d.mts +0 -33
  191. package/dist/src/tui/workflowRuntime.d.mts.map +0 -1
  192. package/dist/src/tui/workflowRuntime.mjs +0 -9
  193. package/dist/src/tui/workflowRuntime.mjs.map +0 -1
  194. package/dist/src/types/webWorkflowTerminal.cjs +0 -2
  195. package/dist/src/types/webWorkflowTerminal.cjs.map +0 -1
  196. package/dist/src/types/webWorkflowTerminal.mjs +0 -2
  197. package/dist/src/types/webWorkflowTerminal.mjs.map +0 -1
  198. package/dist/src/types/webWorkflows.cjs +0 -2
  199. package/dist/src/types/webWorkflows.cjs.map +0 -1
  200. package/dist/src/types/webWorkflows.mjs +0 -2
  201. package/dist/src/types/webWorkflows.mjs.map +0 -1
  202. package/src/extensions/web.ts +0 -80
  203. package/src/web/api/terminalApi.ts +0 -208
  204. package/src/web/components/ArtifactsPane.tsx +0 -451
  205. package/src/web/components/LaunchWorkflowDialog.tsx +0 -259
  206. package/src/web/components/StepTerminalPanel.tsx +0 -243
  207. package/src/web/components/WorkflowCatalogDrawer.tsx +0 -229
  208. package/src/web/components/WorkflowToolMessage.tsx +0 -55
  209. package/src/web/components/WorkflowsActivitySection.tsx +0 -155
  210. package/src/web/components/WorkflowsPanel.tsx +0 -750
  211. package/src/web/lib/launchLine.ts +0 -66
  212. package/src/web/lib/runDuration.ts +0 -15
  213. package/src/web/lib/workflowActivity.ts +0 -102
  214. package/src/web/lib/workflowToolRender.ts +0 -585
  215. package/src/web/stores/catalogStore.ts +0 -123
  216. package/src/web/stores/workflowsStore.ts +0 -91
  217. package/src/web/tsconfig.json +0 -22
  218. /package/{src/web/LICENSE → LICENSE.web} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/workflowLauncher/index.ts"],"sourcesContent":["import { resolve } from 'node:path';\n\nimport { type EmbeddedWorkflowFeature, type Workflow } from '@agimon-ai/workflow-mcp';\nimport type { ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\nimport { type DispatchInput, workflowCatalogPageSchema, workflowDispatchInputsSchema } from '../../schemas/workflowPi';\nimport type {\n WorkflowCatalogDetail,\n WorkflowInputSummary,\n WorkflowJobSummary,\n} from '../../tui/workflow/workflowCatalog';\nimport { toolResultText } from '../piToolBridge';\nimport { type WorkflowLaunchExecutor, type WorkflowLaunchInput } from '../workflowExecution';\n\nconst CATALOG_PAGE_SIZE = 100;\nconst USER_PROMPT_TRIGGER = 'user_prompt';\nconst WORKFLOW_DISPATCH_TRIGGER = 'workflow_dispatch';\nconst BOOLEAN_INPUT_TYPE = 'boolean';\n\ntype WorkflowListTool = EmbeddedWorkflowFeature['listWorkflowsTool'];\ntype WorkflowListInput = Parameters<WorkflowListTool['execute']>[0];\n\nexport interface WorkflowCatalogEntry {\n description: string;\n name: string;\n path: string;\n relativePath: string;\n tags: string[];\n}\n\nexport interface WorkflowCatalogPage {\n directory: string;\n hasNextPage: boolean;\n page: number;\n pageSize: number;\n workflows: WorkflowCatalogEntry[];\n}\n\nexport interface WorkflowLauncherUi {\n editor(title: string, prefill?: string): Promise<string | undefined>;\n input(title: string, placeholder?: string): Promise<string | undefined>;\n notify(message: string, type?: 'info' | 'warning' | 'error'): void;\n select(title: string, options: string[]): Promise<string | undefined>;\n}\n\nexport interface WorkflowEntryLaunchOptions {\n compatibleRunners(workflow: Workflow): string[] | undefined;\n launch: WorkflowLaunchExecutor['execute'];\n parseWorkflow(workflowPath: string): Workflow;\n ui: WorkflowLauncherUi;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === 'object' && !Array.isArray(value) ? { ...value } : {};\n}\n\nfunction workflowTriggerBlock(workflow: Workflow): Record<string, unknown> {\n return asRecord(workflow.on);\n}\n\nfunction dispatchInputs(workflow: Workflow): Record<string, DispatchInput> {\n const dispatch = workflowTriggerBlock(workflow)[WORKFLOW_DISPATCH_TRIGGER];\n if (dispatch === null || dispatch === undefined) return {};\n const parsed = workflowDispatchInputsSchema.safeParse(dispatch);\n if (!parsed.success) throw new Error(`Invalid workflow_dispatch input definitions: ${parsed.error.message}`);\n return parsed.data.inputs ?? {};\n}\n\nfunction launchResultError(result: CallToolResult): Error {\n return new Error(toolResultText(result) || 'Workflow launch failed.');\n}\n\nexport function parseWorkflowCatalogPage(result: CallToolResult): WorkflowCatalogPage {\n if (result.isError) throw launchResultError(result);\n let value: unknown;\n try {\n value = JSON.parse(toolResultText(result));\n } catch (cause) {\n throw new Error('Workflow catalog returned invalid JSON.', { cause });\n }\n const parsed = workflowCatalogPageSchema.safeParse(value);\n if (!parsed.success) throw new Error(`Workflow catalog response was invalid: ${parsed.error.message}`);\n return {\n directory: parsed.data.directory,\n hasNextPage: parsed.data.hasNextPage,\n page: parsed.data.page,\n pageSize: parsed.data.pageSize,\n workflows: parsed.data.workflows.map((entry) => ({\n description: entry.description,\n name: entry.name,\n path: resolve(parsed.data.directory, entry.path),\n relativePath: entry.path,\n tags: entry.tags,\n })),\n };\n}\n\nexport async function loadWorkflowCatalog(\n listWorkflowsTool: WorkflowListTool,\n directory: string,\n): Promise<WorkflowCatalogEntry[]> {\n const entries: WorkflowCatalogEntry[] = [];\n let page = 1;\n let hasNextPage = true;\n while (hasNextPage) {\n const input: WorkflowListInput = { directory, page, pageSize: CATALOG_PAGE_SIZE };\n const result = await listWorkflowsTool.execute(input);\n const catalogPage = parseWorkflowCatalogPage(result);\n entries.push(...catalogPage.workflows);\n hasNextPage = catalogPage.hasNextPage;\n page += 1;\n }\n return entries;\n}\n\nasync function requestPrompt(ui: WorkflowLauncherUi): Promise<string | undefined> {\n while (true) {\n const value = await ui.editor('Workflow prompt');\n if (value === undefined) return undefined;\n const prompt = value.trim();\n if (prompt) return prompt;\n ui.notify('A workflow prompt is required.', 'warning');\n }\n}\n\nasync function requestDispatchInput(\n ui: WorkflowLauncherUi,\n name: string,\n input: DispatchInput,\n): Promise<string | undefined> {\n const title = input.description ? `${name}: ${input.description}` : `Workflow input: ${name}`;\n if (input.options && input.options.length > 0) return ui.select(title, input.options);\n if (input.type === BOOLEAN_INPUT_TYPE) return ui.select(title, ['true', 'false']);\n\n while (true) {\n const value = await ui.input(title, input.default);\n if (value === undefined) return undefined;\n const normalized = value.trim() || input.default;\n if (normalized || !input.required) return normalized || undefined;\n ui.notify(`A value is required for workflow input ${name}.`, 'warning');\n }\n}\n\nexport async function collectWorkflowInputs(\n workflow: Workflow,\n ui: WorkflowLauncherUi,\n): Promise<{ inputs?: Record<string, string>; prompt?: string } | undefined> {\n const triggers = workflowTriggerBlock(workflow);\n const requiresPrompt = Object.hasOwn(triggers, USER_PROMPT_TRIGGER);\n const prompt = requiresPrompt ? await requestPrompt(ui) : undefined;\n if (requiresPrompt && prompt === undefined) return undefined;\n\n const definitions = dispatchInputs(workflow);\n const inputs: Record<string, string> = {};\n for (const [name, definition] of Object.entries(definitions)) {\n const value = await requestDispatchInput(ui, name, definition);\n if (value === undefined && definition.required) return undefined;\n if (value !== undefined) inputs[name] = value;\n }\n return {\n ...(prompt === undefined ? {} : { prompt }),\n ...(Object.keys(inputs).length === 0 ? {} : { inputs }),\n };\n}\n\n/**\n * Everything a launch needs once the workflow is chosen: the runner, the\n * prompt, and any `workflow_dispatch` inputs, each asked for through the\n * host's own dialogs. Selection is the caller's business - `SPC w l` launches\n * the row under its cursor, so this takes an entry rather than picking one.\n */\nfunction stepLabel(step: { name?: string; uses?: string; run?: unknown; id?: string }, index: number): string {\n if (step.name) return step.name;\n if (step.uses) return step.uses;\n if (step.id) return step.id;\n // A `run:` step without a name is identified by its position; printing the\n // command would put a shell line into a summary pane.\n return `step ${index + 1}`;\n}\n\nfunction catalogInputs(workflow: Workflow): WorkflowInputSummary[] {\n return Object.entries(dispatchInputs(workflow)).map(([name, definition]) => ({\n name,\n ...(definition.description ? { description: definition.description } : {}),\n ...(definition.required === undefined ? {} : { required: definition.required }),\n ...(definition.default === undefined ? {} : { default: definition.default }),\n ...(definition.options ? { options: definition.options } : {}),\n }));\n}\n\nfunction catalogJobs(workflow: Workflow): WorkflowJobSummary[] {\n return Object.entries(workflow.jobs ?? {}).map(([name, job]) => ({\n name,\n steps: (job.steps ?? []).map((step, index) => stepLabel(step, index)),\n }));\n}\n\n/**\n * The parsed shape `SPC w l`'s detail pane shows. A parse failure is returned\n * as `error` rather than thrown: one unparseable file in a repository must not\n * take the whole board down.\n */\nexport function summarizeWorkflowFile(\n workflowPath: string,\n options: Pick<WorkflowEntryLaunchOptions, 'compatibleRunners' | 'parseWorkflow'>,\n): WorkflowCatalogDetail {\n try {\n const workflow = options.parseWorkflow(workflowPath);\n const runners = options.compatibleRunners(workflow);\n return {\n triggers: Object.keys(workflowTriggerBlock(workflow)),\n inputs: catalogInputs(workflow),\n jobs: catalogJobs(workflow),\n ...(runners ? { runners } : {}),\n };\n } catch (cause) {\n return {\n triggers: [],\n inputs: [],\n jobs: [],\n error: cause instanceof Error ? cause.message : String(cause),\n };\n }\n}\n\nexport async function launchWorkflowEntry(\n options: WorkflowEntryLaunchOptions,\n selected: Pick<WorkflowCatalogEntry, 'path'>,\n ctx: ExtensionContext,\n): Promise<CallToolResult | undefined> {\n const workflow = options.parseWorkflow(selected.path);\n const runners = options.compatibleRunners(workflow);\n if (runners && runners.length === 0) throw new Error('No compatible runner is available for this workflow.');\n let runner: string | undefined;\n if (runners) {\n runner = await options.ui.select('Select a workflow runner', runners);\n if (runner === undefined) return undefined;\n }\n\n const triggerInputs = await collectWorkflowInputs(workflow, options.ui);\n if (triggerInputs === undefined) return undefined;\n const launchInput: WorkflowLaunchInput = {\n workflowPath: selected.path,\n ...(runner === undefined ? {} : { runner }),\n ...(typeof workflow.workspace === 'string' ? { workspace: workflow.workspace } : {}),\n ...triggerInputs,\n };\n return options.launch(launchInput, ctx);\n}\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- function e(e,t,n,r){let i=n===void 0&&r;i&&e.narrate(`Workflow launched: ${t.displayName}.`),(n===`running`||i)&&(t.stage===`completed`&&e.narrate(`Workflow succeeded: ${t.displayName}.`),t.stage===`error`&&e.narrate(`Workflow failed: ${t.displayName}.`))}exports.narrateWorkflowTransition=e;
2
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/services/workflowNarration/index.ts"],"sourcesContent":["import type { WorkflowRunRecord, WorkflowStage } from '@agimon-ai/workflow-mcp';\n\n/** Package-local narration sink; the Pi adapter binds it to the injected Cordis service. */\nexport interface WorkflowNarrationSink {\n narrate(text: string): void;\n}\n\nconst RUNNING_STAGE: WorkflowStage = 'running';\nconst COMPLETED_STAGE: WorkflowStage = 'completed';\nconst ERROR_STAGE: WorkflowStage = 'error';\n\ntype NarratedWorkflow = Pick<WorkflowRunRecord, 'displayName' | 'stage'>;\n\n/**\n * Translate an observed workflow lifecycle transition into generic speech.\n * Voice owns delivery and ignores requests while its minor mode is inactive.\n */\nexport function narrateWorkflowTransition(\n requester: WorkflowNarrationSink,\n record: NarratedWorkflow,\n previousStage: WorkflowStage | undefined,\n startedDuringSession: boolean,\n): void {\n const newlyLaunched = previousStage === undefined && startedDuringSession;\n if (newlyLaunched) requester.narrate(`Workflow launched: ${record.displayName}.`);\n\n if (previousStage !== RUNNING_STAGE && !newlyLaunched) return;\n if (record.stage === COMPLETED_STAGE) requester.narrate(`Workflow succeeded: ${record.displayName}.`);\n if (record.stage === ERROR_STAGE) requester.narrate(`Workflow failed: ${record.displayName}.`);\n}\n"],"mappings":"AAiBA,SAAgB,EACd,EACA,EACA,EACA,EACM,CACN,IAAM,EAAgB,IAAkB,IAAA,IAAa,EACjD,GAAe,EAAU,QAAQ,sBAAsB,EAAO,YAAY,EAAE,GAE5E,IAAkB,WAAkB,KACpC,EAAO,QAAU,aAAiB,EAAU,QAAQ,uBAAuB,EAAO,YAAY,EAAE,EAChG,EAAO,QAAU,SAAa,EAAU,QAAQ,oBAAoB,EAAO,YAAY,EAAE,EAC/F"}
@@ -1,2 +0,0 @@
1
- function e(e,t,n,r){let i=n===void 0&&r;i&&e.narrate(`Workflow launched: ${t.displayName}.`),(n===`running`||i)&&(t.stage===`completed`&&e.narrate(`Workflow succeeded: ${t.displayName}.`),t.stage===`error`&&e.narrate(`Workflow failed: ${t.displayName}.`))}export{e as narrateWorkflowTransition};
2
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/workflowNarration/index.ts"],"sourcesContent":["import type { WorkflowRunRecord, WorkflowStage } from '@agimon-ai/workflow-mcp';\n\n/** Package-local narration sink; the Pi adapter binds it to the injected Cordis service. */\nexport interface WorkflowNarrationSink {\n narrate(text: string): void;\n}\n\nconst RUNNING_STAGE: WorkflowStage = 'running';\nconst COMPLETED_STAGE: WorkflowStage = 'completed';\nconst ERROR_STAGE: WorkflowStage = 'error';\n\ntype NarratedWorkflow = Pick<WorkflowRunRecord, 'displayName' | 'stage'>;\n\n/**\n * Translate an observed workflow lifecycle transition into generic speech.\n * Voice owns delivery and ignores requests while its minor mode is inactive.\n */\nexport function narrateWorkflowTransition(\n requester: WorkflowNarrationSink,\n record: NarratedWorkflow,\n previousStage: WorkflowStage | undefined,\n startedDuringSession: boolean,\n): void {\n const newlyLaunched = previousStage === undefined && startedDuringSession;\n if (newlyLaunched) requester.narrate(`Workflow launched: ${record.displayName}.`);\n\n if (previousStage !== RUNNING_STAGE && !newlyLaunched) return;\n if (record.stage === COMPLETED_STAGE) requester.narrate(`Workflow succeeded: ${record.displayName}.`);\n if (record.stage === ERROR_STAGE) requester.narrate(`Workflow failed: ${record.displayName}.`);\n}\n"],"mappings":"AAiBA,SAAgB,EACd,EACA,EACA,EACA,EACM,CACN,IAAM,EAAgB,IAAkB,IAAA,IAAa,EACjD,GAAe,EAAU,QAAQ,sBAAsB,EAAO,YAAY,EAAE,GAE5E,IAAkB,WAAkB,KACpC,EAAO,QAAU,aAAiB,EAAU,QAAQ,uBAAuB,EAAO,YAAY,EAAE,EAChG,EAAO,QAAU,SAAa,EAAU,QAAQ,oBAAoB,EAAO,YAAY,EAAE,EAC/F"}
@@ -1,2 +0,0 @@
1
- const e=require("../../../_virtual/_rolldown/runtime.cjs");let t=require("node:fs"),n=require("node:fs/promises"),r=require("node:path");r=e.__toESM(r,1);let i=require("node:url");function a(){let e=r.default.dirname((0,i.fileURLToPath)(require("url").pathToFileURL(__filename).href));for(;!(0,t.existsSync)(r.default.join(e,`package.json`));){let t=r.default.dirname(e);if(t===e)throw Error(`Workflow package resources are unavailable.`);e=t}return e}function o(){return r.default.join(a(),`skills`)}async function s(e){return(0,n.readFile)(r.default.join(a(),e===`workflow-recovery`?`skills`:`src/prompts`,e,`SKILL.md`),`utf8`)}exports.readWorkflowSkill=s,exports.workflowSkillDirectory=o;
2
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","names":["path","fileURLToPath","existsSync","readFile"],"sources":["../../../../src/services/workflowResource/index.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nfunction packageRoot(): string {\n let directory = path.dirname(fileURLToPath(import.meta.url));\n while (!existsSync(path.join(directory, 'package.json'))) {\n const parent = path.dirname(directory);\n if (parent === directory) throw new Error('Workflow package resources are unavailable.');\n directory = parent;\n }\n return directory;\n}\nexport function workflowSkillDirectory(): string {\n return path.join(packageRoot(), 'skills');\n}\nexport async function readWorkflowSkill(name: string): Promise<string> {\n return readFile(\n path.join(packageRoot(), name === 'workflow-recovery' ? 'skills' : 'src/prompts', name, 'SKILL.md'),\n 'utf8',\n );\n}\n"],"mappings":"oLAIA,SAAS,GAAsB,CAC7B,IAAI,EAAYA,EAAAA,QAAK,SAAA,EAAQC,EAAAA,cAAAA,CAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,IAA6B,CAAC,EAC3D,KAAO,EAAA,EAACC,EAAAA,WAAAA,CAAWF,EAAAA,QAAK,KAAK,EAAW,cAAc,CAAC,GAAG,CACxD,IAAM,EAASA,EAAAA,QAAK,QAAQ,CAAS,EACrC,GAAI,IAAW,EAAW,MAAU,MAAM,6CAA6C,EACvF,EAAY,CACd,CACA,OAAO,CACT,CACA,SAAgB,GAAiC,CAC/C,OAAOA,EAAAA,QAAK,KAAK,EAAY,EAAG,QAAQ,CAC1C,CACA,eAAsB,EAAkB,EAA+B,CACrE,OAAA,EAAOG,EAAAA,SAAAA,CACLH,EAAAA,QAAK,KAAK,EAAY,EAAG,IAAS,oBAAsB,SAAW,cAAe,EAAM,UAAU,EAClG,MACF,CACF"}
@@ -1,2 +0,0 @@
1
- import{existsSync as e}from"node:fs";import{readFile as t}from"node:fs/promises";import n from"node:path";import{fileURLToPath as r}from"node:url";function i(){let t=n.dirname(r(import.meta.url));for(;!e(n.join(t,`package.json`));){let e=n.dirname(t);if(e===t)throw Error(`Workflow package resources are unavailable.`);t=e}return t}function a(){return n.join(i(),`skills`)}async function o(e){return t(n.join(i(),e===`workflow-recovery`?`skills`:`src/prompts`,e,`SKILL.md`),`utf8`)}export{o as readWorkflowSkill,a as workflowSkillDirectory};
2
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/workflowResource/index.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nfunction packageRoot(): string {\n let directory = path.dirname(fileURLToPath(import.meta.url));\n while (!existsSync(path.join(directory, 'package.json'))) {\n const parent = path.dirname(directory);\n if (parent === directory) throw new Error('Workflow package resources are unavailable.');\n directory = parent;\n }\n return directory;\n}\nexport function workflowSkillDirectory(): string {\n return path.join(packageRoot(), 'skills');\n}\nexport async function readWorkflowSkill(name: string): Promise<string> {\n return readFile(\n path.join(packageRoot(), name === 'workflow-recovery' ? 'skills' : 'src/prompts', name, 'SKILL.md'),\n 'utf8',\n );\n}\n"],"mappings":"mJAIA,SAAS,GAAsB,CAC7B,IAAI,EAAY,EAAK,QAAQ,EAAc,YAAY,GAAG,CAAC,EAC3D,KAAO,CAAC,EAAW,EAAK,KAAK,EAAW,cAAc,CAAC,GAAG,CACxD,IAAM,EAAS,EAAK,QAAQ,CAAS,EACrC,GAAI,IAAW,EAAW,MAAU,MAAM,6CAA6C,EACvF,EAAY,CACd,CACA,OAAO,CACT,CACA,SAAgB,GAAiC,CAC/C,OAAO,EAAK,KAAK,EAAY,EAAG,QAAQ,CAC1C,CACA,eAAsB,EAAkB,EAA+B,CACrE,OAAO,EACL,EAAK,KAAK,EAAY,EAAG,IAAS,oBAAsB,SAAW,cAAe,EAAM,UAAU,EAClG,MACF,CACF"}
@@ -1,3 +0,0 @@
1
- let e=require("node:path");const t=[`running`,`completed`,`error`],n=864e5,r=`PI_SESSION_ID`,i=new Set([`running`,`completed`,`skipped`,`failed`,`pause_requested`,`paused`,`resumed`]),a=new Set([`success`,`skipped`,`failed`,`interrupted`]),o=new Set([`running`,`pause_requested`,`paused`,`resume_requested`]),s=new Set([`completed`,`skipped`,`failed`]),c=`post`,l=/\u001b\[[0-9;]*[A-Za-z]/g;function u(t){return(0,e.resolve)(t.envValue!==void 0&&t.envValue!==``?t.envValue:(0,e.resolve)(t.homeDir,`.workflow-mcp`))}function d(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function f(e){return typeof e==`string`&&e!==``?e:void 0}function p(e){return typeof e==`number`&&Number.isFinite(e)?e:void 0}function m(e){return e.replace(l,``).trim()}function h(e){let n;try{n=JSON.parse(e)}catch{return}if(!d(n))return;let i=f(n.runKey),s=f(n.workspace),c=f(n.workflowPath),l=f(n.startedAt),u=f(n.stage);if(!i||!s||!c||!l||u===void 0||!t.includes(u))return;let p=f(n.outcome),h=f(n.executionState),g=f(n.errorMessage),_=d(n.env)?n.env:void 0,v={runKey:i,workspace:s,displayName:f(n.displayName)??i,...f(n.workflowName)===void 0?{}:{workflowName:f(n.workflowName)},workflowPath:c,stage:u,...p!==void 0&&a.has(p)?{outcome:p}:{},...h!==void 0&&o.has(h)?{executionState:h}:{},...f(n.prompt)===void 0?{}:{prompt:f(n.prompt)},startedAt:l,...f(n.finishedAt)===void 0?{}:{finishedAt:f(n.finishedAt)},...g===void 0?{}:{errorMessage:m(g)},...f(n.failedJob)===void 0?{}:{failedJob:f(n.failedJob)},...n.stale===!0?{stale:!0}:{},...f(n.staleReason)===void 0?{}:{staleReason:f(n.staleReason)},...f(n.worktreeBranch)===void 0?{}:{worktreeBranch:f(n.worktreeBranch)},jobs:[]},y=_===void 0?void 0:f(_[r]);return{view:v,...y===void 0?{}:{piSessionId:y}}}function g(e){let t=[];for(let n of e.split(`
2
- `)){let e=n.trim();if(e===``)continue;let r;try{r=JSON.parse(e)}catch{continue}if(!d(r))continue;let a=r.type,o=f(r.status),s=f(r.job),c=f(r.at);if(a!==`job`&&a!==`step`||!s||!c||o===void 0||!i.has(o))continue;let l=f(r.step),u=p(r.index),m=p(r.total),h=f(r.reason);t.push({type:a,status:o,job:s,...l===void 0?{}:{step:l},...u===void 0?{}:{index:u},...m===void 0?{}:{total:m},...h===void 0?{}:{reason:h},at:c})}return t}function _(e){return e===`pre`?`pre`:e===c?c:`job`}function v(e){let t=[],n=new Map;for(let r of e){let e=n.get(r.job);if(e===void 0&&(e={name:r.job,phase:_(r.job),status:r.status,steps:[]},n.set(r.job,e),t.push(e)),r.type===`job`){e.status=r.status,r.reason!==void 0&&(e.reason=r.reason),r.index!==void 0&&(e.index=r.index),r.total!==void 0&&(e.total=r.total),r.status===`running`&&e.startedAt===void 0&&(e.startedAt=r.at),s.has(r.status)&&(e.endedAt=r.at);continue}if(r.step===void 0)continue;let i=e.steps.find(e=>e.name===r.step);i===void 0&&(i={name:r.step,status:r.status},e.steps.push(i)),i.status=r.status,r.reason!==void 0&&(i.reason=r.reason),r.status===`running`&&i.startedAt===void 0&&(i.startedAt=r.at),s.has(r.status)&&(i.endedAt=r.at)}return t}const y=new Set([`running`,`pause_requested`,`paused`,`resumed`]);function b(e){for(let t=e.length-1;t>=0;--t){let n=e[t];if(y.has(n.status)){for(let e=n.steps.length-1;e>=0;--e){let t=n.steps[e];if(y.has(t.status))return{job:n.name,step:t.name,...n.index===void 0?{}:{index:n.index},...n.total===void 0?{}:{total:n.total}}}return{job:n.name,...n.index===void 0?{}:{index:n.index},...n.total===void 0?{}:{total:n.total}}}}}function x(e,t){let n=e.stage===`running`?b(t):void 0;return{...e,jobs:t,...n===void 0?{}:{position:n}}}function S(e,t){return e.piSessionId!==void 0&&e.piSessionId===t}function C(e){if(e===void 0)return 0;let t=Date.parse(e);return Number.isFinite(t)?t:0}const w={running:0,error:1,completed:2};function T(e,t){return e.stage!==`error`||t-(C(e.finishedAt)||C(e.startedAt))<n}function E(e){return e.stage===`running`?C(e.startedAt):C(e.finishedAt)||C(e.startedAt)}function D(e,t){let n=new Map;for(let r of e){if(!T(r,t))continue;let e=n.get(r.stage);e?e.push(r):n.set(r.stage,[r])}return[...n.entries()].sort(([e],[t])=>w[e]-w[t]).flatMap(([,e])=>e.sort((e,t)=>E(t)-E(e)).slice(0,24))}exports.PI_SESSION_ENV=r,exports.PROGRESS_FILE_NAME=`progress.ndjson`,exports.RUN_RECORD_FILE_NAME=`run.json`,exports.WORKFLOW_ERROR_RETENTION_MS=n,exports.WORKFLOW_HOME_ENV=`WORKFLOW_MCP_HOME`,exports.WORKFLOW_STAGES=t,exports.WORKSPACES_DIR_NAME=`workspaces`,exports.completeWorkflowRunView=x,exports.foldWorkflowProgress=v,exports.parseWorkflowProgress=g,exports.parseWorkflowRunRecord=h,exports.presentWorkflowRuns=D,exports.resolveWorkflowHome=u,exports.runBelongsToSession=S,exports.workflowPosition=b;
3
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","names":["resolve"],"sources":["../../../../src/services/workflowRuns/index.ts"],"sourcesContent":["import { resolve } from 'node:path';\n\nimport type {\n WorkflowJobPhase,\n WorkflowJobView,\n WorkflowOutcome,\n WorkflowPosition,\n WorkflowProgressState,\n WorkflowRunView,\n WorkflowStage,\n WorkflowStepView,\n} from '../../types/webWorkflows';\n\n/**\n * MIRRORS @agimon-ai/workflow-mcp's on-disk registry\n * (src/services/WorkflowRegistryService.ts and WorkflowProgressService.ts in\n * that package). The engine keeps one directory per run under\n * `<home>/workspaces/<workspace>/<stage>/<runKey>/` holding a `run.json`\n * record and an append-only `progress.ndjson` job/step log; the hub only ever\n * reads them. The derivation is duplicated rather than imported because\n * doompi-web must not depend on the engine at runtime; the unit suite pins\n * this mirror against the package's published types.\n */\nconst DEFAULT_WORKFLOW_HOME_DIR_NAME = '.workflow-mcp';\nexport const WORKFLOW_HOME_ENV = 'WORKFLOW_MCP_HOME';\nexport const WORKSPACES_DIR_NAME = 'workspaces';\nexport const RUN_RECORD_FILE_NAME = 'run.json';\nexport const PROGRESS_FILE_NAME = 'progress.ndjson';\nexport const WORKFLOW_STAGES: readonly WorkflowStage[] = ['running', 'completed', 'error'];\n\n/**\n * Errored runs stay a day, which is how long recovery has to act on them.\n *\n * Finished runs have no retention of their own: a session keeps every run it\n * launched, grouped by outcome, for as long as the session lives. A run leaves\n * the view when the engine prunes its registry directory, not on a timer here,\n * because \"what did this session run today\" is a question asked long after the\n * run settled.\n */\nexport const WORKFLOW_ERROR_RETENTION_MS = 24 * 60 * 60 * 1000;\n/**\n * Upper bound per outcome group rather than over the whole list.\n *\n * One cap across every run let a long stream of green runs push a failure out\n * of the list, which is the one thing that must never fall off the end.\n */\nexport const MAX_PRESENTED_WORKFLOW_RUNS_PER_GROUP = 24;\n\n/** The env key doompi-workflow stamps on runs it launches; ties a run to a Pi session. */\nexport const PI_SESSION_ENV = 'PI_SESSION_ID';\n\nconst PROGRESS_STATES: ReadonlySet<string> = new Set([\n 'running',\n 'completed',\n 'skipped',\n 'failed',\n 'pause_requested',\n 'paused',\n 'resumed',\n]);\nconst OUTCOMES: ReadonlySet<string> = new Set(['success', 'skipped', 'failed', 'interrupted']);\nconst EXECUTION_STATES: ReadonlySet<string> = new Set(['running', 'pause_requested', 'paused', 'resume_requested']);\nconst STEP_TERMINAL_STATES: ReadonlySet<WorkflowProgressState> = new Set(['completed', 'skipped', 'failed']);\n/** The engine records its pre:/post: blocks under these reserved pseudo-job names. */\nconst PHASE_JOB_PRE = 'pre';\nconst PHASE_JOB_POST = 'post';\n\n// oxlint-disable-next-line no-control-regex -- stripping terminal color codes from errorMessage is the point\nconst ANSI_ESCAPE_PATTERN = /\\u001b\\[[0-9;]*[A-Za-z]/g;\n\nexport interface WorkflowHomeInput {\n /** process.env.WORKFLOW_MCP_HOME, supplied by the caller. */\n envValue: string | undefined;\n /** os.homedir(), supplied by the caller. */\n homeDir: string;\n}\n\n/** Where workflow-mcp keeps its registry, mirroring the engine's own resolution. */\nexport function resolveWorkflowHome(input: WorkflowHomeInput): string {\n return resolve(\n input.envValue !== undefined && input.envValue !== ''\n ? input.envValue\n : resolve(input.homeDir, DEFAULT_WORKFLOW_HOME_DIR_NAME),\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction asOptionalString(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined;\n}\n\nfunction asOptionalNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction stripAnsi(text: string): string {\n return text.replace(ANSI_ESCAPE_PATTERN, '').trim();\n}\n\n/**\n * One run record plus the facts the hub needs for session scoping but the\n * page never sees.\n */\nexport interface ParsedWorkflowRun {\n /** The wire view, before the progress log is folded in (jobs empty). */\n view: WorkflowRunView;\n /** env.PI_SESSION_ID from the record, when the launcher stamped one. */\n piSessionId?: string;\n}\n\n/**\n * Validates one run.json into the wire shape plus scoping facts.\n *\n * Returns undefined for anything unreadable or a foreign format. The record\n * is trusted for its own stage: the registry rewrites it when a run moves\n * between stage directories.\n */\nexport function parseWorkflowRunRecord(raw: string): ParsedWorkflowRun | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (!isRecord(parsed)) return undefined;\n const runKey = asOptionalString(parsed.runKey);\n const workspace = asOptionalString(parsed.workspace);\n const workflowPath = asOptionalString(parsed.workflowPath);\n const startedAt = asOptionalString(parsed.startedAt);\n const stage = asOptionalString(parsed.stage);\n if (!runKey || !workspace || !workflowPath || !startedAt) return undefined;\n if (stage === undefined || !WORKFLOW_STAGES.includes(stage as WorkflowStage)) return undefined;\n\n const outcome = asOptionalString(parsed.outcome);\n const executionState = asOptionalString(parsed.executionState);\n const rawError = asOptionalString(parsed.errorMessage);\n const env = isRecord(parsed.env) ? parsed.env : undefined;\n const view: WorkflowRunView = {\n runKey,\n workspace,\n displayName: asOptionalString(parsed.displayName) ?? runKey,\n ...(asOptionalString(parsed.workflowName) === undefined\n ? {}\n : { workflowName: asOptionalString(parsed.workflowName) }),\n workflowPath,\n stage: stage as WorkflowStage,\n ...(outcome !== undefined && OUTCOMES.has(outcome) ? { outcome: outcome as WorkflowOutcome } : {}),\n ...(executionState !== undefined && EXECUTION_STATES.has(executionState)\n ? { executionState: executionState as WorkflowRunView['executionState'] }\n : {}),\n ...(asOptionalString(parsed.prompt) === undefined ? {} : { prompt: asOptionalString(parsed.prompt) }),\n startedAt,\n ...(asOptionalString(parsed.finishedAt) === undefined ? {} : { finishedAt: asOptionalString(parsed.finishedAt) }),\n ...(rawError === undefined ? {} : { errorMessage: stripAnsi(rawError) }),\n ...(asOptionalString(parsed.failedJob) === undefined ? {} : { failedJob: asOptionalString(parsed.failedJob) }),\n ...(parsed.stale === true ? { stale: true } : {}),\n ...(asOptionalString(parsed.staleReason) === undefined\n ? {}\n : { staleReason: asOptionalString(parsed.staleReason) }),\n ...(asOptionalString(parsed.worktreeBranch) === undefined\n ? {}\n : { worktreeBranch: asOptionalString(parsed.worktreeBranch) }),\n jobs: [],\n };\n const piSessionId = env === undefined ? undefined : asOptionalString(env[PI_SESSION_ENV]);\n return { view, ...(piSessionId === undefined ? {} : { piSessionId }) };\n}\n\ninterface WorkflowProgressEvent {\n type: 'job' | 'step';\n status: WorkflowProgressState;\n job: string;\n step?: string;\n index?: number;\n total?: number;\n reason?: string;\n at: string;\n}\n\n/**\n * Parses the append-only progress log. Malformed lines are skipped rather\n * than failing the file: a reader routinely catches a torn final line\n * mid-append.\n */\nexport function parseWorkflowProgress(raw: string): WorkflowProgressEvent[] {\n const events: WorkflowProgressEvent[] = [];\n for (const line of raw.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed === '') continue;\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n continue;\n }\n if (!isRecord(parsed)) continue;\n const type = parsed.type;\n const status = asOptionalString(parsed.status);\n const job = asOptionalString(parsed.job);\n const at = asOptionalString(parsed.at);\n if ((type !== 'job' && type !== 'step') || !job || !at) continue;\n if (status === undefined || !PROGRESS_STATES.has(status)) continue;\n const step = asOptionalString(parsed.step);\n const index = asOptionalNumber(parsed.index);\n const total = asOptionalNumber(parsed.total);\n const reason = asOptionalString(parsed.reason);\n events.push({\n type,\n status: status as WorkflowProgressState,\n job,\n ...(step === undefined ? {} : { step }),\n ...(index === undefined ? {} : { index }),\n ...(total === undefined ? {} : { total }),\n ...(reason === undefined ? {} : { reason }),\n at,\n });\n }\n return events;\n}\n\nfunction jobPhase(name: string): WorkflowJobPhase {\n if (name === PHASE_JOB_PRE) return PHASE_JOB_PRE;\n if (name === PHASE_JOB_POST) return PHASE_JOB_POST;\n return 'job';\n}\n\n/**\n * Folds the event log into the current job tree; later events win, so a job\n * that started and later failed reads as failed. Jobs and steps keep the\n * order of their first appearance.\n */\nexport function foldWorkflowProgress(events: readonly WorkflowProgressEvent[]): WorkflowJobView[] {\n const jobs: WorkflowJobView[] = [];\n const jobByName = new Map<string, WorkflowJobView>();\n for (const event of events) {\n let job = jobByName.get(event.job);\n if (job === undefined) {\n job = { name: event.job, phase: jobPhase(event.job), status: event.status, steps: [] };\n jobByName.set(event.job, job);\n jobs.push(job);\n }\n if (event.type === 'job') {\n job.status = event.status;\n if (event.reason !== undefined) job.reason = event.reason;\n if (event.index !== undefined) job.index = event.index;\n if (event.total !== undefined) job.total = event.total;\n if (event.status === 'running' && job.startedAt === undefined) job.startedAt = event.at;\n if (STEP_TERMINAL_STATES.has(event.status)) job.endedAt = event.at;\n continue;\n }\n if (event.step === undefined) continue;\n let step: WorkflowStepView | undefined = job.steps.find((candidate) => candidate.name === event.step);\n if (step === undefined) {\n step = { name: event.step, status: event.status };\n job.steps.push(step);\n }\n step.status = event.status;\n if (event.reason !== undefined) step.reason = event.reason;\n if (event.status === 'running' && step.startedAt === undefined) step.startedAt = event.at;\n if (STEP_TERMINAL_STATES.has(event.status)) step.endedAt = event.at;\n }\n return jobs;\n}\n\nconst ACTIVE_PROGRESS_STATES: ReadonlySet<WorkflowProgressState> = new Set([\n 'running',\n 'pause_requested',\n 'paused',\n 'resumed',\n]);\n\n/** The job and step a run is on right now, for the NOW breadcrumb. */\nexport function workflowPosition(jobs: readonly WorkflowJobView[]): WorkflowPosition | undefined {\n for (let jobIndex = jobs.length - 1; jobIndex >= 0; jobIndex -= 1) {\n const job = jobs[jobIndex]!;\n if (!ACTIVE_PROGRESS_STATES.has(job.status)) continue;\n for (let stepIndex = job.steps.length - 1; stepIndex >= 0; stepIndex -= 1) {\n const step = job.steps[stepIndex]!;\n if (ACTIVE_PROGRESS_STATES.has(step.status)) {\n return {\n job: job.name,\n step: step.name,\n ...(job.index === undefined ? {} : { index: job.index }),\n ...(job.total === undefined ? {} : { total: job.total }),\n };\n }\n }\n return {\n job: job.name,\n ...(job.index === undefined ? {} : { index: job.index }),\n ...(job.total === undefined ? {} : { total: job.total }),\n };\n }\n return undefined;\n}\n\n/** The finished view: record fields plus the folded job tree and position. */\nexport function completeWorkflowRunView(view: WorkflowRunView, jobs: WorkflowJobView[]): WorkflowRunView {\n const position = view.stage === 'running' ? workflowPosition(jobs) : undefined;\n return { ...view, jobs, ...(position === undefined ? {} : { position }) };\n}\n\n/**\n * Whether a run belongs on a session's workflow tab: launched by that Pi\n * session, which is the same env test doompi-workflow's isSessionRun applies.\n *\n * The registry is one directory under $HOME shared by every repository and\n * every Pi session on the machine, so repository proximity is not ownership.\n * Scoping on it as well let two sessions open in one repo read each other's\n * history, and kept a dead session's runs on the board of every session that\n * replaced it. Fails closed on an unstamped record, which is what a launch\n * from the CLI rather than from a session leaves behind.\n */\nexport function runBelongsToSession(run: ParsedWorkflowRun, sessionId: string): boolean {\n return run.piSessionId !== undefined && run.piSessionId === sessionId;\n}\n\nfunction parseTime(value: string | undefined): number {\n if (value === undefined) return 0;\n const time = Date.parse(value);\n return Number.isFinite(time) ? time : 0;\n}\n\nconst STAGE_ORDER: Readonly<Record<WorkflowStage, number>> = { running: 0, error: 1, completed: 2 };\n\n/**\n * Whether a run still belongs in the view.\n *\n * Only errored runs age out here, and slowly: everything else a session\n * started stays until the engine forgets the run itself.\n */\nfunction withinRetention(run: WorkflowRunView, now: number): boolean {\n if (run.stage !== 'error') return true;\n const settledAt = parseTime(run.finishedAt) || parseTime(run.startedAt);\n return now - settledAt < WORKFLOW_ERROR_RETENTION_MS;\n}\n\n/**\n * When a run last moved, which is what orders it within its group.\n *\n * A running run has only its start; a settled one is ordered by when it\n * settled, because a list kept for the whole session is read as a history and\n * two runs started together can finish an hour apart.\n */\nfunction lastMovedAt(run: WorkflowRunView): number {\n return run.stage === 'running' ? parseTime(run.startedAt) : parseTime(run.finishedAt) || parseTime(run.startedAt);\n}\n\n/**\n * Running runs first, then errored (recovery acts on them), then finished;\n * newest first within a group, and each group capped on its own.\n */\nexport function presentWorkflowRuns(runs: readonly WorkflowRunView[], now: number): WorkflowRunView[] {\n const groups = new Map<WorkflowStage, WorkflowRunView[]>();\n for (const run of runs) {\n if (!withinRetention(run, now)) continue;\n const group = groups.get(run.stage);\n if (group) group.push(run);\n else groups.set(run.stage, [run]);\n }\n return [...groups.entries()]\n .sort(([left], [right]) => STAGE_ORDER[left] - STAGE_ORDER[right])\n .flatMap(([, group]) =>\n group\n .sort((left, right) => lastMovedAt(right) - lastMovedAt(left))\n .slice(0, MAX_PRESENTED_WORKFLOW_RUNS_PER_GROUP),\n );\n}\n"],"mappings":"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"}
@@ -1,3 +0,0 @@
1
- import{resolve as e}from"node:path";const t=`WORKFLOW_MCP_HOME`,n=`workspaces`,r=`run.json`,i=`progress.ndjson`,a=[`running`,`completed`,`error`],o=864e5,s=`PI_SESSION_ID`,c=new Set([`running`,`completed`,`skipped`,`failed`,`pause_requested`,`paused`,`resumed`]),l=new Set([`success`,`skipped`,`failed`,`interrupted`]),u=new Set([`running`,`pause_requested`,`paused`,`resume_requested`]),d=new Set([`completed`,`skipped`,`failed`]),f=`post`,p=/\u001b\[[0-9;]*[A-Za-z]/g;function m(t){return e(t.envValue!==void 0&&t.envValue!==``?t.envValue:e(t.homeDir,`.workflow-mcp`))}function h(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function g(e){return typeof e==`string`&&e!==``?e:void 0}function _(e){return typeof e==`number`&&Number.isFinite(e)?e:void 0}function v(e){return e.replace(p,``).trim()}function y(e){let t;try{t=JSON.parse(e)}catch{return}if(!h(t))return;let n=g(t.runKey),r=g(t.workspace),i=g(t.workflowPath),o=g(t.startedAt),c=g(t.stage);if(!n||!r||!i||!o||c===void 0||!a.includes(c))return;let d=g(t.outcome),f=g(t.executionState),p=g(t.errorMessage),m=h(t.env)?t.env:void 0,_={runKey:n,workspace:r,displayName:g(t.displayName)??n,...g(t.workflowName)===void 0?{}:{workflowName:g(t.workflowName)},workflowPath:i,stage:c,...d!==void 0&&l.has(d)?{outcome:d}:{},...f!==void 0&&u.has(f)?{executionState:f}:{},...g(t.prompt)===void 0?{}:{prompt:g(t.prompt)},startedAt:o,...g(t.finishedAt)===void 0?{}:{finishedAt:g(t.finishedAt)},...p===void 0?{}:{errorMessage:v(p)},...g(t.failedJob)===void 0?{}:{failedJob:g(t.failedJob)},...t.stale===!0?{stale:!0}:{},...g(t.staleReason)===void 0?{}:{staleReason:g(t.staleReason)},...g(t.worktreeBranch)===void 0?{}:{worktreeBranch:g(t.worktreeBranch)},jobs:[]},y=m===void 0?void 0:g(m[s]);return{view:_,...y===void 0?{}:{piSessionId:y}}}function b(e){let t=[];for(let n of e.split(`
2
- `)){let e=n.trim();if(e===``)continue;let r;try{r=JSON.parse(e)}catch{continue}if(!h(r))continue;let i=r.type,a=g(r.status),o=g(r.job),s=g(r.at);if(i!==`job`&&i!==`step`||!o||!s||a===void 0||!c.has(a))continue;let l=g(r.step),u=_(r.index),d=_(r.total),f=g(r.reason);t.push({type:i,status:a,job:o,...l===void 0?{}:{step:l},...u===void 0?{}:{index:u},...d===void 0?{}:{total:d},...f===void 0?{}:{reason:f},at:s})}return t}function x(e){return e===`pre`?`pre`:e===f?f:`job`}function S(e){let t=[],n=new Map;for(let r of e){let e=n.get(r.job);if(e===void 0&&(e={name:r.job,phase:x(r.job),status:r.status,steps:[]},n.set(r.job,e),t.push(e)),r.type===`job`){e.status=r.status,r.reason!==void 0&&(e.reason=r.reason),r.index!==void 0&&(e.index=r.index),r.total!==void 0&&(e.total=r.total),r.status===`running`&&e.startedAt===void 0&&(e.startedAt=r.at),d.has(r.status)&&(e.endedAt=r.at);continue}if(r.step===void 0)continue;let i=e.steps.find(e=>e.name===r.step);i===void 0&&(i={name:r.step,status:r.status},e.steps.push(i)),i.status=r.status,r.reason!==void 0&&(i.reason=r.reason),r.status===`running`&&i.startedAt===void 0&&(i.startedAt=r.at),d.has(r.status)&&(i.endedAt=r.at)}return t}const C=new Set([`running`,`pause_requested`,`paused`,`resumed`]);function w(e){for(let t=e.length-1;t>=0;--t){let n=e[t];if(C.has(n.status)){for(let e=n.steps.length-1;e>=0;--e){let t=n.steps[e];if(C.has(t.status))return{job:n.name,step:t.name,...n.index===void 0?{}:{index:n.index},...n.total===void 0?{}:{total:n.total}}}return{job:n.name,...n.index===void 0?{}:{index:n.index},...n.total===void 0?{}:{total:n.total}}}}}function T(e,t){let n=e.stage===`running`?w(t):void 0;return{...e,jobs:t,...n===void 0?{}:{position:n}}}function E(e,t){return e.piSessionId!==void 0&&e.piSessionId===t}function D(e){if(e===void 0)return 0;let t=Date.parse(e);return Number.isFinite(t)?t:0}const O={running:0,error:1,completed:2};function k(e,t){return e.stage!==`error`||t-(D(e.finishedAt)||D(e.startedAt))<o}function A(e){return e.stage===`running`?D(e.startedAt):D(e.finishedAt)||D(e.startedAt)}function j(e,t){let n=new Map;for(let r of e){if(!k(r,t))continue;let e=n.get(r.stage);e?e.push(r):n.set(r.stage,[r])}return[...n.entries()].sort(([e],[t])=>O[e]-O[t]).flatMap(([,e])=>e.sort((e,t)=>A(t)-A(e)).slice(0,24))}export{s as PI_SESSION_ENV,i as PROGRESS_FILE_NAME,r as RUN_RECORD_FILE_NAME,o as WORKFLOW_ERROR_RETENTION_MS,t as WORKFLOW_HOME_ENV,a as WORKFLOW_STAGES,n as WORKSPACES_DIR_NAME,T as completeWorkflowRunView,S as foldWorkflowProgress,b as parseWorkflowProgress,y as parseWorkflowRunRecord,j as presentWorkflowRuns,m as resolveWorkflowHome,E as runBelongsToSession,w as workflowPosition};
3
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/workflowRuns/index.ts"],"sourcesContent":["import { resolve } from 'node:path';\n\nimport type {\n WorkflowJobPhase,\n WorkflowJobView,\n WorkflowOutcome,\n WorkflowPosition,\n WorkflowProgressState,\n WorkflowRunView,\n WorkflowStage,\n WorkflowStepView,\n} from '../../types/webWorkflows';\n\n/**\n * MIRRORS @agimon-ai/workflow-mcp's on-disk registry\n * (src/services/WorkflowRegistryService.ts and WorkflowProgressService.ts in\n * that package). The engine keeps one directory per run under\n * `<home>/workspaces/<workspace>/<stage>/<runKey>/` holding a `run.json`\n * record and an append-only `progress.ndjson` job/step log; the hub only ever\n * reads them. The derivation is duplicated rather than imported because\n * doompi-web must not depend on the engine at runtime; the unit suite pins\n * this mirror against the package's published types.\n */\nconst DEFAULT_WORKFLOW_HOME_DIR_NAME = '.workflow-mcp';\nexport const WORKFLOW_HOME_ENV = 'WORKFLOW_MCP_HOME';\nexport const WORKSPACES_DIR_NAME = 'workspaces';\nexport const RUN_RECORD_FILE_NAME = 'run.json';\nexport const PROGRESS_FILE_NAME = 'progress.ndjson';\nexport const WORKFLOW_STAGES: readonly WorkflowStage[] = ['running', 'completed', 'error'];\n\n/**\n * Errored runs stay a day, which is how long recovery has to act on them.\n *\n * Finished runs have no retention of their own: a session keeps every run it\n * launched, grouped by outcome, for as long as the session lives. A run leaves\n * the view when the engine prunes its registry directory, not on a timer here,\n * because \"what did this session run today\" is a question asked long after the\n * run settled.\n */\nexport const WORKFLOW_ERROR_RETENTION_MS = 24 * 60 * 60 * 1000;\n/**\n * Upper bound per outcome group rather than over the whole list.\n *\n * One cap across every run let a long stream of green runs push a failure out\n * of the list, which is the one thing that must never fall off the end.\n */\nexport const MAX_PRESENTED_WORKFLOW_RUNS_PER_GROUP = 24;\n\n/** The env key doompi-workflow stamps on runs it launches; ties a run to a Pi session. */\nexport const PI_SESSION_ENV = 'PI_SESSION_ID';\n\nconst PROGRESS_STATES: ReadonlySet<string> = new Set([\n 'running',\n 'completed',\n 'skipped',\n 'failed',\n 'pause_requested',\n 'paused',\n 'resumed',\n]);\nconst OUTCOMES: ReadonlySet<string> = new Set(['success', 'skipped', 'failed', 'interrupted']);\nconst EXECUTION_STATES: ReadonlySet<string> = new Set(['running', 'pause_requested', 'paused', 'resume_requested']);\nconst STEP_TERMINAL_STATES: ReadonlySet<WorkflowProgressState> = new Set(['completed', 'skipped', 'failed']);\n/** The engine records its pre:/post: blocks under these reserved pseudo-job names. */\nconst PHASE_JOB_PRE = 'pre';\nconst PHASE_JOB_POST = 'post';\n\n// oxlint-disable-next-line no-control-regex -- stripping terminal color codes from errorMessage is the point\nconst ANSI_ESCAPE_PATTERN = /\\u001b\\[[0-9;]*[A-Za-z]/g;\n\nexport interface WorkflowHomeInput {\n /** process.env.WORKFLOW_MCP_HOME, supplied by the caller. */\n envValue: string | undefined;\n /** os.homedir(), supplied by the caller. */\n homeDir: string;\n}\n\n/** Where workflow-mcp keeps its registry, mirroring the engine's own resolution. */\nexport function resolveWorkflowHome(input: WorkflowHomeInput): string {\n return resolve(\n input.envValue !== undefined && input.envValue !== ''\n ? input.envValue\n : resolve(input.homeDir, DEFAULT_WORKFLOW_HOME_DIR_NAME),\n );\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction asOptionalString(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined;\n}\n\nfunction asOptionalNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction stripAnsi(text: string): string {\n return text.replace(ANSI_ESCAPE_PATTERN, '').trim();\n}\n\n/**\n * One run record plus the facts the hub needs for session scoping but the\n * page never sees.\n */\nexport interface ParsedWorkflowRun {\n /** The wire view, before the progress log is folded in (jobs empty). */\n view: WorkflowRunView;\n /** env.PI_SESSION_ID from the record, when the launcher stamped one. */\n piSessionId?: string;\n}\n\n/**\n * Validates one run.json into the wire shape plus scoping facts.\n *\n * Returns undefined for anything unreadable or a foreign format. The record\n * is trusted for its own stage: the registry rewrites it when a run moves\n * between stage directories.\n */\nexport function parseWorkflowRunRecord(raw: string): ParsedWorkflowRun | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (!isRecord(parsed)) return undefined;\n const runKey = asOptionalString(parsed.runKey);\n const workspace = asOptionalString(parsed.workspace);\n const workflowPath = asOptionalString(parsed.workflowPath);\n const startedAt = asOptionalString(parsed.startedAt);\n const stage = asOptionalString(parsed.stage);\n if (!runKey || !workspace || !workflowPath || !startedAt) return undefined;\n if (stage === undefined || !WORKFLOW_STAGES.includes(stage as WorkflowStage)) return undefined;\n\n const outcome = asOptionalString(parsed.outcome);\n const executionState = asOptionalString(parsed.executionState);\n const rawError = asOptionalString(parsed.errorMessage);\n const env = isRecord(parsed.env) ? parsed.env : undefined;\n const view: WorkflowRunView = {\n runKey,\n workspace,\n displayName: asOptionalString(parsed.displayName) ?? runKey,\n ...(asOptionalString(parsed.workflowName) === undefined\n ? {}\n : { workflowName: asOptionalString(parsed.workflowName) }),\n workflowPath,\n stage: stage as WorkflowStage,\n ...(outcome !== undefined && OUTCOMES.has(outcome) ? { outcome: outcome as WorkflowOutcome } : {}),\n ...(executionState !== undefined && EXECUTION_STATES.has(executionState)\n ? { executionState: executionState as WorkflowRunView['executionState'] }\n : {}),\n ...(asOptionalString(parsed.prompt) === undefined ? {} : { prompt: asOptionalString(parsed.prompt) }),\n startedAt,\n ...(asOptionalString(parsed.finishedAt) === undefined ? {} : { finishedAt: asOptionalString(parsed.finishedAt) }),\n ...(rawError === undefined ? {} : { errorMessage: stripAnsi(rawError) }),\n ...(asOptionalString(parsed.failedJob) === undefined ? {} : { failedJob: asOptionalString(parsed.failedJob) }),\n ...(parsed.stale === true ? { stale: true } : {}),\n ...(asOptionalString(parsed.staleReason) === undefined\n ? {}\n : { staleReason: asOptionalString(parsed.staleReason) }),\n ...(asOptionalString(parsed.worktreeBranch) === undefined\n ? {}\n : { worktreeBranch: asOptionalString(parsed.worktreeBranch) }),\n jobs: [],\n };\n const piSessionId = env === undefined ? undefined : asOptionalString(env[PI_SESSION_ENV]);\n return { view, ...(piSessionId === undefined ? {} : { piSessionId }) };\n}\n\ninterface WorkflowProgressEvent {\n type: 'job' | 'step';\n status: WorkflowProgressState;\n job: string;\n step?: string;\n index?: number;\n total?: number;\n reason?: string;\n at: string;\n}\n\n/**\n * Parses the append-only progress log. Malformed lines are skipped rather\n * than failing the file: a reader routinely catches a torn final line\n * mid-append.\n */\nexport function parseWorkflowProgress(raw: string): WorkflowProgressEvent[] {\n const events: WorkflowProgressEvent[] = [];\n for (const line of raw.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed === '') continue;\n let parsed: unknown;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n continue;\n }\n if (!isRecord(parsed)) continue;\n const type = parsed.type;\n const status = asOptionalString(parsed.status);\n const job = asOptionalString(parsed.job);\n const at = asOptionalString(parsed.at);\n if ((type !== 'job' && type !== 'step') || !job || !at) continue;\n if (status === undefined || !PROGRESS_STATES.has(status)) continue;\n const step = asOptionalString(parsed.step);\n const index = asOptionalNumber(parsed.index);\n const total = asOptionalNumber(parsed.total);\n const reason = asOptionalString(parsed.reason);\n events.push({\n type,\n status: status as WorkflowProgressState,\n job,\n ...(step === undefined ? {} : { step }),\n ...(index === undefined ? {} : { index }),\n ...(total === undefined ? {} : { total }),\n ...(reason === undefined ? {} : { reason }),\n at,\n });\n }\n return events;\n}\n\nfunction jobPhase(name: string): WorkflowJobPhase {\n if (name === PHASE_JOB_PRE) return PHASE_JOB_PRE;\n if (name === PHASE_JOB_POST) return PHASE_JOB_POST;\n return 'job';\n}\n\n/**\n * Folds the event log into the current job tree; later events win, so a job\n * that started and later failed reads as failed. Jobs and steps keep the\n * order of their first appearance.\n */\nexport function foldWorkflowProgress(events: readonly WorkflowProgressEvent[]): WorkflowJobView[] {\n const jobs: WorkflowJobView[] = [];\n const jobByName = new Map<string, WorkflowJobView>();\n for (const event of events) {\n let job = jobByName.get(event.job);\n if (job === undefined) {\n job = { name: event.job, phase: jobPhase(event.job), status: event.status, steps: [] };\n jobByName.set(event.job, job);\n jobs.push(job);\n }\n if (event.type === 'job') {\n job.status = event.status;\n if (event.reason !== undefined) job.reason = event.reason;\n if (event.index !== undefined) job.index = event.index;\n if (event.total !== undefined) job.total = event.total;\n if (event.status === 'running' && job.startedAt === undefined) job.startedAt = event.at;\n if (STEP_TERMINAL_STATES.has(event.status)) job.endedAt = event.at;\n continue;\n }\n if (event.step === undefined) continue;\n let step: WorkflowStepView | undefined = job.steps.find((candidate) => candidate.name === event.step);\n if (step === undefined) {\n step = { name: event.step, status: event.status };\n job.steps.push(step);\n }\n step.status = event.status;\n if (event.reason !== undefined) step.reason = event.reason;\n if (event.status === 'running' && step.startedAt === undefined) step.startedAt = event.at;\n if (STEP_TERMINAL_STATES.has(event.status)) step.endedAt = event.at;\n }\n return jobs;\n}\n\nconst ACTIVE_PROGRESS_STATES: ReadonlySet<WorkflowProgressState> = new Set([\n 'running',\n 'pause_requested',\n 'paused',\n 'resumed',\n]);\n\n/** The job and step a run is on right now, for the NOW breadcrumb. */\nexport function workflowPosition(jobs: readonly WorkflowJobView[]): WorkflowPosition | undefined {\n for (let jobIndex = jobs.length - 1; jobIndex >= 0; jobIndex -= 1) {\n const job = jobs[jobIndex]!;\n if (!ACTIVE_PROGRESS_STATES.has(job.status)) continue;\n for (let stepIndex = job.steps.length - 1; stepIndex >= 0; stepIndex -= 1) {\n const step = job.steps[stepIndex]!;\n if (ACTIVE_PROGRESS_STATES.has(step.status)) {\n return {\n job: job.name,\n step: step.name,\n ...(job.index === undefined ? {} : { index: job.index }),\n ...(job.total === undefined ? {} : { total: job.total }),\n };\n }\n }\n return {\n job: job.name,\n ...(job.index === undefined ? {} : { index: job.index }),\n ...(job.total === undefined ? {} : { total: job.total }),\n };\n }\n return undefined;\n}\n\n/** The finished view: record fields plus the folded job tree and position. */\nexport function completeWorkflowRunView(view: WorkflowRunView, jobs: WorkflowJobView[]): WorkflowRunView {\n const position = view.stage === 'running' ? workflowPosition(jobs) : undefined;\n return { ...view, jobs, ...(position === undefined ? {} : { position }) };\n}\n\n/**\n * Whether a run belongs on a session's workflow tab: launched by that Pi\n * session, which is the same env test doompi-workflow's isSessionRun applies.\n *\n * The registry is one directory under $HOME shared by every repository and\n * every Pi session on the machine, so repository proximity is not ownership.\n * Scoping on it as well let two sessions open in one repo read each other's\n * history, and kept a dead session's runs on the board of every session that\n * replaced it. Fails closed on an unstamped record, which is what a launch\n * from the CLI rather than from a session leaves behind.\n */\nexport function runBelongsToSession(run: ParsedWorkflowRun, sessionId: string): boolean {\n return run.piSessionId !== undefined && run.piSessionId === sessionId;\n}\n\nfunction parseTime(value: string | undefined): number {\n if (value === undefined) return 0;\n const time = Date.parse(value);\n return Number.isFinite(time) ? time : 0;\n}\n\nconst STAGE_ORDER: Readonly<Record<WorkflowStage, number>> = { running: 0, error: 1, completed: 2 };\n\n/**\n * Whether a run still belongs in the view.\n *\n * Only errored runs age out here, and slowly: everything else a session\n * started stays until the engine forgets the run itself.\n */\nfunction withinRetention(run: WorkflowRunView, now: number): boolean {\n if (run.stage !== 'error') return true;\n const settledAt = parseTime(run.finishedAt) || parseTime(run.startedAt);\n return now - settledAt < WORKFLOW_ERROR_RETENTION_MS;\n}\n\n/**\n * When a run last moved, which is what orders it within its group.\n *\n * A running run has only its start; a settled one is ordered by when it\n * settled, because a list kept for the whole session is read as a history and\n * two runs started together can finish an hour apart.\n */\nfunction lastMovedAt(run: WorkflowRunView): number {\n return run.stage === 'running' ? parseTime(run.startedAt) : parseTime(run.finishedAt) || parseTime(run.startedAt);\n}\n\n/**\n * Running runs first, then errored (recovery acts on them), then finished;\n * newest first within a group, and each group capped on its own.\n */\nexport function presentWorkflowRuns(runs: readonly WorkflowRunView[], now: number): WorkflowRunView[] {\n const groups = new Map<WorkflowStage, WorkflowRunView[]>();\n for (const run of runs) {\n if (!withinRetention(run, now)) continue;\n const group = groups.get(run.stage);\n if (group) group.push(run);\n else groups.set(run.stage, [run]);\n }\n return [...groups.entries()]\n .sort(([left], [right]) => STAGE_ORDER[left] - STAGE_ORDER[right])\n .flatMap(([, group]) =>\n group\n .sort((left, right) => lastMovedAt(right) - lastMovedAt(left))\n .slice(0, MAX_PRESENTED_WORKFLOW_RUNS_PER_GROUP),\n );\n}\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- const e=`Another reader holds the keyboard for this run.`;function t(t){let n=t.refreshMs??750,r=t.leaseMs??6e4,i=new Map,a=new Map,o=e=>{let n=a.get(e);if(n!==void 0){if(n.expiresAt>t.now())return n;a.delete(e)}};return{async screen(e,r,a){let o=i.get(e)??{lines:[]};if(i.set(e,o),o.inFlight||o.at!==void 0&&t.now()-o.at<n)return o.lines;o.inFlight=t.terminal.screen(r,{lines:a});try{o.lines=await o.inFlight}finally{o.at=t.now(),o.inFlight=void 0}return o.lines},capabilities(e){return t.terminal.capabilities(e)},takeControl(e,n){let i=o(e);return i!==void 0&&i.token!==n?!1:(a.set(e,{token:n,expiresAt:t.now()+r}),!0)},releaseControl(e,t){o(e)?.token===t&&a.delete(e)},async write(n,i,s,c){let l=o(n);if(l===void 0||l.token!==s)throw Error(e);a.set(n,{token:s,expiresAt:t.now()+r}),await t.terminal.write(i,c)},async resize(n,r,i,a,s){let c=o(n);if(c===void 0||c.token!==i)throw Error(e);return t.terminal.resize(r,a,s)},forget(e){for(let t of i.keys())e.has(t)||i.delete(t);for(let t of a.keys())e.has(t)||a.delete(t)}}}exports.createWorkflowTerminalService=t;
2
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/services/workflowTerminal/index.ts"],"sourcesContent":["import type { WorkflowTerminalCapabilitiesView } from '../../types/webWorkflowTerminal';\n\n/**\n * One run's terminal, shared by every surface watching it.\n *\n * WHY COALESCING LIVES HERE:\n * Reading a multiplexer's screen forks a CLI, measured at roughly 75ms a call.\n * Several readers on one run (a step tab, a second browser, the session's own\n * panel) would otherwise each fork on their own timer, and a read that runs\n * long would have the next one start on top of it, unbounded. One read per run\n * at a time, no faster than the interval, however many surfaces are watching.\n *\n * WHY A CONTROL LEASE:\n * A run's terminal takes keystrokes from anyone who can reach it, so two\n * readers typing into one nested agent is a real state, not a hypothetical.\n * The lease does not make it impossible, since the session's own panel can\n * still type; it makes it visible, and keeps two cockpit tabs from fighting.\n */\n\n/** The engine's terminal facade, as this service needs it. */\nexport interface TerminalPort<Record> {\n capabilities(record: Record): WorkflowTerminalCapabilitiesView;\n screen(record: Record, options: { lines?: number }): Promise<string[]>;\n write(record: Record, data: string): Promise<void>;\n resize(record: Record, columns: number, rows: number): Promise<boolean>;\n}\n\nexport interface WorkflowTerminalDeps<Record> {\n terminal: TerminalPort<Record>;\n /** Injected so the cadence and the lease clock are assertable. */\n now: () => number;\n /** Shortest gap between two reads of one run. */\n refreshMs?: number;\n /** How long a keyboard lease survives without being renewed. */\n leaseMs?: number;\n}\n\n/** A read in flight, and what the last one returned. */\ninterface ScreenEntry {\n /**\n * When the last read finished, absent until one has.\n *\n * Absent rather than zero: zero is a real instant, and a service whose clock\n * starts near it would answer the very first read from an empty cache and\n * paint nothing until the interval had passed.\n */\n at?: number;\n lines: string[];\n inFlight?: Promise<string[]>;\n}\n\ninterface Lease {\n token: string;\n expiresAt: number;\n}\n\nconst DEFAULT_REFRESH_MS = 750;\nconst DEFAULT_LEASE_MS = 60_000;\nconst NOT_HELD = 'Another reader holds the keyboard for this run.';\n\nexport interface WorkflowTerminalService<Record> {\n /** The run's recent screen, reusing a recent read rather than forking again. */\n screen(identity: string, record: Record, lines: number): Promise<string[]>;\n capabilities(record: Record): WorkflowTerminalCapabilitiesView;\n /** Takes the keyboard, or reports who has it. Renews when the holder asks again. */\n takeControl(identity: string, token: string): boolean;\n releaseControl(identity: string, token: string): void;\n /** Writes on behalf of a lease holder; throws when the lease is not theirs. */\n write(identity: string, record: Record, token: string, data: string): Promise<void>;\n /**\n * Matches the run's terminal to the holder's viewport.\n *\n * Behind the same lease as writing: geometry is shared by everyone watching,\n * so a passive reader must not reflow the screen under the person typing.\n */\n resize(identity: string, record: Record, token: string, columns: number, rows: number): Promise<boolean>;\n /** Drops what is remembered for runs that can never change again. */\n forget(live: ReadonlySet<string>): void;\n}\n\nexport function createWorkflowTerminalService<Record>(\n deps: WorkflowTerminalDeps<Record>,\n): WorkflowTerminalService<Record> {\n const refreshMs = deps.refreshMs ?? DEFAULT_REFRESH_MS;\n const leaseMs = deps.leaseMs ?? DEFAULT_LEASE_MS;\n const screens = new Map<string, ScreenEntry>();\n const leases = new Map<string, Lease>();\n\n const heldBy = (identity: string): Lease | undefined => {\n const lease = leases.get(identity);\n if (lease === undefined) return undefined;\n if (lease.expiresAt > deps.now()) return lease;\n leases.delete(identity);\n return undefined;\n };\n\n return {\n async screen(identity, record, lines) {\n const entry = screens.get(identity) ?? { lines: [] };\n screens.set(identity, entry);\n if (entry.inFlight) return entry.lines;\n if (entry.at !== undefined && deps.now() - entry.at < refreshMs) return entry.lines;\n entry.inFlight = deps.terminal.screen(record, { lines });\n try {\n entry.lines = await entry.inFlight;\n } finally {\n // Stamped after the read, not before: the interval is a gap between\n // reads, so a slow scrape must not immediately earn another one.\n entry.at = deps.now();\n entry.inFlight = undefined;\n }\n return entry.lines;\n },\n capabilities(record) {\n return deps.terminal.capabilities(record);\n },\n takeControl(identity, token) {\n const holder = heldBy(identity);\n if (holder !== undefined && holder.token !== token) return false;\n leases.set(identity, { token, expiresAt: deps.now() + leaseMs });\n return true;\n },\n releaseControl(identity, token) {\n if (heldBy(identity)?.token === token) leases.delete(identity);\n },\n async write(identity, record, token, data) {\n const holder = heldBy(identity);\n if (holder === undefined || holder.token !== token) throw new Error(NOT_HELD);\n leases.set(identity, { token, expiresAt: deps.now() + leaseMs });\n await deps.terminal.write(record, data);\n },\n async resize(identity, record, token, columns, rows) {\n const holder = heldBy(identity);\n if (holder === undefined || holder.token !== token) throw new Error(NOT_HELD);\n return deps.terminal.resize(record, columns, rows);\n },\n forget(live) {\n for (const identity of screens.keys()) {\n if (!live.has(identity)) screens.delete(identity);\n }\n for (const identity of leases.keys()) {\n if (!live.has(identity)) leases.delete(identity);\n }\n },\n };\n}\n"],"mappings":"AAwDA,MAEM,EAAW,kDAsBjB,SAAgB,EACd,EACiC,CACjC,IAAM,EAAY,EAAK,WAAa,IAC9B,EAAU,EAAK,SAAW,IAC1B,EAAU,IAAI,IACd,EAAS,IAAI,IAEb,EAAU,GAAwC,CACtD,IAAM,EAAQ,EAAO,IAAI,CAAQ,EAC7B,OAAU,IAAA,GACd,IAAI,EAAM,UAAY,EAAK,IAAI,EAAG,OAAO,EACzC,EAAO,OAAO,CAAQ,CADmB,CAG3C,EAEA,MAAO,CACL,MAAM,OAAO,EAAU,EAAQ,EAAO,CACpC,IAAM,EAAQ,EAAQ,IAAI,CAAQ,GAAK,CAAE,MAAO,CAAC,CAAE,EAGnD,GAFA,EAAQ,IAAI,EAAU,CAAK,EACvB,EAAM,UACN,EAAM,KAAO,IAAA,IAAa,EAAK,IAAI,EAAI,EAAM,GAAK,EAAW,OAAO,EAAM,MAC9E,EAAM,SAAW,EAAK,SAAS,OAAO,EAAQ,CAAE,OAAM,CAAC,EACvD,GAAI,CACF,EAAM,MAAQ,MAAM,EAAM,QAC5B,QAAU,CAGR,EAAM,GAAK,EAAK,IAAI,EACpB,EAAM,SAAW,IAAA,EACnB,CACA,OAAO,EAAM,KACf,EACA,aAAa,EAAQ,CACnB,OAAO,EAAK,SAAS,aAAa,CAAM,CAC1C,EACA,YAAY,EAAU,EAAO,CAC3B,IAAM,EAAS,EAAO,CAAQ,EAG9B,OAFI,IAAW,IAAA,IAAa,EAAO,QAAU,EAAc,IAC3D,EAAO,IAAI,EAAU,CAAE,QAAO,UAAW,EAAK,IAAI,EAAI,CAAQ,CAAC,EACxD,GACT,EACA,eAAe,EAAU,EAAO,CAC1B,EAAO,CAAQ,CAAC,EAAE,QAAU,GAAO,EAAO,OAAO,CAAQ,CAC/D,EACA,MAAM,MAAM,EAAU,EAAQ,EAAO,EAAM,CACzC,IAAM,EAAS,EAAO,CAAQ,EAC9B,GAAI,IAAW,IAAA,IAAa,EAAO,QAAU,EAAO,MAAU,MAAM,CAAQ,EAC5E,EAAO,IAAI,EAAU,CAAE,QAAO,UAAW,EAAK,IAAI,EAAI,CAAQ,CAAC,EAC/D,MAAM,EAAK,SAAS,MAAM,EAAQ,CAAI,CACxC,EACA,MAAM,OAAO,EAAU,EAAQ,EAAO,EAAS,EAAM,CACnD,IAAM,EAAS,EAAO,CAAQ,EAC9B,GAAI,IAAW,IAAA,IAAa,EAAO,QAAU,EAAO,MAAU,MAAM,CAAQ,EAC5E,OAAO,EAAK,SAAS,OAAO,EAAQ,EAAS,CAAI,CACnD,EACA,OAAO,EAAM,CACX,IAAK,IAAM,KAAY,EAAQ,KAAK,EAC7B,EAAK,IAAI,CAAQ,GAAG,EAAQ,OAAO,CAAQ,EAElD,IAAK,IAAM,KAAY,EAAO,KAAK,EAC5B,EAAK,IAAI,CAAQ,GAAG,EAAO,OAAO,CAAQ,CAEnD,CACF,CACF"}
@@ -1,2 +0,0 @@
1
- const e=`Another reader holds the keyboard for this run.`;function t(t){let n=t.refreshMs??750,r=t.leaseMs??6e4,i=new Map,a=new Map,o=e=>{let n=a.get(e);if(n!==void 0){if(n.expiresAt>t.now())return n;a.delete(e)}};return{async screen(e,r,a){let o=i.get(e)??{lines:[]};if(i.set(e,o),o.inFlight||o.at!==void 0&&t.now()-o.at<n)return o.lines;o.inFlight=t.terminal.screen(r,{lines:a});try{o.lines=await o.inFlight}finally{o.at=t.now(),o.inFlight=void 0}return o.lines},capabilities(e){return t.terminal.capabilities(e)},takeControl(e,n){let i=o(e);return i!==void 0&&i.token!==n?!1:(a.set(e,{token:n,expiresAt:t.now()+r}),!0)},releaseControl(e,t){o(e)?.token===t&&a.delete(e)},async write(n,i,s,c){let l=o(n);if(l===void 0||l.token!==s)throw Error(e);a.set(n,{token:s,expiresAt:t.now()+r}),await t.terminal.write(i,c)},async resize(n,r,i,a,s){let c=o(n);if(c===void 0||c.token!==i)throw Error(e);return t.terminal.resize(r,a,s)},forget(e){for(let t of i.keys())e.has(t)||i.delete(t);for(let t of a.keys())e.has(t)||a.delete(t)}}}export{t as createWorkflowTerminalService};
2
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/workflowTerminal/index.ts"],"sourcesContent":["import type { WorkflowTerminalCapabilitiesView } from '../../types/webWorkflowTerminal';\n\n/**\n * One run's terminal, shared by every surface watching it.\n *\n * WHY COALESCING LIVES HERE:\n * Reading a multiplexer's screen forks a CLI, measured at roughly 75ms a call.\n * Several readers on one run (a step tab, a second browser, the session's own\n * panel) would otherwise each fork on their own timer, and a read that runs\n * long would have the next one start on top of it, unbounded. One read per run\n * at a time, no faster than the interval, however many surfaces are watching.\n *\n * WHY A CONTROL LEASE:\n * A run's terminal takes keystrokes from anyone who can reach it, so two\n * readers typing into one nested agent is a real state, not a hypothetical.\n * The lease does not make it impossible, since the session's own panel can\n * still type; it makes it visible, and keeps two cockpit tabs from fighting.\n */\n\n/** The engine's terminal facade, as this service needs it. */\nexport interface TerminalPort<Record> {\n capabilities(record: Record): WorkflowTerminalCapabilitiesView;\n screen(record: Record, options: { lines?: number }): Promise<string[]>;\n write(record: Record, data: string): Promise<void>;\n resize(record: Record, columns: number, rows: number): Promise<boolean>;\n}\n\nexport interface WorkflowTerminalDeps<Record> {\n terminal: TerminalPort<Record>;\n /** Injected so the cadence and the lease clock are assertable. */\n now: () => number;\n /** Shortest gap between two reads of one run. */\n refreshMs?: number;\n /** How long a keyboard lease survives without being renewed. */\n leaseMs?: number;\n}\n\n/** A read in flight, and what the last one returned. */\ninterface ScreenEntry {\n /**\n * When the last read finished, absent until one has.\n *\n * Absent rather than zero: zero is a real instant, and a service whose clock\n * starts near it would answer the very first read from an empty cache and\n * paint nothing until the interval had passed.\n */\n at?: number;\n lines: string[];\n inFlight?: Promise<string[]>;\n}\n\ninterface Lease {\n token: string;\n expiresAt: number;\n}\n\nconst DEFAULT_REFRESH_MS = 750;\nconst DEFAULT_LEASE_MS = 60_000;\nconst NOT_HELD = 'Another reader holds the keyboard for this run.';\n\nexport interface WorkflowTerminalService<Record> {\n /** The run's recent screen, reusing a recent read rather than forking again. */\n screen(identity: string, record: Record, lines: number): Promise<string[]>;\n capabilities(record: Record): WorkflowTerminalCapabilitiesView;\n /** Takes the keyboard, or reports who has it. Renews when the holder asks again. */\n takeControl(identity: string, token: string): boolean;\n releaseControl(identity: string, token: string): void;\n /** Writes on behalf of a lease holder; throws when the lease is not theirs. */\n write(identity: string, record: Record, token: string, data: string): Promise<void>;\n /**\n * Matches the run's terminal to the holder's viewport.\n *\n * Behind the same lease as writing: geometry is shared by everyone watching,\n * so a passive reader must not reflow the screen under the person typing.\n */\n resize(identity: string, record: Record, token: string, columns: number, rows: number): Promise<boolean>;\n /** Drops what is remembered for runs that can never change again. */\n forget(live: ReadonlySet<string>): void;\n}\n\nexport function createWorkflowTerminalService<Record>(\n deps: WorkflowTerminalDeps<Record>,\n): WorkflowTerminalService<Record> {\n const refreshMs = deps.refreshMs ?? DEFAULT_REFRESH_MS;\n const leaseMs = deps.leaseMs ?? DEFAULT_LEASE_MS;\n const screens = new Map<string, ScreenEntry>();\n const leases = new Map<string, Lease>();\n\n const heldBy = (identity: string): Lease | undefined => {\n const lease = leases.get(identity);\n if (lease === undefined) return undefined;\n if (lease.expiresAt > deps.now()) return lease;\n leases.delete(identity);\n return undefined;\n };\n\n return {\n async screen(identity, record, lines) {\n const entry = screens.get(identity) ?? { lines: [] };\n screens.set(identity, entry);\n if (entry.inFlight) return entry.lines;\n if (entry.at !== undefined && deps.now() - entry.at < refreshMs) return entry.lines;\n entry.inFlight = deps.terminal.screen(record, { lines });\n try {\n entry.lines = await entry.inFlight;\n } finally {\n // Stamped after the read, not before: the interval is a gap between\n // reads, so a slow scrape must not immediately earn another one.\n entry.at = deps.now();\n entry.inFlight = undefined;\n }\n return entry.lines;\n },\n capabilities(record) {\n return deps.terminal.capabilities(record);\n },\n takeControl(identity, token) {\n const holder = heldBy(identity);\n if (holder !== undefined && holder.token !== token) return false;\n leases.set(identity, { token, expiresAt: deps.now() + leaseMs });\n return true;\n },\n releaseControl(identity, token) {\n if (heldBy(identity)?.token === token) leases.delete(identity);\n },\n async write(identity, record, token, data) {\n const holder = heldBy(identity);\n if (holder === undefined || holder.token !== token) throw new Error(NOT_HELD);\n leases.set(identity, { token, expiresAt: deps.now() + leaseMs });\n await deps.terminal.write(record, data);\n },\n async resize(identity, record, token, columns, rows) {\n const holder = heldBy(identity);\n if (holder === undefined || holder.token !== token) throw new Error(NOT_HELD);\n return deps.terminal.resize(record, columns, rows);\n },\n forget(live) {\n for (const identity of screens.keys()) {\n if (!live.has(identity)) screens.delete(identity);\n }\n for (const identity of leases.keys()) {\n if (!live.has(identity)) leases.delete(identity);\n }\n },\n };\n}\n"],"mappings":"AAwDA,MAEM,EAAW,kDAsBjB,SAAgB,EACd,EACiC,CACjC,IAAM,EAAY,EAAK,WAAa,IAC9B,EAAU,EAAK,SAAW,IAC1B,EAAU,IAAI,IACd,EAAS,IAAI,IAEb,EAAU,GAAwC,CACtD,IAAM,EAAQ,EAAO,IAAI,CAAQ,EAC7B,OAAU,IAAA,GACd,IAAI,EAAM,UAAY,EAAK,IAAI,EAAG,OAAO,EACzC,EAAO,OAAO,CAAQ,CADmB,CAG3C,EAEA,MAAO,CACL,MAAM,OAAO,EAAU,EAAQ,EAAO,CACpC,IAAM,EAAQ,EAAQ,IAAI,CAAQ,GAAK,CAAE,MAAO,CAAC,CAAE,EAGnD,GAFA,EAAQ,IAAI,EAAU,CAAK,EACvB,EAAM,UACN,EAAM,KAAO,IAAA,IAAa,EAAK,IAAI,EAAI,EAAM,GAAK,EAAW,OAAO,EAAM,MAC9E,EAAM,SAAW,EAAK,SAAS,OAAO,EAAQ,CAAE,OAAM,CAAC,EACvD,GAAI,CACF,EAAM,MAAQ,MAAM,EAAM,QAC5B,QAAU,CAGR,EAAM,GAAK,EAAK,IAAI,EACpB,EAAM,SAAW,IAAA,EACnB,CACA,OAAO,EAAM,KACf,EACA,aAAa,EAAQ,CACnB,OAAO,EAAK,SAAS,aAAa,CAAM,CAC1C,EACA,YAAY,EAAU,EAAO,CAC3B,IAAM,EAAS,EAAO,CAAQ,EAG9B,OAFI,IAAW,IAAA,IAAa,EAAO,QAAU,EAAc,IAC3D,EAAO,IAAI,EAAU,CAAE,QAAO,UAAW,EAAK,IAAI,EAAI,CAAQ,CAAC,EACxD,GACT,EACA,eAAe,EAAU,EAAO,CAC1B,EAAO,CAAQ,CAAC,EAAE,QAAU,GAAO,EAAO,OAAO,CAAQ,CAC/D,EACA,MAAM,MAAM,EAAU,EAAQ,EAAO,EAAM,CACzC,IAAM,EAAS,EAAO,CAAQ,EAC9B,GAAI,IAAW,IAAA,IAAa,EAAO,QAAU,EAAO,MAAU,MAAM,CAAQ,EAC5E,EAAO,IAAI,EAAU,CAAE,QAAO,UAAW,EAAK,IAAI,EAAI,CAAQ,CAAC,EAC/D,MAAM,EAAK,SAAS,MAAM,EAAQ,CAAI,CACxC,EACA,MAAM,OAAO,EAAU,EAAQ,EAAO,EAAS,EAAM,CACnD,IAAM,EAAS,EAAO,CAAQ,EAC9B,GAAI,IAAW,IAAA,IAAa,EAAO,QAAU,EAAO,MAAU,MAAM,CAAQ,EAC5E,OAAO,EAAK,SAAS,OAAO,EAAQ,EAAS,CAAI,CACnD,EACA,OAAO,EAAM,CACX,IAAK,IAAM,KAAY,EAAQ,KAAK,EAC7B,EAAK,IAAI,CAAQ,GAAG,EAAQ,OAAO,CAAQ,EAElD,IAAK,IAAM,KAAY,EAAO,KAAK,EAC5B,EAAK,IAAI,CAAQ,GAAG,EAAO,OAAO,CAAQ,CAEnD,CACF,CACF"}
@@ -1,2 +0,0 @@
1
- const e=require("../../../_virtual/_rolldown/runtime.cjs"),t=require("../workflowRuns/index.cjs");let n=require("node:fs");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);let i=require("node:os");i=e.__toESM(i,1);function a(e={}){let a=e.environment,o=e.homeDir??t.resolveWorkflowHome({envValue:a?.WORKFLOW_MCP_HOME,homeDir:i.default.homedir()}),s=r.default.join(o,t.WORKSPACES_DIR_NAME),c=new Map,l=new Map,u=(e,t,r,i)=>{let a;try{a=n.default.statSync(t)}catch{return e.delete(t),i}let o=e.get(t);if(o&&o.size===a.size&&o.mtimeMs===a.mtimeMs)return o.value;let s;try{s=n.default.readFileSync(t,`utf8`)}catch{return e.delete(t),i}let c=r(s);return e.set(t,{size:a.size,mtimeMs:a.mtimeMs,value:c}),c},d=e=>{try{return n.default.readdirSync(e).sort()}catch{return[]}},f=[];for(let e of d(s))for(let n of t.WORKFLOW_STAGES){let i=r.default.join(s,e,n);for(let e of d(i)){let n=r.default.join(i,e),a=r.default.join(n,t.RUN_RECORD_FILE_NAME),o=r.default.join(n,t.PROGRESS_FILE_NAME),s=u(c,a,t.parseWorkflowRunRecord,void 0);if(s===void 0)continue;let d=u(l,o,e=>t.foldWorkflowProgress(t.parseWorkflowProgress(e)),[]);f.push({...s,view:t.completeWorkflowRunView(s.view,d)})}}return f}exports.readWorkflowRuns=a;
2
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","names":["resolveWorkflowHome","os","path","WORKSPACES_DIR_NAME","fs","WORKFLOW_STAGES","RUN_RECORD_FILE_NAME","PROGRESS_FILE_NAME","parseWorkflowRunRecord","foldWorkflowProgress","parseWorkflowProgress","completeWorkflowRunView"],"sources":["../../../../src/services/workflowWatcher/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport {\n completeWorkflowRunView,\n foldWorkflowProgress,\n type ParsedWorkflowRun,\n parseWorkflowProgress,\n parseWorkflowRunRecord,\n PROGRESS_FILE_NAME,\n resolveWorkflowHome,\n RUN_RECORD_FILE_NAME,\n WORKFLOW_HOME_ENV,\n WORKFLOW_STAGES,\n WORKSPACES_DIR_NAME,\n} from '../workflowRuns';\n\nexport interface ReadWorkflowRunsOptions {\n /** Registry home override, primarily for tests. */\n homeDir?: string;\n /** Admitted session environment used to resolve the registry home. */\n environment?: Readonly<Record<string, string | undefined>>;\n}\n\n/**\n * Read the durable workflow registry once.\n *\n * The registry is an initial snapshot only. Live delivery belongs to the\n * session lifecycle and is routed through the host-owned direct event bus.\n */\nexport function readWorkflowRuns(options: ReadWorkflowRunsOptions = {}): ParsedWorkflowRun[] {\n const environment = options.environment;\n const homeDir =\n options.homeDir ??\n resolveWorkflowHome({\n envValue: environment?.[WORKFLOW_HOME_ENV],\n homeDir: os.homedir(),\n });\n const workspacesDir = path.join(homeDir, WORKSPACES_DIR_NAME);\n const recordCache = new Map<string, { size: number; mtimeMs: number; value: ParsedWorkflowRun | undefined }>();\n const progressCache = new Map<\n string,\n { size: number; mtimeMs: number; value: ReturnType<typeof foldWorkflowProgress> }\n >();\n\n const cachedParse = <T>(\n cache: Map<string, { size: number; mtimeMs: number; value: T }>,\n filePath: string,\n parse: (raw: string) => T,\n empty: T,\n ): T => {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(filePath);\n } catch {\n cache.delete(filePath);\n return empty;\n }\n const cached = cache.get(filePath);\n if (cached && cached.size === stat.size && cached.mtimeMs === stat.mtimeMs) return cached.value;\n let raw: string;\n try {\n raw = fs.readFileSync(filePath, 'utf8');\n } catch {\n cache.delete(filePath);\n return empty;\n }\n const value = parse(raw);\n cache.set(filePath, { size: stat.size, mtimeMs: stat.mtimeMs, value });\n return value;\n };\n\n const listDir = (directory: string): string[] => {\n try {\n return fs.readdirSync(directory).sort();\n } catch {\n return [];\n }\n };\n\n const runs: ParsedWorkflowRun[] = [];\n for (const workspace of listDir(workspacesDir)) {\n for (const stage of WORKFLOW_STAGES) {\n const stageDir = path.join(workspacesDir, workspace, stage);\n for (const runKey of listDir(stageDir)) {\n const runDir = path.join(stageDir, runKey);\n const recordPath = path.join(runDir, RUN_RECORD_FILE_NAME);\n const progressPath = path.join(runDir, PROGRESS_FILE_NAME);\n const parsed = cachedParse(recordCache, recordPath, parseWorkflowRunRecord, undefined);\n if (parsed === undefined) continue;\n const jobs = cachedParse(\n progressCache,\n progressPath,\n (raw) => foldWorkflowProgress(parseWorkflowProgress(raw)),\n [],\n );\n runs.push({ ...parsed, view: completeWorkflowRunView(parsed.view, jobs) });\n }\n }\n }\n return runs;\n}\n"],"mappings":"kOA+BA,SAAgB,EAAiB,EAAmC,CAAC,EAAwB,CAC3F,IAAM,EAAc,EAAQ,YACtB,EACJ,EAAQ,SACRA,EAAAA,oBAAoB,CAClB,SAAU,GAAA,kBACV,QAASC,EAAAA,QAAG,QAAQ,CACtB,CAAC,EACG,EAAgBC,EAAAA,QAAK,KAAK,EAASC,EAAAA,mBAAmB,EACtD,EAAc,IAAI,IAClB,EAAgB,IAAI,IAKpB,GACJ,EACA,EACA,EACA,IACM,CACN,IAAI,EACJ,GAAI,CACF,EAAOC,EAAAA,QAAG,SAAS,CAAQ,CAC7B,MAAQ,CAEN,OADA,EAAM,OAAO,CAAQ,EACd,CACT,CACA,IAAM,EAAS,EAAM,IAAI,CAAQ,EACjC,GAAI,GAAU,EAAO,OAAS,EAAK,MAAQ,EAAO,UAAY,EAAK,QAAS,OAAO,EAAO,MAC1F,IAAI,EACJ,GAAI,CACF,EAAMA,EAAAA,QAAG,aAAa,EAAU,MAAM,CACxC,MAAQ,CAEN,OADA,EAAM,OAAO,CAAQ,EACd,CACT,CACA,IAAM,EAAQ,EAAM,CAAG,EAEvB,OADA,EAAM,IAAI,EAAU,CAAE,KAAM,EAAK,KAAM,QAAS,EAAK,QAAS,OAAM,CAAC,EAC9D,CACT,EAEM,EAAW,GAAgC,CAC/C,GAAI,CACF,OAAOA,EAAAA,QAAG,YAAY,CAAS,CAAC,CAAC,KAAK,CACxC,MAAQ,CACN,MAAO,CAAC,CACV,CACF,EAEM,EAA4B,CAAC,EACnC,IAAK,IAAM,KAAa,EAAQ,CAAa,EAC3C,IAAK,IAAM,KAASC,EAAAA,gBAAiB,CACnC,IAAM,EAAWH,EAAAA,QAAK,KAAK,EAAe,EAAW,CAAK,EAC1D,IAAK,IAAM,KAAU,EAAQ,CAAQ,EAAG,CACtC,IAAM,EAASA,EAAAA,QAAK,KAAK,EAAU,CAAM,EACnC,EAAaA,EAAAA,QAAK,KAAK,EAAQI,EAAAA,oBAAoB,EACnD,EAAeJ,EAAAA,QAAK,KAAK,EAAQK,EAAAA,kBAAkB,EACnD,EAAS,EAAY,EAAa,EAAYC,EAAAA,uBAAwB,IAAA,EAAS,EACrF,GAAI,IAAW,IAAA,GAAW,SAC1B,IAAM,EAAO,EACX,EACA,EACC,GAAQC,EAAAA,qBAAqBC,EAAAA,sBAAsB,CAAG,CAAC,EACxD,CAAC,CACH,EACA,EAAK,KAAK,CAAE,GAAG,EAAQ,KAAMC,EAAAA,wBAAwB,EAAO,KAAM,CAAI,CAAE,CAAC,CAC3E,CACF,CAEF,OAAO,CACT"}
@@ -1,2 +0,0 @@
1
- import{PROGRESS_FILE_NAME as e,RUN_RECORD_FILE_NAME as t,WORKFLOW_STAGES as n,WORKSPACES_DIR_NAME as r,completeWorkflowRunView as i,foldWorkflowProgress as a,parseWorkflowProgress as o,parseWorkflowRunRecord as s,resolveWorkflowHome as c}from"../workflowRuns/index.mjs";import l from"node:fs";import u from"node:path";import d from"node:os";function f(f={}){let p=f.environment,m=f.homeDir??c({envValue:p?.WORKFLOW_MCP_HOME,homeDir:d.homedir()}),h=u.join(m,r),g=new Map,_=new Map,v=(e,t,n,r)=>{let i;try{i=l.statSync(t)}catch{return e.delete(t),r}let a=e.get(t);if(a&&a.size===i.size&&a.mtimeMs===i.mtimeMs)return a.value;let o;try{o=l.readFileSync(t,`utf8`)}catch{return e.delete(t),r}let s=n(o);return e.set(t,{size:i.size,mtimeMs:i.mtimeMs,value:s}),s},y=e=>{try{return l.readdirSync(e).sort()}catch{return[]}},b=[];for(let r of y(h))for(let c of n){let n=u.join(h,r,c);for(let r of y(n)){let c=u.join(n,r),l=u.join(c,t),d=u.join(c,e),f=v(g,l,s,void 0);if(f===void 0)continue;let p=v(_,d,e=>a(o(e)),[]);b.push({...f,view:i(f.view,p)})}}return b}export{f as readWorkflowRuns};
2
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/workflowWatcher/index.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nimport {\n completeWorkflowRunView,\n foldWorkflowProgress,\n type ParsedWorkflowRun,\n parseWorkflowProgress,\n parseWorkflowRunRecord,\n PROGRESS_FILE_NAME,\n resolveWorkflowHome,\n RUN_RECORD_FILE_NAME,\n WORKFLOW_HOME_ENV,\n WORKFLOW_STAGES,\n WORKSPACES_DIR_NAME,\n} from '../workflowRuns';\n\nexport interface ReadWorkflowRunsOptions {\n /** Registry home override, primarily for tests. */\n homeDir?: string;\n /** Admitted session environment used to resolve the registry home. */\n environment?: Readonly<Record<string, string | undefined>>;\n}\n\n/**\n * Read the durable workflow registry once.\n *\n * The registry is an initial snapshot only. Live delivery belongs to the\n * session lifecycle and is routed through the host-owned direct event bus.\n */\nexport function readWorkflowRuns(options: ReadWorkflowRunsOptions = {}): ParsedWorkflowRun[] {\n const environment = options.environment;\n const homeDir =\n options.homeDir ??\n resolveWorkflowHome({\n envValue: environment?.[WORKFLOW_HOME_ENV],\n homeDir: os.homedir(),\n });\n const workspacesDir = path.join(homeDir, WORKSPACES_DIR_NAME);\n const recordCache = new Map<string, { size: number; mtimeMs: number; value: ParsedWorkflowRun | undefined }>();\n const progressCache = new Map<\n string,\n { size: number; mtimeMs: number; value: ReturnType<typeof foldWorkflowProgress> }\n >();\n\n const cachedParse = <T>(\n cache: Map<string, { size: number; mtimeMs: number; value: T }>,\n filePath: string,\n parse: (raw: string) => T,\n empty: T,\n ): T => {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(filePath);\n } catch {\n cache.delete(filePath);\n return empty;\n }\n const cached = cache.get(filePath);\n if (cached && cached.size === stat.size && cached.mtimeMs === stat.mtimeMs) return cached.value;\n let raw: string;\n try {\n raw = fs.readFileSync(filePath, 'utf8');\n } catch {\n cache.delete(filePath);\n return empty;\n }\n const value = parse(raw);\n cache.set(filePath, { size: stat.size, mtimeMs: stat.mtimeMs, value });\n return value;\n };\n\n const listDir = (directory: string): string[] => {\n try {\n return fs.readdirSync(directory).sort();\n } catch {\n return [];\n }\n };\n\n const runs: ParsedWorkflowRun[] = [];\n for (const workspace of listDir(workspacesDir)) {\n for (const stage of WORKFLOW_STAGES) {\n const stageDir = path.join(workspacesDir, workspace, stage);\n for (const runKey of listDir(stageDir)) {\n const runDir = path.join(stageDir, runKey);\n const recordPath = path.join(runDir, RUN_RECORD_FILE_NAME);\n const progressPath = path.join(runDir, PROGRESS_FILE_NAME);\n const parsed = cachedParse(recordCache, recordPath, parseWorkflowRunRecord, undefined);\n if (parsed === undefined) continue;\n const jobs = cachedParse(\n progressCache,\n progressPath,\n (raw) => foldWorkflowProgress(parseWorkflowProgress(raw)),\n [],\n );\n runs.push({ ...parsed, view: completeWorkflowRunView(parsed.view, jobs) });\n }\n }\n }\n return runs;\n}\n"],"mappings":"qVA+BA,SAAgB,EAAiB,EAAmC,CAAC,EAAwB,CAC3F,IAAM,EAAc,EAAQ,YACtB,EACJ,EAAQ,SACR,EAAoB,CAClB,SAAU,GAAA,kBACV,QAAS,EAAG,QAAQ,CACtB,CAAC,EACG,EAAgB,EAAK,KAAK,EAAS,CAAmB,EACtD,EAAc,IAAI,IAClB,EAAgB,IAAI,IAKpB,GACJ,EACA,EACA,EACA,IACM,CACN,IAAI,EACJ,GAAI,CACF,EAAO,EAAG,SAAS,CAAQ,CAC7B,MAAQ,CAEN,OADA,EAAM,OAAO,CAAQ,EACd,CACT,CACA,IAAM,EAAS,EAAM,IAAI,CAAQ,EACjC,GAAI,GAAU,EAAO,OAAS,EAAK,MAAQ,EAAO,UAAY,EAAK,QAAS,OAAO,EAAO,MAC1F,IAAI,EACJ,GAAI,CACF,EAAM,EAAG,aAAa,EAAU,MAAM,CACxC,MAAQ,CAEN,OADA,EAAM,OAAO,CAAQ,EACd,CACT,CACA,IAAM,EAAQ,EAAM,CAAG,EAEvB,OADA,EAAM,IAAI,EAAU,CAAE,KAAM,EAAK,KAAM,QAAS,EAAK,QAAS,OAAM,CAAC,EAC9D,CACT,EAEM,EAAW,GAAgC,CAC/C,GAAI,CACF,OAAO,EAAG,YAAY,CAAS,CAAC,CAAC,KAAK,CACxC,MAAQ,CACN,MAAO,CAAC,CACV,CACF,EAEM,EAA4B,CAAC,EACnC,IAAK,IAAM,KAAa,EAAQ,CAAa,EAC3C,IAAK,IAAM,KAAS,EAAiB,CACnC,IAAM,EAAW,EAAK,KAAK,EAAe,EAAW,CAAK,EAC1D,IAAK,IAAM,KAAU,EAAQ,CAAQ,EAAG,CACtC,IAAM,EAAS,EAAK,KAAK,EAAU,CAAM,EACnC,EAAa,EAAK,KAAK,EAAQ,CAAoB,EACnD,EAAe,EAAK,KAAK,EAAQ,CAAkB,EACnD,EAAS,EAAY,EAAa,EAAY,EAAwB,IAAA,EAAS,EACrF,GAAI,IAAW,IAAA,GAAW,SAC1B,IAAM,EAAO,EACX,EACA,EACC,GAAQ,EAAqB,EAAsB,CAAG,CAAC,EACxD,CAAC,CACH,EACA,EAAK,KAAK,CAAE,GAAG,EAAQ,KAAM,EAAwB,EAAO,KAAM,CAAI,CAAE,CAAC,CAC3E,CACF,CAEF,OAAO,CACT"}
@@ -1,5 +0,0 @@
1
- const e=require("../constants/workflow.cjs"),t=require("../schemas/workflowPi.cjs"),n=require("../services/piToolBridge/index.cjs"),r=require("../services/workflowExecution/index.cjs");let i=require("@agimon-ai/doompi-core/child-process"),a=require("@agimon-ai/workflow-mcp"),o=require("zod");const s={workflowPath:!0,workspace:!0,runner:!0,name:!0,env:!0,prompt:!0,job:!0,inputs:!0};function c(c,l=()=>({})){let u=c.feature??(0,a.createEmbeddedWorkflowFeature)(),d=c.runTool??u.runTool,f=c.listWorkflowsTool??u.listWorkflowsTool,p=c.recoverTool??u.createRecoverTool(),m=c.controlTool??u.createControlTool(),h=c.trackPendingRun??(e=>e),g=c.launchExecutor??r.createWorkflowLaunchExecutor({environment:c.environment,activeRunCount:c.activeRunCount,onLaunch:c.onLaunch,observeSession:c.observeSession,rejectRunner:c.rejectRunner,runTool:d,trackPendingRun:h});return[{name:e.LIST_WORKFLOWS_TOOL_NAME,label:`List Workflows`,description:`List the workflow definitions available in this repository.`,promptSnippet:`List available workflow definitions`,parameters:o.z.toJSONSchema(f.getInputSchema()),renderShell:`self`,...l(e.LIST_WORKFLOWS_TOOL_NAME),async execute(t,r){return n.toAgentToolResult(e.LIST_WORKFLOWS_TOOL_NAME,await f.execute(r))}},{name:e.LAUNCH_WORKFLOW_TOOL_NAME,label:`Launch Workflow`,description:`Start a workflow run. Returns once the run has started, not once it finishes; use workflow_run with action status or wait for the completion notice.`,promptSnippet:`Start a workflow run and return its identifier`,promptGuidelines:[`Before launching, call list_workflows and pick the workflow whose own description matches the work. A workflow description is the source of truth; never infer one from its filename. If nothing matches, say so and stop rather than launching an approximate fit.`,`launch_workflow returns when the run has STARTED, not when it has finished. Never report success from its result. Poll workflow_run with action status, or wait for the completion notice the extension delivers on its own.`,`When dispatching Agiflow work, pass AGIFLOW_PROJECT_ID, AGIFLOW_JOB_KIND, and AGIFLOW_JOB_ID through env plus a non-empty prompt. Kind must be task or work-unit. The id alone is not a key, and omitting prompt leaves user_prompt workflows waiting for terminal input.`,`A workflow claims its own job in its pre step. Never claim a job yourself. JOB_ALREADY_CLAIMED or HTTP 409 means another worker won the race, so report contention and do not unlock, release, or retry automatically.`,`If a result says WORKFLOW_NOT_OWNED or a release failed, inspect current ownership and ask the user before any release or unlock. Never force another worker’s lock.`,`If a running workflow is not found, call workflow_run with action status to re-check current state before retrying.`,`The per-session concurrency ceiling is a real limit. If launch_workflow refuses, wait for a run to finish; do not launch through the shell to get around it.`],parameters:o.z.toJSONSchema(d.getInputSchema().pick(s)),renderShell:`self`,...l(e.LAUNCH_WORKFLOW_TOOL_NAME),async execute(t,i,a,o,s){let c=i,l=await g.execute(c,s,o),u=n.toAgentToolResult(e.LAUNCH_WORKFLOW_TOOL_NAME,l,r.launchFailureOptions);return r.appendGuidance(u,`Next: call workflow_run with action status and verify the recorded stage before reporting the workflow outcome.`)}},{name:e.WORKFLOW_RUN_TOOL_NAME,label:`Workflow Run`,description:`Inspect or control one workflow run. Use action status, tail, recovery-evidence, follow, open, pause, resume, stop, or recover. Every action requires an exact run key.`,promptSnippet:`Inspect or control a workflow run`,promptGuidelines:[`Use action status as the authority for stage, outcome, execution state, and pause cursor. A run that started is not a run that succeeded.`,`Use action pause or resume for cooperative lifecycle control. Pause is not stop: the run keeps its stage running, capacity, services, and heartbeat while paused.`,`Use action stop only when the user requests stopping a run. A user-requested stop is not a failure and needs no failure diagnosis.`,`Do not use follow or tail merely to wait for a healthy run; use status or the completion notice. Tail returns chat-safe status without raw PTY frames. Use follow only when the user asks to pin live progress, and open only when a human wants the recorded launcher foregrounded.`,`Use action recovery-evidence for terminal-only, durable status and artifacts before recovery. It is the only inspection path that may read a failed run from an earlier session and it never reads a live launcher.`,"Use action recover only after loading the `workflow-recovery` skill and gathering its required evidence. Recovery may adopt a terminal failed run from an earlier Pi session; it never grants cross-session control of running work. Never edit issue.md or repair.json, and verify real process and registry progress before recovery.",`Never unlock or force-release an Agiflow claim from this tool.`],parameters:o.z.toJSONSchema(t.workflowRunInputSchema),renderShell:`self`,...l(e.WORKFLOW_RUN_TOOL_NAME),async execute(a,o,s,l,u){let d=t.workflowRunInputSchema.parse(o),f=(0,i.resolveRootSessionId)(u.sessionManager.getSessionId(),c.environment);c.observeSession?.(f);let g={runKey:d.runKey,workspace:d.workspace},_=async()=>{if(!c.requireSessionRun)throw Error(`Workflow run ownership is not available in this session.`);return c.requireSessionRun(g,f)};if(d.action===`status`){let t=await _();return{content:[{type:`text`,text:JSON.stringify(t,null,2)}],details:{tool:e.WORKFLOW_RUN_TOOL_NAME}}}if(d.action===`tail`){if(!c.tailRun)throw Error(`Reading workflow output is not available in this session.`);let t=await _();return r.reportProgress(l,e.WORKFLOW_RUN_TOOL_NAME,`Reading output for workflow ${d.runKey}...`),await c.tailRun(g,u),{content:[{type:`text`,text:[`Workflow: ${t.displayName} (${t.runKey})`,`Stage: ${t.effectiveStage??t.stage}`,`Raw launcher output is hidden from chat. Use action follow or open to view it.`].join(`
2
- `)}],details:{tool:e.WORKFLOW_RUN_TOOL_NAME}}}if(d.action===`recovery-evidence`){if(!c.readRecoveryEvidence)throw Error(`Reading workflow recovery evidence is not available in this session.`);return r.reportProgress(l,e.WORKFLOW_RUN_TOOL_NAME,`Reading recovery evidence for workflow ${d.runKey}...`),{content:[{type:`text`,text:await c.readRecoveryEvidence(g,u)}],details:{tool:e.WORKFLOW_RUN_TOOL_NAME}}}if(d.action===`follow`){if(!c.followRun)throw Error(`Following workflow output is not available in this session.`);return await _(),r.reportProgress(l,e.WORKFLOW_RUN_TOOL_NAME,`Following workflow ${d.runKey}...`),{content:[{type:`text`,text:await c.followRun(g,u)}],details:{tool:e.WORKFLOW_RUN_TOOL_NAME}}}if(d.action===`open`){if(!c.openRun)throw Error(`Opening a workflow launcher is not available in this session.`);return await _(),r.reportProgress(l,e.WORKFLOW_RUN_TOOL_NAME,`Opening workflow ${d.runKey}...`),{content:[{type:`text`,text:await c.openRun(g,u)}],details:{tool:e.WORKFLOW_RUN_TOOL_NAME}}}if(d.action===`recover`){if(!c.requireRecoverableRun)throw Error(`Workflow recovery ownership transfer is not available in this session.`);let t=await c.requireRecoverableRun(g),i={runKey:t.runKey,workspace:t.workspace};if(r.reportProgress(l,e.WORKFLOW_RUN_TOOL_NAME,`Recovering workflow ${d.runKey}...`),!d.dryRun){let t=await c.delegateRecovery?.({...i,runner:d.runner},u);if(t){let n=r.actionGuidance(d.action,d.runKey);return await c.onLaunch?.(u),{content:[{type:`text`,text:[t,n[0]].join(`
3
-
4
- `)}],details:{tool:e.WORKFLOW_RUN_TOOL_NAME}}}}let a={dryRun:d.dryRun,job:d.job,runKey:i.runKey,runner:d.runner,workspace:i.workspace},o=await h(p.execute(a)),s=r.actionGuidance(d.action,d.runKey),f=n.toAgentToolResult(e.WORKFLOW_RUN_TOOL_NAME,o,s);return await c.onLaunch?.(u),r.appendGuidance(f,s[0])}let v=await _();if(!v.runId)throw Error(`Workflow ${d.runKey} has no controllable run generation.`);r.reportProgress(l,e.WORKFLOW_RUN_TOOL_NAME,`Requesting ${d.action} for workflow ${d.runKey}...`);let y=await m.execute({action:d.action,expectedRunId:v.runId,reason:d.reason,runKey:d.runKey,workspace:d.workspace}),b=r.actionGuidance(d.action,d.runKey),x=n.toAgentToolResult(e.WORKFLOW_RUN_TOOL_NAME,y,b);return r.appendGuidance(x,b[0])}}]}exports.createWorkflowTools=c;
5
- //# sourceMappingURL=workflowTools.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"workflowTools.cjs","names":["createEmbeddedWorkflowFeature","createWorkflowLaunchExecutor","LIST_WORKFLOWS_TOOL_NAME","z","toAgentToolResult","LAUNCH_WORKFLOW_TOOL_NAME","launchFailureOptions","appendGuidance","WORKFLOW_RUN_TOOL_NAME","workflowRunInputSchema","resolveRootSessionId","actionGuidance"],"sources":["../../../src/tools/workflowTools.ts"],"sourcesContent":["import { resolveRootSessionId } from '@agimon-ai/doompi-core/child-process';\nimport {\n createEmbeddedWorkflowFeature,\n type WorkflowRunRecord,\n type EmbeddedWorkflowFeature,\n} from '@agimon-ai/workflow-mcp';\nimport type { ExtensionAPI, ToolDefinition } from '@earendil-works/pi-coding-agent';\nimport { z } from 'zod';\n\nimport { workflowRunInputSchema } from '../schemas/workflowPi';\nimport { toAgentToolResult, type WorkflowToolDetails } from '../services/piToolBridge';\nimport {\n type WorkflowLaunchInput,\n type WorkflowRunSelector,\n launchFailureOptions,\n} from '../services/workflowExecution';\nimport {\n createWorkflowLaunchExecutor,\n reportProgress,\n appendGuidance,\n actionGuidance,\n type WorkflowPiToolDependencies,\n} from '../services/workflowExecution';\ntype WorkflowToolSchema = Parameters<ExtensionAPI['registerTool']>[0]['parameters'];\nimport {\n WORKFLOW_PI_TOOL_NAMES,\n LIST_WORKFLOWS_TOOL_NAME,\n LAUNCH_WORKFLOW_TOOL_NAME,\n WORKFLOW_RUN_TOOL_NAME,\n} from '../constants/workflow';\nconst PI_LAUNCH_FIELDS = {\n workflowPath: true,\n workspace: true,\n runner: true,\n name: true,\n env: true,\n prompt: true,\n job: true,\n inputs: true,\n} as const;\nexport type WorkflowNativeTool = ToolDefinition<WorkflowToolSchema, WorkflowToolDetails>;\nexport type WorkflowToolRenderers = Pick<WorkflowNativeTool, 'renderCall' | 'renderResult'>;\nexport function createWorkflowTools(\n dependencies: WorkflowPiToolDependencies,\n renderers: (name: (typeof WORKFLOW_PI_TOOL_NAMES)[number]) => WorkflowToolRenderers = () => ({}),\n): WorkflowNativeTool[] {\n const feature = dependencies.feature ?? createEmbeddedWorkflowFeature();\n const runTool = dependencies.runTool ?? feature.runTool;\n const listWorkflowsTool = dependencies.listWorkflowsTool ?? feature.listWorkflowsTool;\n const recoverTool = dependencies.recoverTool ?? feature.createRecoverTool();\n const controlTool = dependencies.controlTool ?? feature.createControlTool();\n const trackPendingRun = dependencies.trackPendingRun ?? (<T>(run: Promise<T>): Promise<T> => run);\n const launchExecutor =\n dependencies.launchExecutor ??\n createWorkflowLaunchExecutor({\n environment: dependencies.environment,\n activeRunCount: dependencies.activeRunCount,\n onLaunch: dependencies.onLaunch,\n observeSession: dependencies.observeSession,\n rejectRunner: dependencies.rejectRunner,\n runTool,\n trackPendingRun,\n });\n\n return [\n {\n name: LIST_WORKFLOWS_TOOL_NAME,\n label: 'List Workflows',\n description: 'List the workflow definitions available in this repository.',\n promptSnippet: 'List available workflow definitions',\n parameters: z.toJSONSchema(listWorkflowsTool.getInputSchema()),\n renderShell: 'self',\n ...renderers(LIST_WORKFLOWS_TOOL_NAME),\n async execute(_toolCallId, params) {\n return toAgentToolResult(\n LIST_WORKFLOWS_TOOL_NAME,\n await listWorkflowsTool.execute(\n params as Parameters<EmbeddedWorkflowFeature['listWorkflowsTool']['execute']>[0],\n ),\n );\n },\n },\n\n {\n name: LAUNCH_WORKFLOW_TOOL_NAME,\n label: 'Launch Workflow',\n description:\n 'Start a workflow run. Returns once the run has started, not once it finishes; use workflow_run with action status or wait for the completion notice.',\n promptSnippet: 'Start a workflow run and return its identifier',\n promptGuidelines: [\n 'Before launching, call list_workflows and pick the workflow whose own description matches the work. A workflow description is the source of truth; never infer one from its filename. If nothing matches, say so and stop rather than launching an approximate fit.',\n 'launch_workflow returns when the run has STARTED, not when it has finished. Never report success from its result. Poll workflow_run with action status, or wait for the completion notice the extension delivers on its own.',\n 'When dispatching Agiflow work, pass AGIFLOW_PROJECT_ID, AGIFLOW_JOB_KIND, and AGIFLOW_JOB_ID through env plus a non-empty prompt. Kind must be task or work-unit. The id alone is not a key, and omitting prompt leaves user_prompt workflows waiting for terminal input.',\n 'A workflow claims its own job in its pre step. Never claim a job yourself. JOB_ALREADY_CLAIMED or HTTP 409 means another worker won the race, so report contention and do not unlock, release, or retry automatically.',\n 'If a result says WORKFLOW_NOT_OWNED or a release failed, inspect current ownership and ask the user before any release or unlock. Never force another worker’s lock.',\n 'If a running workflow is not found, call workflow_run with action status to re-check current state before retrying.',\n 'The per-session concurrency ceiling is a real limit. If launch_workflow refuses, wait for a run to finish; do not launch through the shell to get around it.',\n ],\n parameters: z.toJSONSchema(runTool.getInputSchema().pick(PI_LAUNCH_FIELDS)),\n renderShell: 'self',\n ...renderers(LAUNCH_WORKFLOW_TOOL_NAME),\n async execute(_toolCallId, params, _signal, onUpdate, ctx) {\n const input = params as WorkflowLaunchInput;\n const result = await launchExecutor.execute(input, ctx, onUpdate);\n const adapted = toAgentToolResult(LAUNCH_WORKFLOW_TOOL_NAME, result, launchFailureOptions);\n return appendGuidance(\n adapted,\n 'Next: call workflow_run with action status and verify the recorded stage before reporting the workflow outcome.',\n );\n },\n },\n\n {\n name: WORKFLOW_RUN_TOOL_NAME,\n label: 'Workflow Run',\n description:\n 'Inspect or control one workflow run. Use action status, tail, recovery-evidence, follow, open, pause, resume, stop, or recover. Every action requires an exact run key.',\n promptSnippet: 'Inspect or control a workflow run',\n promptGuidelines: [\n 'Use action status as the authority for stage, outcome, execution state, and pause cursor. A run that started is not a run that succeeded.',\n 'Use action pause or resume for cooperative lifecycle control. Pause is not stop: the run keeps its stage running, capacity, services, and heartbeat while paused.',\n 'Use action stop only when the user requests stopping a run. A user-requested stop is not a failure and needs no failure diagnosis.',\n 'Do not use follow or tail merely to wait for a healthy run; use status or the completion notice. Tail returns chat-safe status without raw PTY frames. Use follow only when the user asks to pin live progress, and open only when a human wants the recorded launcher foregrounded.',\n 'Use action recovery-evidence for terminal-only, durable status and artifacts before recovery. It is the only inspection path that may read a failed run from an earlier session and it never reads a live launcher.',\n 'Use action recover only after loading the `workflow-recovery` skill and gathering its required evidence. Recovery may adopt a terminal failed run from an earlier Pi session; it never grants cross-session control of running work. Never edit issue.md or repair.json, and verify real process and registry progress before recovery.',\n 'Never unlock or force-release an Agiflow claim from this tool.',\n ],\n parameters: z.toJSONSchema(workflowRunInputSchema),\n renderShell: 'self',\n ...renderers(WORKFLOW_RUN_TOOL_NAME),\n async execute(_toolCallId, rawParams, _signal, onUpdate, ctx) {\n const input = workflowRunInputSchema.parse(rawParams);\n const sessionId = resolveRootSessionId(ctx.sessionManager.getSessionId(), dependencies.environment);\n dependencies.observeSession?.(sessionId);\n const selector: WorkflowRunSelector = { runKey: input.runKey, workspace: input.workspace };\n const requireOwnedRun = async (): Promise<WorkflowRunRecord> => {\n if (!dependencies.requireSessionRun) {\n throw new Error('Workflow run ownership is not available in this session.');\n }\n return dependencies.requireSessionRun(selector, sessionId);\n };\n\n if (input.action === 'status') {\n const record = await requireOwnedRun();\n return {\n content: [{ type: 'text', text: JSON.stringify(record, null, 2) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'tail') {\n if (!dependencies.tailRun) throw new Error('Reading workflow output is not available in this session.');\n const record = await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Reading output for workflow ${input.runKey}...`);\n await dependencies.tailRun(selector, ctx);\n return {\n content: [\n {\n type: 'text',\n text: [\n `Workflow: ${record.displayName} (${record.runKey})`,\n `Stage: ${record.effectiveStage ?? record.stage}`,\n 'Raw launcher output is hidden from chat. Use action follow or open to view it.',\n ].join('\\n'),\n },\n ],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'recovery-evidence') {\n if (!dependencies.readRecoveryEvidence) {\n throw new Error('Reading workflow recovery evidence is not available in this session.');\n }\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Reading recovery evidence for workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.readRecoveryEvidence(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'follow') {\n if (!dependencies.followRun) throw new Error('Following workflow output is not available in this session.');\n await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Following workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.followRun(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'open') {\n if (!dependencies.openRun) throw new Error('Opening a workflow launcher is not available in this session.');\n await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Opening workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.openRun(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n\n if (input.action === 'recover') {\n if (!dependencies.requireRecoverableRun) {\n throw new Error('Workflow recovery ownership transfer is not available in this session.');\n }\n const recoverable = await dependencies.requireRecoverableRun(selector);\n const recoverySelector = { runKey: recoverable.runKey, workspace: recoverable.workspace };\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Recovering workflow ${input.runKey}...`);\n // A recovery replays the workflow, and the replay skips the launch\n // step, so every job runs in THIS process. For a workflow whose steps\n // are interactive that means an agent TUI spawned onto this process's\n // own terminal. Hand it to a launcher instead, where the workflow says\n // how. A dry run prints rather than executes, so it stays here.\n if (!input.dryRun) {\n const delegated = await dependencies.delegateRecovery?.({ ...recoverySelector, runner: input.runner }, ctx);\n if (delegated) {\n const guidance = actionGuidance(input.action, input.runKey);\n await dependencies.onLaunch?.(ctx);\n return {\n content: [{ type: 'text', text: [delegated, guidance[0]].join('\\n\\n') }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n }\n const recoverInput = {\n dryRun: input.dryRun,\n job: input.job,\n runKey: recoverySelector.runKey,\n runner: input.runner,\n workspace: recoverySelector.workspace,\n };\n const result = await trackPendingRun(recoverTool.execute(recoverInput));\n const guidance = actionGuidance(input.action, input.runKey);\n const adapted = toAgentToolResult(WORKFLOW_RUN_TOOL_NAME, result, guidance);\n await dependencies.onLaunch?.(ctx);\n return appendGuidance(adapted, guidance[0]!);\n }\n\n const ownedRun = await requireOwnedRun();\n if (!ownedRun.runId) throw new Error(`Workflow ${input.runKey} has no controllable run generation.`);\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Requesting ${input.action} for workflow ${input.runKey}...`);\n const result = await controlTool.execute({\n action: input.action,\n expectedRunId: ownedRun.runId,\n reason: input.reason,\n runKey: input.runKey,\n workspace: input.workspace,\n });\n const guidance = actionGuidance(input.action, input.runKey);\n const adapted = toAgentToolResult(WORKFLOW_RUN_TOOL_NAME, result, guidance);\n return appendGuidance(adapted, guidance[0]!);\n },\n },\n ];\n}\n"],"mappings":"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"}
@@ -1,5 +0,0 @@
1
- import{LAUNCH_WORKFLOW_TOOL_NAME as e,LIST_WORKFLOWS_TOOL_NAME as t,WORKFLOW_RUN_TOOL_NAME as n}from"../constants/workflow.mjs";import{workflowRunInputSchema as r}from"../schemas/workflowPi.mjs";import{toAgentToolResult as i}from"../services/piToolBridge/index.mjs";import{actionGuidance as a,appendGuidance as o,createWorkflowLaunchExecutor as s,launchFailureOptions as c,reportProgress as l}from"../services/workflowExecution/index.mjs";import{resolveRootSessionId as u}from"@agimon-ai/doompi-core/child-process";import{createEmbeddedWorkflowFeature as d}from"@agimon-ai/workflow-mcp";import{z as f}from"zod";const p={workflowPath:!0,workspace:!0,runner:!0,name:!0,env:!0,prompt:!0,job:!0,inputs:!0};function m(m,h=()=>({})){let g=m.feature??d(),_=m.runTool??g.runTool,v=m.listWorkflowsTool??g.listWorkflowsTool,y=m.recoverTool??g.createRecoverTool(),b=m.controlTool??g.createControlTool(),x=m.trackPendingRun??(e=>e),S=m.launchExecutor??s({environment:m.environment,activeRunCount:m.activeRunCount,onLaunch:m.onLaunch,observeSession:m.observeSession,rejectRunner:m.rejectRunner,runTool:_,trackPendingRun:x});return[{name:t,label:`List Workflows`,description:`List the workflow definitions available in this repository.`,promptSnippet:`List available workflow definitions`,parameters:f.toJSONSchema(v.getInputSchema()),renderShell:`self`,...h(t),async execute(e,n){return i(t,await v.execute(n))}},{name:e,label:`Launch Workflow`,description:`Start a workflow run. Returns once the run has started, not once it finishes; use workflow_run with action status or wait for the completion notice.`,promptSnippet:`Start a workflow run and return its identifier`,promptGuidelines:[`Before launching, call list_workflows and pick the workflow whose own description matches the work. A workflow description is the source of truth; never infer one from its filename. If nothing matches, say so and stop rather than launching an approximate fit.`,`launch_workflow returns when the run has STARTED, not when it has finished. Never report success from its result. Poll workflow_run with action status, or wait for the completion notice the extension delivers on its own.`,`When dispatching Agiflow work, pass AGIFLOW_PROJECT_ID, AGIFLOW_JOB_KIND, and AGIFLOW_JOB_ID through env plus a non-empty prompt. Kind must be task or work-unit. The id alone is not a key, and omitting prompt leaves user_prompt workflows waiting for terminal input.`,`A workflow claims its own job in its pre step. Never claim a job yourself. JOB_ALREADY_CLAIMED or HTTP 409 means another worker won the race, so report contention and do not unlock, release, or retry automatically.`,`If a result says WORKFLOW_NOT_OWNED or a release failed, inspect current ownership and ask the user before any release or unlock. Never force another worker’s lock.`,`If a running workflow is not found, call workflow_run with action status to re-check current state before retrying.`,`The per-session concurrency ceiling is a real limit. If launch_workflow refuses, wait for a run to finish; do not launch through the shell to get around it.`],parameters:f.toJSONSchema(_.getInputSchema().pick(p)),renderShell:`self`,...h(e),async execute(t,n,r,a,s){let l=n,u=await S.execute(l,s,a),d=i(e,u,c);return o(d,`Next: call workflow_run with action status and verify the recorded stage before reporting the workflow outcome.`)}},{name:n,label:`Workflow Run`,description:`Inspect or control one workflow run. Use action status, tail, recovery-evidence, follow, open, pause, resume, stop, or recover. Every action requires an exact run key.`,promptSnippet:`Inspect or control a workflow run`,promptGuidelines:[`Use action status as the authority for stage, outcome, execution state, and pause cursor. A run that started is not a run that succeeded.`,`Use action pause or resume for cooperative lifecycle control. Pause is not stop: the run keeps its stage running, capacity, services, and heartbeat while paused.`,`Use action stop only when the user requests stopping a run. A user-requested stop is not a failure and needs no failure diagnosis.`,`Do not use follow or tail merely to wait for a healthy run; use status or the completion notice. Tail returns chat-safe status without raw PTY frames. Use follow only when the user asks to pin live progress, and open only when a human wants the recorded launcher foregrounded.`,`Use action recovery-evidence for terminal-only, durable status and artifacts before recovery. It is the only inspection path that may read a failed run from an earlier session and it never reads a live launcher.`,"Use action recover only after loading the `workflow-recovery` skill and gathering its required evidence. Recovery may adopt a terminal failed run from an earlier Pi session; it never grants cross-session control of running work. Never edit issue.md or repair.json, and verify real process and registry progress before recovery.",`Never unlock or force-release an Agiflow claim from this tool.`],parameters:f.toJSONSchema(r),renderShell:`self`,...h(n),async execute(e,t,s,c,d){let f=r.parse(t),p=u(d.sessionManager.getSessionId(),m.environment);m.observeSession?.(p);let h={runKey:f.runKey,workspace:f.workspace},g=async()=>{if(!m.requireSessionRun)throw Error(`Workflow run ownership is not available in this session.`);return m.requireSessionRun(h,p)};if(f.action===`status`){let e=await g();return{content:[{type:`text`,text:JSON.stringify(e,null,2)}],details:{tool:n}}}if(f.action===`tail`){if(!m.tailRun)throw Error(`Reading workflow output is not available in this session.`);let e=await g();return l(c,n,`Reading output for workflow ${f.runKey}...`),await m.tailRun(h,d),{content:[{type:`text`,text:[`Workflow: ${e.displayName} (${e.runKey})`,`Stage: ${e.effectiveStage??e.stage}`,`Raw launcher output is hidden from chat. Use action follow or open to view it.`].join(`
2
- `)}],details:{tool:n}}}if(f.action===`recovery-evidence`){if(!m.readRecoveryEvidence)throw Error(`Reading workflow recovery evidence is not available in this session.`);return l(c,n,`Reading recovery evidence for workflow ${f.runKey}...`),{content:[{type:`text`,text:await m.readRecoveryEvidence(h,d)}],details:{tool:n}}}if(f.action===`follow`){if(!m.followRun)throw Error(`Following workflow output is not available in this session.`);return await g(),l(c,n,`Following workflow ${f.runKey}...`),{content:[{type:`text`,text:await m.followRun(h,d)}],details:{tool:n}}}if(f.action===`open`){if(!m.openRun)throw Error(`Opening a workflow launcher is not available in this session.`);return await g(),l(c,n,`Opening workflow ${f.runKey}...`),{content:[{type:`text`,text:await m.openRun(h,d)}],details:{tool:n}}}if(f.action===`recover`){if(!m.requireRecoverableRun)throw Error(`Workflow recovery ownership transfer is not available in this session.`);let e=await m.requireRecoverableRun(h),t={runKey:e.runKey,workspace:e.workspace};if(l(c,n,`Recovering workflow ${f.runKey}...`),!f.dryRun){let e=await m.delegateRecovery?.({...t,runner:f.runner},d);if(e){let t=a(f.action,f.runKey);return await m.onLaunch?.(d),{content:[{type:`text`,text:[e,t[0]].join(`
3
-
4
- `)}],details:{tool:n}}}}let r={dryRun:f.dryRun,job:f.job,runKey:t.runKey,runner:f.runner,workspace:t.workspace},s=await x(y.execute(r)),u=a(f.action,f.runKey),p=i(n,s,u);return await m.onLaunch?.(d),o(p,u[0])}let _=await g();if(!_.runId)throw Error(`Workflow ${f.runKey} has no controllable run generation.`);l(c,n,`Requesting ${f.action} for workflow ${f.runKey}...`);let v=await b.execute({action:f.action,expectedRunId:_.runId,reason:f.reason,runKey:f.runKey,workspace:f.workspace}),S=a(f.action,f.runKey),C=i(n,v,S);return o(C,S[0])}}]}export{m as createWorkflowTools};
5
- //# sourceMappingURL=workflowTools.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"workflowTools.mjs","names":[],"sources":["../../../src/tools/workflowTools.ts"],"sourcesContent":["import { resolveRootSessionId } from '@agimon-ai/doompi-core/child-process';\nimport {\n createEmbeddedWorkflowFeature,\n type WorkflowRunRecord,\n type EmbeddedWorkflowFeature,\n} from '@agimon-ai/workflow-mcp';\nimport type { ExtensionAPI, ToolDefinition } from '@earendil-works/pi-coding-agent';\nimport { z } from 'zod';\n\nimport { workflowRunInputSchema } from '../schemas/workflowPi';\nimport { toAgentToolResult, type WorkflowToolDetails } from '../services/piToolBridge';\nimport {\n type WorkflowLaunchInput,\n type WorkflowRunSelector,\n launchFailureOptions,\n} from '../services/workflowExecution';\nimport {\n createWorkflowLaunchExecutor,\n reportProgress,\n appendGuidance,\n actionGuidance,\n type WorkflowPiToolDependencies,\n} from '../services/workflowExecution';\ntype WorkflowToolSchema = Parameters<ExtensionAPI['registerTool']>[0]['parameters'];\nimport {\n WORKFLOW_PI_TOOL_NAMES,\n LIST_WORKFLOWS_TOOL_NAME,\n LAUNCH_WORKFLOW_TOOL_NAME,\n WORKFLOW_RUN_TOOL_NAME,\n} from '../constants/workflow';\nconst PI_LAUNCH_FIELDS = {\n workflowPath: true,\n workspace: true,\n runner: true,\n name: true,\n env: true,\n prompt: true,\n job: true,\n inputs: true,\n} as const;\nexport type WorkflowNativeTool = ToolDefinition<WorkflowToolSchema, WorkflowToolDetails>;\nexport type WorkflowToolRenderers = Pick<WorkflowNativeTool, 'renderCall' | 'renderResult'>;\nexport function createWorkflowTools(\n dependencies: WorkflowPiToolDependencies,\n renderers: (name: (typeof WORKFLOW_PI_TOOL_NAMES)[number]) => WorkflowToolRenderers = () => ({}),\n): WorkflowNativeTool[] {\n const feature = dependencies.feature ?? createEmbeddedWorkflowFeature();\n const runTool = dependencies.runTool ?? feature.runTool;\n const listWorkflowsTool = dependencies.listWorkflowsTool ?? feature.listWorkflowsTool;\n const recoverTool = dependencies.recoverTool ?? feature.createRecoverTool();\n const controlTool = dependencies.controlTool ?? feature.createControlTool();\n const trackPendingRun = dependencies.trackPendingRun ?? (<T>(run: Promise<T>): Promise<T> => run);\n const launchExecutor =\n dependencies.launchExecutor ??\n createWorkflowLaunchExecutor({\n environment: dependencies.environment,\n activeRunCount: dependencies.activeRunCount,\n onLaunch: dependencies.onLaunch,\n observeSession: dependencies.observeSession,\n rejectRunner: dependencies.rejectRunner,\n runTool,\n trackPendingRun,\n });\n\n return [\n {\n name: LIST_WORKFLOWS_TOOL_NAME,\n label: 'List Workflows',\n description: 'List the workflow definitions available in this repository.',\n promptSnippet: 'List available workflow definitions',\n parameters: z.toJSONSchema(listWorkflowsTool.getInputSchema()),\n renderShell: 'self',\n ...renderers(LIST_WORKFLOWS_TOOL_NAME),\n async execute(_toolCallId, params) {\n return toAgentToolResult(\n LIST_WORKFLOWS_TOOL_NAME,\n await listWorkflowsTool.execute(\n params as Parameters<EmbeddedWorkflowFeature['listWorkflowsTool']['execute']>[0],\n ),\n );\n },\n },\n\n {\n name: LAUNCH_WORKFLOW_TOOL_NAME,\n label: 'Launch Workflow',\n description:\n 'Start a workflow run. Returns once the run has started, not once it finishes; use workflow_run with action status or wait for the completion notice.',\n promptSnippet: 'Start a workflow run and return its identifier',\n promptGuidelines: [\n 'Before launching, call list_workflows and pick the workflow whose own description matches the work. A workflow description is the source of truth; never infer one from its filename. If nothing matches, say so and stop rather than launching an approximate fit.',\n 'launch_workflow returns when the run has STARTED, not when it has finished. Never report success from its result. Poll workflow_run with action status, or wait for the completion notice the extension delivers on its own.',\n 'When dispatching Agiflow work, pass AGIFLOW_PROJECT_ID, AGIFLOW_JOB_KIND, and AGIFLOW_JOB_ID through env plus a non-empty prompt. Kind must be task or work-unit. The id alone is not a key, and omitting prompt leaves user_prompt workflows waiting for terminal input.',\n 'A workflow claims its own job in its pre step. Never claim a job yourself. JOB_ALREADY_CLAIMED or HTTP 409 means another worker won the race, so report contention and do not unlock, release, or retry automatically.',\n 'If a result says WORKFLOW_NOT_OWNED or a release failed, inspect current ownership and ask the user before any release or unlock. Never force another worker’s lock.',\n 'If a running workflow is not found, call workflow_run with action status to re-check current state before retrying.',\n 'The per-session concurrency ceiling is a real limit. If launch_workflow refuses, wait for a run to finish; do not launch through the shell to get around it.',\n ],\n parameters: z.toJSONSchema(runTool.getInputSchema().pick(PI_LAUNCH_FIELDS)),\n renderShell: 'self',\n ...renderers(LAUNCH_WORKFLOW_TOOL_NAME),\n async execute(_toolCallId, params, _signal, onUpdate, ctx) {\n const input = params as WorkflowLaunchInput;\n const result = await launchExecutor.execute(input, ctx, onUpdate);\n const adapted = toAgentToolResult(LAUNCH_WORKFLOW_TOOL_NAME, result, launchFailureOptions);\n return appendGuidance(\n adapted,\n 'Next: call workflow_run with action status and verify the recorded stage before reporting the workflow outcome.',\n );\n },\n },\n\n {\n name: WORKFLOW_RUN_TOOL_NAME,\n label: 'Workflow Run',\n description:\n 'Inspect or control one workflow run. Use action status, tail, recovery-evidence, follow, open, pause, resume, stop, or recover. Every action requires an exact run key.',\n promptSnippet: 'Inspect or control a workflow run',\n promptGuidelines: [\n 'Use action status as the authority for stage, outcome, execution state, and pause cursor. A run that started is not a run that succeeded.',\n 'Use action pause or resume for cooperative lifecycle control. Pause is not stop: the run keeps its stage running, capacity, services, and heartbeat while paused.',\n 'Use action stop only when the user requests stopping a run. A user-requested stop is not a failure and needs no failure diagnosis.',\n 'Do not use follow or tail merely to wait for a healthy run; use status or the completion notice. Tail returns chat-safe status without raw PTY frames. Use follow only when the user asks to pin live progress, and open only when a human wants the recorded launcher foregrounded.',\n 'Use action recovery-evidence for terminal-only, durable status and artifacts before recovery. It is the only inspection path that may read a failed run from an earlier session and it never reads a live launcher.',\n 'Use action recover only after loading the `workflow-recovery` skill and gathering its required evidence. Recovery may adopt a terminal failed run from an earlier Pi session; it never grants cross-session control of running work. Never edit issue.md or repair.json, and verify real process and registry progress before recovery.',\n 'Never unlock or force-release an Agiflow claim from this tool.',\n ],\n parameters: z.toJSONSchema(workflowRunInputSchema),\n renderShell: 'self',\n ...renderers(WORKFLOW_RUN_TOOL_NAME),\n async execute(_toolCallId, rawParams, _signal, onUpdate, ctx) {\n const input = workflowRunInputSchema.parse(rawParams);\n const sessionId = resolveRootSessionId(ctx.sessionManager.getSessionId(), dependencies.environment);\n dependencies.observeSession?.(sessionId);\n const selector: WorkflowRunSelector = { runKey: input.runKey, workspace: input.workspace };\n const requireOwnedRun = async (): Promise<WorkflowRunRecord> => {\n if (!dependencies.requireSessionRun) {\n throw new Error('Workflow run ownership is not available in this session.');\n }\n return dependencies.requireSessionRun(selector, sessionId);\n };\n\n if (input.action === 'status') {\n const record = await requireOwnedRun();\n return {\n content: [{ type: 'text', text: JSON.stringify(record, null, 2) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'tail') {\n if (!dependencies.tailRun) throw new Error('Reading workflow output is not available in this session.');\n const record = await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Reading output for workflow ${input.runKey}...`);\n await dependencies.tailRun(selector, ctx);\n return {\n content: [\n {\n type: 'text',\n text: [\n `Workflow: ${record.displayName} (${record.runKey})`,\n `Stage: ${record.effectiveStage ?? record.stage}`,\n 'Raw launcher output is hidden from chat. Use action follow or open to view it.',\n ].join('\\n'),\n },\n ],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'recovery-evidence') {\n if (!dependencies.readRecoveryEvidence) {\n throw new Error('Reading workflow recovery evidence is not available in this session.');\n }\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Reading recovery evidence for workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.readRecoveryEvidence(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'follow') {\n if (!dependencies.followRun) throw new Error('Following workflow output is not available in this session.');\n await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Following workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.followRun(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n if (input.action === 'open') {\n if (!dependencies.openRun) throw new Error('Opening a workflow launcher is not available in this session.');\n await requireOwnedRun();\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Opening workflow ${input.runKey}...`);\n return {\n content: [{ type: 'text', text: await dependencies.openRun(selector, ctx) }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n\n if (input.action === 'recover') {\n if (!dependencies.requireRecoverableRun) {\n throw new Error('Workflow recovery ownership transfer is not available in this session.');\n }\n const recoverable = await dependencies.requireRecoverableRun(selector);\n const recoverySelector = { runKey: recoverable.runKey, workspace: recoverable.workspace };\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Recovering workflow ${input.runKey}...`);\n // A recovery replays the workflow, and the replay skips the launch\n // step, so every job runs in THIS process. For a workflow whose steps\n // are interactive that means an agent TUI spawned onto this process's\n // own terminal. Hand it to a launcher instead, where the workflow says\n // how. A dry run prints rather than executes, so it stays here.\n if (!input.dryRun) {\n const delegated = await dependencies.delegateRecovery?.({ ...recoverySelector, runner: input.runner }, ctx);\n if (delegated) {\n const guidance = actionGuidance(input.action, input.runKey);\n await dependencies.onLaunch?.(ctx);\n return {\n content: [{ type: 'text', text: [delegated, guidance[0]].join('\\n\\n') }],\n details: { tool: WORKFLOW_RUN_TOOL_NAME },\n };\n }\n }\n const recoverInput = {\n dryRun: input.dryRun,\n job: input.job,\n runKey: recoverySelector.runKey,\n runner: input.runner,\n workspace: recoverySelector.workspace,\n };\n const result = await trackPendingRun(recoverTool.execute(recoverInput));\n const guidance = actionGuidance(input.action, input.runKey);\n const adapted = toAgentToolResult(WORKFLOW_RUN_TOOL_NAME, result, guidance);\n await dependencies.onLaunch?.(ctx);\n return appendGuidance(adapted, guidance[0]!);\n }\n\n const ownedRun = await requireOwnedRun();\n if (!ownedRun.runId) throw new Error(`Workflow ${input.runKey} has no controllable run generation.`);\n reportProgress(onUpdate, WORKFLOW_RUN_TOOL_NAME, `Requesting ${input.action} for workflow ${input.runKey}...`);\n const result = await controlTool.execute({\n action: input.action,\n expectedRunId: ownedRun.runId,\n reason: input.reason,\n runKey: input.runKey,\n workspace: input.workspace,\n });\n const guidance = actionGuidance(input.action, input.runKey);\n const adapted = toAgentToolResult(WORKFLOW_RUN_TOOL_NAME, result, guidance);\n return appendGuidance(adapted, guidance[0]!);\n },\n },\n ];\n}\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- const e=require("../constants/workflow.cjs"),t=`doom-workflow`,n={key:`w`,label:e.LEADER_LABEL,detail:e.LEADER_DETAIL,order:50};function r(r){return[{id:`${t}.catalog`,path:[n,{key:`l`,label:`list`,detail:`browse and launch workflows`}],action:{name:e.LEADER_CATALOG_ACTION}},{id:`${t}.manage`,path:[n,{key:`r`,label:`runs`,detail:`runs in this session`}],action:{name:e.LEADER_MANAGE_ACTION}},r?{id:`${t}.disable`,path:[n,{key:`e`,label:`exit`,detail:`take back workflow tools`,tone:`exit`}],action:{name:e.LEADER_DISABLE_ACTION}}:{id:`${t}.enable`,path:[n,{key:`e`,label:`enter`,detail:`give the agent workflow tools`}],action:{name:e.LEADER_ENABLE_ACTION}},{id:`${t}.recover`,path:[n,{key:`c`,label:`recover`,detail:`adopt a failed run`}],action:{name:e.LEADER_RECOVER_ACTION}}]}function i(t,n=!1){let i=t.registerLeader({source:e.PACKAGE_SOURCE,bindings:r(n)});return{setMode(e){i.update(r(e))},dispose(){i.dispose()}}}exports.registerLeaderContribution=i,exports.workflowLeaderBindings=r;
2
- //# sourceMappingURL=leader.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"leader.cjs","names":["LEADER_LABEL","LEADER_DETAIL","LEADER_CATALOG_ACTION","LEADER_MANAGE_ACTION","LEADER_DISABLE_ACTION","LEADER_ENABLE_ACTION","LEADER_RECOVER_ACTION","PACKAGE_SOURCE"],"sources":["../../../src/tui/leader.ts"],"sourcesContent":["import type { LeaderBinding } from '@agimon-ai/doompi-core/leader';\nimport type { DoomUiHubService } from '@agimon-ai/doompi-core/ui-hub';\n\nimport {\n LEADER_CATALOG_ACTION,\n LEADER_DETAIL,\n LEADER_DISABLE_ACTION,\n LEADER_ENABLE_ACTION,\n LEADER_KEY,\n LEADER_LABEL,\n LEADER_MANAGE_ACTION,\n LEADER_ORDER,\n LEADER_RECOVER_ACTION,\n PACKAGE_SOURCE,\n} from '../constants/workflow';\n\nconst LEADER_BINDING_PREFIX = 'doom-workflow';\nconst GROUP_SEGMENT = {\n key: LEADER_KEY,\n label: LEADER_LABEL,\n detail: LEADER_DETAIL,\n order: LEADER_ORDER,\n} 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}.catalog`,\n // Launching lives on this board's own `r` key rather than a second menu\n // entry: picking which workflow to run is the same act as reading what\n // one does.\n path: [GROUP_SEGMENT, { key: 'l', label: 'list', detail: 'browse and launch workflows' }],\n action: { name: LEADER_CATALOG_ACTION },\n },\n {\n id: `${LEADER_BINDING_PREFIX}.manage`,\n path: [GROUP_SEGMENT, { key: 'r', label: 'runs', detail: 'runs in this session' }],\n action: { name: LEADER_MANAGE_ACTION },\n },\n mode\n ? {\n id: `${LEADER_BINDING_PREFIX}.disable`,\n path: [GROUP_SEGMENT, { key: 'e', label: 'exit', detail: 'take back workflow tools', tone: 'exit' }],\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 // `r` is runs everywhere, so recovery moves off it rather than reading as a\n // third flavour of list in the one space that has both.\n {\n id: `${LEADER_BINDING_PREFIX}.recover`,\n path: [GROUP_SEGMENT, { key: 'c', label: 'recover', detail: 'adopt a failed run' }],\n action: { name: LEADER_RECOVER_ACTION },\n },\n ];\n}\n\nexport function registerLeaderContribution(hub: DoomUiHubService, enabled = false): WorkflowLeaderHandle {\n const contribution = hub.registerLeader({\n source: PACKAGE_SOURCE,\n bindings: workflowLeaderBindings(enabled),\n });\n return {\n setMode(enabled) {\n contribution.update(workflowLeaderBindings(enabled));\n },\n dispose() {\n contribution.dispose();\n },\n };\n}\n"],"mappings":"6CAgBM,EAAwB,gBACxB,EAAgB,CACpB,IAAA,IACA,MAAOA,EAAAA,aACP,OAAQC,EAAAA,cACR,MAAA,EACF,EAeA,SAAgB,EAAuB,EAAgC,CACrE,MAAO,CACL,CACE,GAAI,GAAG,EAAsB,UAI7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,6BAA8B,CAAC,EACxF,OAAQ,CAAE,KAAMC,EAAAA,qBAAsB,CACxC,EACA,CACE,GAAI,GAAG,EAAsB,SAC7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,sBAAuB,CAAC,EACjF,OAAQ,CAAE,KAAMC,EAAAA,oBAAqB,CACvC,EACA,EACI,CACE,GAAI,GAAG,EAAsB,UAC7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,2BAA4B,KAAM,MAAO,CAAC,EACnG,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,EAGJ,CACE,GAAI,GAAG,EAAsB,UAC7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,UAAW,OAAQ,oBAAqB,CAAC,EAClF,OAAQ,CAAE,KAAMC,EAAAA,qBAAsB,CACxC,CACF,CACF,CAEA,SAAgB,EAA2B,EAAuB,EAAU,GAA6B,CACvG,IAAM,EAAe,EAAI,eAAe,CACtC,OAAQC,EAAAA,eACR,SAAU,EAAuB,CAAO,CAC1C,CAAC,EACD,MAAO,CACL,QAAQ,EAAS,CACf,EAAa,OAAO,EAAuB,CAAO,CAAC,CACrD,EACA,SAAU,CACR,EAAa,QAAQ,CACvB,CACF,CACF"}
@@ -1,2 +0,0 @@
1
- import{LEADER_CATALOG_ACTION as e,LEADER_DETAIL as t,LEADER_DISABLE_ACTION as n,LEADER_ENABLE_ACTION as r,LEADER_LABEL as i,LEADER_MANAGE_ACTION as a,LEADER_RECOVER_ACTION as o,PACKAGE_SOURCE as s}from"../constants/workflow.mjs";const c=`doom-workflow`,l={key:`w`,label:i,detail:t,order:50};function u(t){return[{id:`${c}.catalog`,path:[l,{key:`l`,label:`list`,detail:`browse and launch workflows`}],action:{name:e}},{id:`${c}.manage`,path:[l,{key:`r`,label:`runs`,detail:`runs in this session`}],action:{name:a}},t?{id:`${c}.disable`,path:[l,{key:`e`,label:`exit`,detail:`take back workflow tools`,tone:`exit`}],action:{name:n}}:{id:`${c}.enable`,path:[l,{key:`e`,label:`enter`,detail:`give the agent workflow tools`}],action:{name:r}},{id:`${c}.recover`,path:[l,{key:`c`,label:`recover`,detail:`adopt a failed run`}],action:{name:o}}]}function d(e,t=!1){let n=e.registerLeader({source:s,bindings:u(t)});return{setMode(e){n.update(u(e))},dispose(){n.dispose()}}}export{d as registerLeaderContribution,u as workflowLeaderBindings};
2
- //# sourceMappingURL=leader.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"leader.mjs","names":[],"sources":["../../../src/tui/leader.ts"],"sourcesContent":["import type { LeaderBinding } from '@agimon-ai/doompi-core/leader';\nimport type { DoomUiHubService } from '@agimon-ai/doompi-core/ui-hub';\n\nimport {\n LEADER_CATALOG_ACTION,\n LEADER_DETAIL,\n LEADER_DISABLE_ACTION,\n LEADER_ENABLE_ACTION,\n LEADER_KEY,\n LEADER_LABEL,\n LEADER_MANAGE_ACTION,\n LEADER_ORDER,\n LEADER_RECOVER_ACTION,\n PACKAGE_SOURCE,\n} from '../constants/workflow';\n\nconst LEADER_BINDING_PREFIX = 'doom-workflow';\nconst GROUP_SEGMENT = {\n key: LEADER_KEY,\n label: LEADER_LABEL,\n detail: LEADER_DETAIL,\n order: LEADER_ORDER,\n} 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}.catalog`,\n // Launching lives on this board's own `r` key rather than a second menu\n // entry: picking which workflow to run is the same act as reading what\n // one does.\n path: [GROUP_SEGMENT, { key: 'l', label: 'list', detail: 'browse and launch workflows' }],\n action: { name: LEADER_CATALOG_ACTION },\n },\n {\n id: `${LEADER_BINDING_PREFIX}.manage`,\n path: [GROUP_SEGMENT, { key: 'r', label: 'runs', detail: 'runs in this session' }],\n action: { name: LEADER_MANAGE_ACTION },\n },\n mode\n ? {\n id: `${LEADER_BINDING_PREFIX}.disable`,\n path: [GROUP_SEGMENT, { key: 'e', label: 'exit', detail: 'take back workflow tools', tone: 'exit' }],\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 // `r` is runs everywhere, so recovery moves off it rather than reading as a\n // third flavour of list in the one space that has both.\n {\n id: `${LEADER_BINDING_PREFIX}.recover`,\n path: [GROUP_SEGMENT, { key: 'c', label: 'recover', detail: 'adopt a failed run' }],\n action: { name: LEADER_RECOVER_ACTION },\n },\n ];\n}\n\nexport function registerLeaderContribution(hub: DoomUiHubService, enabled = false): WorkflowLeaderHandle {\n const contribution = hub.registerLeader({\n source: PACKAGE_SOURCE,\n bindings: workflowLeaderBindings(enabled),\n });\n return {\n setMode(enabled) {\n contribution.update(workflowLeaderBindings(enabled));\n },\n dispose() {\n contribution.dispose();\n },\n };\n}\n"],"mappings":"qOAgBA,MAAM,EAAwB,gBACxB,EAAgB,CACpB,IAAA,IACA,MAAO,EACP,OAAQ,EACR,MAAA,EACF,EAeA,SAAgB,EAAuB,EAAgC,CACrE,MAAO,CACL,CACE,GAAI,GAAG,EAAsB,UAI7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,6BAA8B,CAAC,EACxF,OAAQ,CAAE,KAAM,CAAsB,CACxC,EACA,CACE,GAAI,GAAG,EAAsB,SAC7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,sBAAuB,CAAC,EACjF,OAAQ,CAAE,KAAM,CAAqB,CACvC,EACA,EACI,CACE,GAAI,GAAG,EAAsB,UAC7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,2BAA4B,KAAM,MAAO,CAAC,EACnG,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,EAGJ,CACE,GAAI,GAAG,EAAsB,UAC7B,KAAM,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,UAAW,OAAQ,oBAAqB,CAAC,EAClF,OAAQ,CAAE,KAAM,CAAsB,CACxC,CACF,CACF,CAEA,SAAgB,EAA2B,EAAuB,EAAU,GAA6B,CACvG,IAAM,EAAe,EAAI,eAAe,CACtC,OAAQ,EACR,SAAU,EAAuB,CAAO,CAC1C,CAAC,EACD,MAAO,CACL,QAAQ,EAAS,CACf,EAAa,OAAO,EAAuB,CAAO,CAAC,CACrD,EACA,SAAU,CACR,EAAa,QAAQ,CACvB,CACF,CACF"}
@@ -1,2 +0,0 @@
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
@@ -1 +0,0 @@
1
- {"version":3,"file":"doomOverlay.cjs","names":["visibleWidth","truncateToWidth","padLine","frameLine","fitLine"],"sources":["../../../../src/tui/workflow/doomOverlay.ts"],"sourcesContent":["import type { Theme } from '@earendil-works/pi-coding-agent';\nimport { type Component, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\n\nimport { fitLine, frameLine, padLine } from './rendering';\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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"}
@@ -1,2 +0,0 @@
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