@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/piToolBridge/index.ts"],"sourcesContent":["/**\n * Pure bridging logic between MCP tool results and the Pi tool surface.\n *\n * DESIGN PATTERNS:\n * - Adapter pattern: MCP `CallToolResult` is converted to Pi's result shape\n * - Pure module: no service or tool-class imports, so this is unit-testable\n * without constructing the workflow execution stack\n *\n * CODING STANDARDS:\n * - Named exports only\n * - Explicit return types on exported functions\n *\n * AVOID:\n * - Importing tool classes or services here (that belongs in `piTools.ts`)\n * - Returning an error flag Pi does not read; Pi tools signal failure by throwing\n */\n\nimport type { WorkflowProgressJob, WorkflowRunRecord } from '@agimon-ai/workflow-mcp';\nimport type { AgentToolResult } from '@earendil-works/pi-coding-agent';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\n\n/**\n * Environment variable stamped onto every run launched from a Pi session.\n *\n * `RunWorkflowService` forwards `env` to the inner CLI invocation across the\n * `launch-command` delegation boundary and persists it on the run record, so\n * this survives backgrounding and identifies the launching session afterwards.\n */\nexport const PI_SESSION_ENV = 'PI_SESSION_ID';\n\n/** Operator override for the per-session launch ceiling. */\nconst MAX_CONCURRENT_ENV = 'WORKFLOW_MCP_MAX_CONCURRENT';\nconst DEFAULT_MAX_CONCURRENT = 5;\n\nexport interface WorkflowToolDetails {\n tool: string;\n}\n\n/** Resolve the per-session launch ceiling from the environment. */\nexport function resolveMaxConcurrent(env: NodeJS.ProcessEnv = process.env): number {\n const parsed = Number(env[MAX_CONCURRENT_ENV]);\n return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_CONCURRENT;\n}\n\n/**\n * True when this session launched the run.\n *\n * Fails closed on a missing session id: an unstamped record (a CLI launch) and\n * a call arriving before the session id is known both match nothing, so no run\n * ever leaks across sessions through an undefined-equals-undefined comparison.\n */\nexport function isSessionRun(record: WorkflowRunRecord, sessionId: string | undefined): boolean {\n return sessionId !== undefined && record.env?.[PI_SESSION_ENV] === sessionId;\n}\n\n/** Runs launched by one Pi session that are still running. */\nexport function runsForSession(records: WorkflowRunRecord[], sessionId: string): WorkflowRunRecord[] {\n return records.filter((record) => isSessionRun(record, sessionId) && record.stage === 'running');\n}\n\n/**\n * What the caller is told when the registry, not the launcher, acknowledged a\n * launch.\n *\n * Leads with the run key because it is the handle every other workflow action\n * takes, and it is no longer available any other way: the launcher's own output\n * is not waited for, so nothing else in this result carries it.\n */\nexport function launchedRunSummary(record: WorkflowRunRecord): string {\n return [\n `Started ${record.displayName} in workspace ${record.workspace}.`,\n `Run key: ${record.runKey}`,\n 'The run is registered and going. This call returned at that point rather than waiting for the launcher process to exit.',\n ].join('\\n');\n}\n\n/**\n * What the caller is told when the launch was handed off but no run has\n * registered inside the acknowledgement budget.\n *\n * Deliberately not an error: the launcher is slow rather than broken often\n * enough that failing here would strand runs that go on to start normally.\n * There is no run key to offer yet, so it points at the surfaces that do not\n * need one.\n */\nexport function launchHandoffSummary(workflowPath: string): string {\n return [\n `Launch handed off for ${workflowPath}, but no run has registered yet.`,\n 'Nothing was cancelled: the launcher is still starting, and a run that registers later still reports back here when it finishes.',\n 'Do not launch it again. Ask the user to check the active workflow list if they need to see it sooner.',\n ].join('\\n');\n}\n\n/**\n * Escape sequences a terminal consumes rather than prints.\n *\n * Built from a string with the rule silenced, the way `doom-runner`'s own\n * scrubber does it, because matching the escape byte is the entire job. Not\n * imported from there: that package's published build is a stale artifact\n * missing the export, and a launch notice should not need another package's\n * build to be current.\n */\n// oxlint-disable-next-line no-control-regex -- matching terminal control bytes is the point\nconst ESCAPE_SEQUENCE = new RegExp(\n ['\\\\u001B\\\\[[0-9;?]*[ -/]*[@-~]', '\\\\u001B\\\\][^\\\\u0007\\\\u001B]*(?:\\\\u0007|\\\\u001B\\\\\\\\)', '\\\\u001B[@-Z\\\\\\\\-_]'].join(\n '|',\n ),\n 'g',\n);\n\n/** Rows made only of box-drawing: separators that carry nothing once trimmed. */\nconst RULE_ROW = /^[\\s─-╿]*$/u;\nconst NOTICE_LINE_LIMIT = 3;\n/**\n * Widest a notice row may be before it is clipped.\n *\n * A three-line cap does not bound a toast on its own: the engine announces its\n * delegation by echoing the whole shell command, temp script path included, and\n * one line of that wraps across three rows of a narrow terminal and shoves the\n * transcript around. Clipped at a width every terminal can hold instead.\n */\nconst NOTICE_WIDTH_LIMIT = 100;\n\n/**\n * Cut a tool result down to something a toast can hold.\n *\n * The launch result used to reach `ui.notify` whole, and for a workflow that\n * executes in this process that result is the engine's entire console log:\n * banner, run directory, job tree, rules, colour. Pi rendered every line of it\n * over the transcript, which reads as a broken screen rather than as output.\n * Escape sequences go first: the log carries colour the notification would\n * render as literal text, and a stray clear or cursor-move would do to the\n * screen exactly what this function exists to prevent.\n */\nexport function launchNotice(text: string, limit: number = NOTICE_LINE_LIMIT): string {\n const lines = text\n .replace(ESCAPE_SEQUENCE, '')\n .split('\\n')\n .map((line) => line.trimEnd())\n .filter((line) => line.trim().length > 0 && !RULE_ROW.test(line))\n .map((line) => (line.length > NOTICE_WIDTH_LIMIT ? `${line.slice(0, NOTICE_WIDTH_LIMIT - 1)}…` : line));\n if (lines.length === 0) return 'Workflow started.';\n if (lines.length <= limit) return lines.join('\\n');\n return [...lines.slice(0, limit), `… ${lines.length - limit} more lines, in the run's own output.`].join('\\n');\n}\n\n/** Flatten MCP tool content to text for Pi. */\nexport function toolResultText(result: CallToolResult): string {\n return result.content\n .map((part) => (part.type === 'text' ? part.text : ''))\n .filter(Boolean)\n .join('\\n')\n .trim();\n}\n\n/**\n * Convert an MCP tool result into a Pi tool result.\n *\n * Pi signals tool failure by throwing: the agent loop catches it and builds the\n * error result. So an `isError` MCP result becomes a thrown error rather than a\n * successful result carrying a flag Pi would ignore.\n */\nexport function toAgentToolResult(\n tool: string,\n result: CallToolResult,\n errorOptions: string[] = [],\n): AgentToolResult<WorkflowToolDetails> {\n const text = toolResultText(result) || 'No output.';\n if (result.isError) throw new Error(withOptions(text, errorOptions));\n return { content: [{ type: 'text', text }], details: { tool } };\n}\n\n/**\n * Attach a short menu of next steps to a problem.\n *\n * An error with no route forward is where an agent invents one: retrying a\n * capacity limit, relaunching a run that should be recovered, or silently\n * giving up on work the user is waiting for. Naming two or three real options\n * turns an error into a decision, and the decision belongs to the user.\n */\nexport function withOptions(problem: string, options: string[]): string {\n if (options.length === 0) return problem;\n return [problem, '', 'Options, put these to the user rather than picking one yourself:', ...options.map(bullet)].join(\n '\\n',\n );\n}\n\nfunction bullet(option: string): string {\n return `- ${option}`;\n}\n\n/** The job and step a run died on, preferring the progress log to the record. */\nfunction failurePosition(\n record: WorkflowRunRecord,\n jobs: WorkflowProgressJob[],\n): { job?: string; step?: string; reason?: string } {\n // The progress log knows which step was in flight; the record only names the\n // job. Falling back keeps this working for runs that predate the log.\n const failedJob = jobs.find((job) => job.status === 'failed');\n const failedStep = failedJob?.steps.find((step) => step.status === 'failed');\n return {\n job: failedJob?.name ?? record.failedJob,\n step: failedStep?.name,\n reason: failedStep?.reason ?? failedJob?.reason,\n };\n}\n\n/**\n * Describe a finished run for the agent that launched it.\n *\n * Carries the job identifiers back so a dispatching agent can match the run to\n * the work item it launched it for without another lookup. When the run failed,\n * carries the failing step and a set of options too: the agent is being told\n * about this out of band, with no user question in flight, so without them it\n * has to guess whether to recover, relaunch, or report.\n */\nexport function finishedRunSummary(record: WorkflowRunRecord, jobs: WorkflowProgressJob[] = []): string {\n const outcome = record.stage === 'completed' ? 'completed' : `ended in ${record.stage}`;\n const failure = failurePosition(record, jobs);\n const lines: Array<string | undefined> = [\n `Workflow run ${record.runKey} in workspace ${record.workspace} ${outcome}.`,\n record.workflowId ? `Workflow: ${record.workflowId}` : undefined,\n failure.job ? `Failed job: ${failure.job}${failure.step ? ` (step: ${failure.step})` : ''}` : undefined,\n record.errorMessage ? `Error: ${record.errorMessage}` : undefined,\n failure.reason && failure.reason !== record.errorMessage ? `Step reported: ${failure.reason}` : undefined,\n ];\n const jobId = record.env?.AGIFLOW_JOB_ID;\n if (jobId) lines.push(`Agiflow job: ${record.env?.AGIFLOW_JOB_KIND ?? 'unknown'} ${jobId}`);\n\n const summary = lines.filter((line): line is string => Boolean(line)).join('\\n');\n if (record.stage === 'completed') return summary;\n\n // A run the user stopped is not a defect, so it gets no diagnosis step: the\n // question is only whether to pick it back up.\n const stopped = record.outcome === 'interrupted';\n if (stopped) {\n return withOptions(summary, [\n `workflow_run {\"action\":\"recover\",\"runKey\":${JSON.stringify(record.runKey)}}: pick it up where it stopped.`,\n 'Leave it stopped and move on.',\n ]);\n }\n\n const diagnosticText = [record.errorMessage, failure.reason].filter(Boolean).join('\\n');\n if (diagnosticText.includes('JOB_ALREADY_CLAIMED')) {\n return withOptions(summary, [\n 'Report contention: another worker owns this job. Do not unlock, release, or retry the claim automatically.',\n ]);\n }\n if (diagnosticText.includes('WORKFLOW_NOT_OWNED') || /release failed/i.test(diagnosticText)) {\n return withOptions(summary, [\n 'Inspect the current workflow ownership and ask the user before any release or unlock. Never force another worker’s lock.',\n ]);\n }\n if (/running workflow not found/i.test(diagnosticText)) {\n return withOptions(summary, [\n `workflow_run {\"action\":\"status\",\"runKey\":${JSON.stringify(record.runKey)}}: re-check current state before retrying.`,\n 'Report the recorded terminal state when no running owner remains.',\n ]);\n }\n\n return withOptions(summary, [\n `workflow_run {\"action\":\"tail\",\"runKey\":${JSON.stringify(record.runKey)}}: read the run's own output first, when the error above does not explain the failure.`,\n `workflow_run {\"action\":\"recover\",\"runKey\":${JSON.stringify(record.runKey)}}: resume from the failed job, keeping the work already done. Fits a transient or external cause.`,\n 'Report the failure and stop, when it needs a code change or a decision you cannot make.',\n ]);\n}\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- function e(e,t){return e!==void 0&&t!==void 0&&e.size===t.size&&e.modifiedAt===t.modifiedAt}function t(t){let n=new Map,r=r=>{let i=t.stamp(r),a=n.get(r);if(a&&e(a.stamp,i))return a.detail;let o=t.summarize(r);return i!==void 0&&n.set(r,{stamp:i,detail:o}),o};return{async read(e){return(await t.list(e)).map(e=>{let t=r(e.path);return{path:e.path,relativePath:e.relativePath,name:e.name===``?e.relativePath:e.name,description:e.description,tags:e.tags,triggers:t.triggers,inputs:t.inputs,jobs:t.jobs,artifacts:t.artifacts,...t.runners===void 0?{}:{runners:t.runners},...t.error===void 0?{}:{error:t.error}}})},forget(e){for(let t of n.keys())e.has(t)||n.delete(t)}}}function n(e){return[...e].sort((e,t)=>e.error===void 0==(t.error===void 0)?e.name.localeCompare(t.name):e.error===void 0?-1:1)}exports.createWorkflowCatalogReader=t,exports.presentWorkflowCatalog=n;
2
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/services/webWorkflowCatalog/index.ts"],"sourcesContent":["import type {\n WorkflowCatalogArtifactView,\n WorkflowCatalogEntryView,\n WorkflowCatalogInputView,\n WorkflowCatalogJobView,\n} from '../../types/webWorkflows';\n/**\n * The cockpit's view of the workflows a session can launch.\n *\n * The engine already reads a repository's workflow files and parses one of\n * them into triggers, inputs, jobs, artifacts and compatible runners, so this\n * module only shapes that answer for the wire and remembers what it read.\n *\n * WHY A CACHE:\n * A session reads the catalog for its initial projection and when its lifecycle\n * publishes a workflow update. Entries are kept per path and reused while the\n * file's size and modification time are unchanged, which is the same test a\n * build tool makes and costs one stat per file.\n */\n\n/** What the engine reports for one workflow file before it is parsed. */\nexport interface CatalogListEntry {\n path: string;\n relativePath: string;\n name: string;\n description: string;\n tags: string[];\n}\n\n/** One file's parsed detail, or the reason it could not be read. */\nexport interface CatalogDetail {\n triggers: string[];\n inputs: WorkflowCatalogInputView[];\n jobs: WorkflowCatalogJobView[];\n artifacts: WorkflowCatalogArtifactView[];\n runners?: string[];\n error?: string;\n}\n\n/** What a file looked like when it was last parsed. */\nexport interface CatalogFileStamp {\n size: number;\n modifiedAt: number;\n}\n\nexport interface WorkflowCatalogReaderDeps {\n /** Every workflow file under a directory. */\n list(directory: string): Promise<CatalogListEntry[]>;\n /** One file's parsed detail; never throws, so a bad file cannot empty a board. */\n summarize(path: string): CatalogDetail;\n /** Size and modification time, or undefined when the file is gone. */\n stamp(path: string): CatalogFileStamp | undefined;\n}\n\ninterface CachedEntry {\n stamp: CatalogFileStamp;\n detail: CatalogDetail;\n}\n\n/** Reads one directory's catalog, reusing what it parsed last time. */\nexport interface WorkflowCatalogReader {\n read(directory: string): Promise<WorkflowCatalogEntryView[]>;\n /** Drops what was parsed for files no longer listed, so a cache cannot grow forever. */\n forget(keep: ReadonlySet<string>): void;\n}\n\nfunction unchanged(left: CatalogFileStamp | undefined, right: CatalogFileStamp | undefined): boolean {\n return left !== undefined && right !== undefined && left.size === right.size && left.modifiedAt === right.modifiedAt;\n}\n\nexport function createWorkflowCatalogReader(deps: WorkflowCatalogReaderDeps): WorkflowCatalogReader {\n const cache = new Map<string, CachedEntry>();\n\n const detailFor = (path: string): CatalogDetail => {\n const stamp = deps.stamp(path);\n const cached = cache.get(path);\n if (cached && unchanged(cached.stamp, stamp)) return cached.detail;\n const detail = deps.summarize(path);\n // A file that vanished between listing and stat is parsed anyway, which\n // answers with its own error; it just cannot be cached against a stamp.\n if (stamp !== undefined) cache.set(path, { stamp, detail });\n return detail;\n };\n\n return {\n async read(directory) {\n const listed = await deps.list(directory);\n return listed.map((entry) => {\n const detail = detailFor(entry.path);\n return {\n path: entry.path,\n relativePath: entry.relativePath,\n name: entry.name === '' ? entry.relativePath : entry.name,\n description: entry.description,\n tags: entry.tags,\n triggers: detail.triggers,\n inputs: detail.inputs,\n jobs: detail.jobs,\n artifacts: detail.artifacts,\n ...(detail.runners === undefined ? {} : { runners: detail.runners }),\n ...(detail.error === undefined ? {} : { error: detail.error }),\n };\n });\n },\n forget(keep) {\n for (const path of cache.keys()) {\n if (!keep.has(path)) cache.delete(path);\n }\n },\n };\n}\n\n/** The launchable rows first, then the ones that would not parse, name ordered. */\nexport function presentWorkflowCatalog(entries: readonly WorkflowCatalogEntryView[]): WorkflowCatalogEntryView[] {\n return [...entries].sort((left, right) => {\n if ((left.error === undefined) !== (right.error === undefined)) return left.error === undefined ? -1 : 1;\n return left.name.localeCompare(right.name);\n });\n}\n"],"mappings":"AAkEA,SAAS,EAAU,EAAoC,EAA8C,CACnG,OAAO,IAAS,IAAA,IAAa,IAAU,IAAA,IAAa,EAAK,OAAS,EAAM,MAAQ,EAAK,aAAe,EAAM,UAC5G,CAEA,SAAgB,EAA4B,EAAwD,CAClG,IAAM,EAAQ,IAAI,IAEZ,EAAa,GAAgC,CACjD,IAAM,EAAQ,EAAK,MAAM,CAAI,EACvB,EAAS,EAAM,IAAI,CAAI,EAC7B,GAAI,GAAU,EAAU,EAAO,MAAO,CAAK,EAAG,OAAO,EAAO,OAC5D,IAAM,EAAS,EAAK,UAAU,CAAI,EAIlC,OADI,IAAU,IAAA,IAAW,EAAM,IAAI,EAAM,CAAE,QAAO,QAAO,CAAC,EACnD,CACT,EAEA,MAAO,CACL,MAAM,KAAK,EAAW,CAEpB,OAAO,MADc,EAAK,KAAK,CAAS,EAAA,CAC1B,IAAK,GAAU,CAC3B,IAAM,EAAS,EAAU,EAAM,IAAI,EACnC,MAAO,CACL,KAAM,EAAM,KACZ,aAAc,EAAM,aACpB,KAAM,EAAM,OAAS,GAAK,EAAM,aAAe,EAAM,KACrD,YAAa,EAAM,YACnB,KAAM,EAAM,KACZ,SAAU,EAAO,SACjB,OAAQ,EAAO,OACf,KAAM,EAAO,KACb,UAAW,EAAO,UAClB,GAAI,EAAO,UAAY,IAAA,GAAY,CAAC,EAAI,CAAE,QAAS,EAAO,OAAQ,EAClE,GAAI,EAAO,QAAU,IAAA,GAAY,CAAC,EAAI,CAAE,MAAO,EAAO,KAAM,CAC9D,CACF,CAAC,CACH,EACA,OAAO,EAAM,CACX,IAAK,IAAM,KAAQ,EAAM,KAAK,EACvB,EAAK,IAAI,CAAI,GAAG,EAAM,OAAO,CAAI,CAE1C,CACF,CACF,CAGA,SAAgB,EAAuB,EAA0E,CAC/G,MAAO,CAAC,GAAG,CAAO,CAAC,CAAC,MAAM,EAAM,IACzB,EAAK,QAAU,IAAA,KAAgB,EAAM,QAAU,IAAA,IAC7C,EAAK,KAAK,cAAc,EAAM,IAAI,EAD8B,EAAK,QAAU,IAAA,GAAY,GAAK,CAExG,CACH"}
@@ -1,2 +0,0 @@
1
- function e(e,t){return e!==void 0&&t!==void 0&&e.size===t.size&&e.modifiedAt===t.modifiedAt}function t(t){let n=new Map,r=r=>{let i=t.stamp(r),a=n.get(r);if(a&&e(a.stamp,i))return a.detail;let o=t.summarize(r);return i!==void 0&&n.set(r,{stamp:i,detail:o}),o};return{async read(e){return(await t.list(e)).map(e=>{let t=r(e.path);return{path:e.path,relativePath:e.relativePath,name:e.name===``?e.relativePath:e.name,description:e.description,tags:e.tags,triggers:t.triggers,inputs:t.inputs,jobs:t.jobs,artifacts:t.artifacts,...t.runners===void 0?{}:{runners:t.runners},...t.error===void 0?{}:{error:t.error}}})},forget(e){for(let t of n.keys())e.has(t)||n.delete(t)}}}function n(e){return[...e].sort((e,t)=>e.error===void 0==(t.error===void 0)?e.name.localeCompare(t.name):e.error===void 0?-1:1)}export{t as createWorkflowCatalogReader,n as presentWorkflowCatalog};
2
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/webWorkflowCatalog/index.ts"],"sourcesContent":["import type {\n WorkflowCatalogArtifactView,\n WorkflowCatalogEntryView,\n WorkflowCatalogInputView,\n WorkflowCatalogJobView,\n} from '../../types/webWorkflows';\n/**\n * The cockpit's view of the workflows a session can launch.\n *\n * The engine already reads a repository's workflow files and parses one of\n * them into triggers, inputs, jobs, artifacts and compatible runners, so this\n * module only shapes that answer for the wire and remembers what it read.\n *\n * WHY A CACHE:\n * A session reads the catalog for its initial projection and when its lifecycle\n * publishes a workflow update. Entries are kept per path and reused while the\n * file's size and modification time are unchanged, which is the same test a\n * build tool makes and costs one stat per file.\n */\n\n/** What the engine reports for one workflow file before it is parsed. */\nexport interface CatalogListEntry {\n path: string;\n relativePath: string;\n name: string;\n description: string;\n tags: string[];\n}\n\n/** One file's parsed detail, or the reason it could not be read. */\nexport interface CatalogDetail {\n triggers: string[];\n inputs: WorkflowCatalogInputView[];\n jobs: WorkflowCatalogJobView[];\n artifacts: WorkflowCatalogArtifactView[];\n runners?: string[];\n error?: string;\n}\n\n/** What a file looked like when it was last parsed. */\nexport interface CatalogFileStamp {\n size: number;\n modifiedAt: number;\n}\n\nexport interface WorkflowCatalogReaderDeps {\n /** Every workflow file under a directory. */\n list(directory: string): Promise<CatalogListEntry[]>;\n /** One file's parsed detail; never throws, so a bad file cannot empty a board. */\n summarize(path: string): CatalogDetail;\n /** Size and modification time, or undefined when the file is gone. */\n stamp(path: string): CatalogFileStamp | undefined;\n}\n\ninterface CachedEntry {\n stamp: CatalogFileStamp;\n detail: CatalogDetail;\n}\n\n/** Reads one directory's catalog, reusing what it parsed last time. */\nexport interface WorkflowCatalogReader {\n read(directory: string): Promise<WorkflowCatalogEntryView[]>;\n /** Drops what was parsed for files no longer listed, so a cache cannot grow forever. */\n forget(keep: ReadonlySet<string>): void;\n}\n\nfunction unchanged(left: CatalogFileStamp | undefined, right: CatalogFileStamp | undefined): boolean {\n return left !== undefined && right !== undefined && left.size === right.size && left.modifiedAt === right.modifiedAt;\n}\n\nexport function createWorkflowCatalogReader(deps: WorkflowCatalogReaderDeps): WorkflowCatalogReader {\n const cache = new Map<string, CachedEntry>();\n\n const detailFor = (path: string): CatalogDetail => {\n const stamp = deps.stamp(path);\n const cached = cache.get(path);\n if (cached && unchanged(cached.stamp, stamp)) return cached.detail;\n const detail = deps.summarize(path);\n // A file that vanished between listing and stat is parsed anyway, which\n // answers with its own error; it just cannot be cached against a stamp.\n if (stamp !== undefined) cache.set(path, { stamp, detail });\n return detail;\n };\n\n return {\n async read(directory) {\n const listed = await deps.list(directory);\n return listed.map((entry) => {\n const detail = detailFor(entry.path);\n return {\n path: entry.path,\n relativePath: entry.relativePath,\n name: entry.name === '' ? entry.relativePath : entry.name,\n description: entry.description,\n tags: entry.tags,\n triggers: detail.triggers,\n inputs: detail.inputs,\n jobs: detail.jobs,\n artifacts: detail.artifacts,\n ...(detail.runners === undefined ? {} : { runners: detail.runners }),\n ...(detail.error === undefined ? {} : { error: detail.error }),\n };\n });\n },\n forget(keep) {\n for (const path of cache.keys()) {\n if (!keep.has(path)) cache.delete(path);\n }\n },\n };\n}\n\n/** The launchable rows first, then the ones that would not parse, name ordered. */\nexport function presentWorkflowCatalog(entries: readonly WorkflowCatalogEntryView[]): WorkflowCatalogEntryView[] {\n return [...entries].sort((left, right) => {\n if ((left.error === undefined) !== (right.error === undefined)) return left.error === undefined ? -1 : 1;\n return left.name.localeCompare(right.name);\n });\n}\n"],"mappings":"AAkEA,SAAS,EAAU,EAAoC,EAA8C,CACnG,OAAO,IAAS,IAAA,IAAa,IAAU,IAAA,IAAa,EAAK,OAAS,EAAM,MAAQ,EAAK,aAAe,EAAM,UAC5G,CAEA,SAAgB,EAA4B,EAAwD,CAClG,IAAM,EAAQ,IAAI,IAEZ,EAAa,GAAgC,CACjD,IAAM,EAAQ,EAAK,MAAM,CAAI,EACvB,EAAS,EAAM,IAAI,CAAI,EAC7B,GAAI,GAAU,EAAU,EAAO,MAAO,CAAK,EAAG,OAAO,EAAO,OAC5D,IAAM,EAAS,EAAK,UAAU,CAAI,EAIlC,OADI,IAAU,IAAA,IAAW,EAAM,IAAI,EAAM,CAAE,QAAO,QAAO,CAAC,EACnD,CACT,EAEA,MAAO,CACL,MAAM,KAAK,EAAW,CAEpB,OAAO,MADc,EAAK,KAAK,CAAS,EAAA,CAC1B,IAAK,GAAU,CAC3B,IAAM,EAAS,EAAU,EAAM,IAAI,EACnC,MAAO,CACL,KAAM,EAAM,KACZ,aAAc,EAAM,aACpB,KAAM,EAAM,OAAS,GAAK,EAAM,aAAe,EAAM,KACrD,YAAa,EAAM,YACnB,KAAM,EAAM,KACZ,SAAU,EAAO,SACjB,OAAQ,EAAO,OACf,KAAM,EAAO,KACb,UAAW,EAAO,UAClB,GAAI,EAAO,UAAY,IAAA,GAAY,CAAC,EAAI,CAAE,QAAS,EAAO,OAAQ,EAClE,GAAI,EAAO,QAAU,IAAA,GAAY,CAAC,EAAI,CAAE,MAAO,EAAO,KAAM,CAC9D,CACF,CAAC,CACH,EACA,OAAO,EAAM,CACX,IAAK,IAAM,KAAQ,EAAM,KAAK,EACvB,EAAK,IAAI,CAAI,GAAG,EAAM,OAAO,CAAI,CAE1C,CACF,CACF,CAGA,SAAgB,EAAuB,EAA0E,CAC/G,MAAO,CAAC,GAAG,CAAO,CAAC,CAAC,MAAM,EAAM,IACzB,EAAK,QAAU,IAAA,KAAgB,EAAM,QAAU,IAAA,IAC7C,EAAK,KAAK,cAAc,EAAM,IAAI,EAD8B,EAAK,QAAU,IAAA,GAAY,GAAK,CAExG,CACH"}
@@ -1,2 +0,0 @@
1
- let e=require("node:fs"),t=require("@agimon-ai/workflow-mcp");function n(){return{list:e=>(0,t.readWorkflowCatalog)(e),summarize:e=>(0,t.summarizeWorkflow)(e),stamp:t=>{try{let n=(0,e.statSync)(t);return{size:n.size,modifiedAt:n.mtimeMs}}catch{return}}}}exports.defaultCatalogDeps=n;
2
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","names":["readWorkflowCatalog","summarizeWorkflow","statSync"],"sources":["../../../../src/services/workflowCatalogDeps/index.ts"],"sourcesContent":["import { statSync } from 'node:fs';\n\nimport { readWorkflowCatalog, summarizeWorkflow } from '@agimon-ai/workflow-mcp';\n\nimport type { WorkflowCatalogReaderDeps } from '../webWorkflowCatalog';\n\n/** Connect the pure catalog projection service to the workflow engine and filesystem. */\nexport function defaultCatalogDeps(): WorkflowCatalogReaderDeps {\n return {\n list: (directory) => readWorkflowCatalog(directory),\n summarize: (workflowPath) => summarizeWorkflow(workflowPath),\n stamp: (workflowPath) => {\n try {\n const stats = statSync(workflowPath);\n return { size: stats.size, modifiedAt: stats.mtimeMs };\n } catch {\n return undefined;\n }\n },\n };\n}\n"],"mappings":"8DAOA,SAAgB,GAAgD,CAC9D,MAAO,CACL,KAAO,IAAA,EAAcA,EAAAA,oBAAAA,CAAoB,CAAS,EAClD,UAAY,IAAA,EAAiBC,EAAAA,kBAAAA,CAAkB,CAAY,EAC3D,MAAQ,GAAiB,CACvB,GAAI,CACF,IAAM,GAAA,EAAQC,EAAAA,SAAAA,CAAS,CAAY,EACnC,MAAO,CAAE,KAAM,EAAM,KAAM,WAAY,EAAM,OAAQ,CACvD,MAAQ,CACN,MACF,CACF,CACF,CACF"}
@@ -1,2 +0,0 @@
1
- import{statSync as e}from"node:fs";import{readWorkflowCatalog as t,summarizeWorkflow as n}from"@agimon-ai/workflow-mcp";function r(){return{list:e=>t(e),summarize:e=>n(e),stamp:t=>{try{let n=e(t);return{size:n.size,modifiedAt:n.mtimeMs}}catch{return}}}}export{r as defaultCatalogDeps};
2
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/workflowCatalogDeps/index.ts"],"sourcesContent":["import { statSync } from 'node:fs';\n\nimport { readWorkflowCatalog, summarizeWorkflow } from '@agimon-ai/workflow-mcp';\n\nimport type { WorkflowCatalogReaderDeps } from '../webWorkflowCatalog';\n\n/** Connect the pure catalog projection service to the workflow engine and filesystem. */\nexport function defaultCatalogDeps(): WorkflowCatalogReaderDeps {\n return {\n list: (directory) => readWorkflowCatalog(directory),\n summarize: (workflowPath) => summarizeWorkflow(workflowPath),\n stamp: (workflowPath) => {\n try {\n const stats = statSync(workflowPath);\n return { size: stats.size, modifiedAt: stats.mtimeMs };\n } catch {\n return undefined;\n }\n },\n };\n}\n"],"mappings":"wHAOA,SAAgB,GAAgD,CAC9D,MAAO,CACL,KAAO,GAAc,EAAoB,CAAS,EAClD,UAAY,GAAiB,EAAkB,CAAY,EAC3D,MAAQ,GAAiB,CACvB,GAAI,CACF,IAAM,EAAQ,EAAS,CAAY,EACnC,MAAO,CAAE,KAAM,EAAM,KAAM,WAAY,EAAM,OAAQ,CACvD,MAAQ,CACN,MACF,CACF,CACF,CACF"}
@@ -1,2 +0,0 @@
1
- const e=require("../../constants/workflow.cjs"),t=require("../piToolBridge/index.cjs");let n=require("@agimon-ai/doompi-core/child-process");const r=`AGIFLOW_PROJECT_ID`;function i(t){let n=new Set(e.WORKFLOW_PI_TOOL_NAMES);return e=>t?e:e.filter(e=>!n.has(e))}function a(e,t,n){e?.({content:[{type:`text`,text:n}],details:{tool:t}})}function o(e,t){return{...e,content:[...e.content,{type:`text`,text:t}]}}function s(e){return`workflow_run {"action":"status","runKey":${JSON.stringify(e)}}`}function c(e,t){let n=s(t);return e===`stop`?[`Next: call ${n} to confirm the run reached a terminal stage.`]:e===`recover`?[`Report the recovery failure and do not retry it blindly: call ${n} first.`,`Ask the user before launching fresh work when the recorded recovery is no longer valid.`]:[`Next: call ${n} to verify the recorded execution state.`]}const l=[`Report the failure to the user with the error above. A launch failure is usually a bad workflow path, a missing launcher, or a workflow whose own pre-conditions refused.`,`list_workflows: confirm the workflow path exists and its description matches the work, if the path may be wrong.`,`workflow_run with action status: check whether an earlier attempt is already running before launching again.`];function u(e){return e instanceof Error?e:Error(String(e),{cause:e})}function d(e){return new Promise(t=>{setTimeout(()=>t(void 0),e).unref?.()})}async function f(e,t,n){let r=t.findLaunchedRun;if(!r)return{kind:`settled`,outcome:await e};let i=t.launchAckPollMs??500,a=Date.now()+(t.launchAckTimeoutMs??15e3);for(;;){let t=await Promise.race([e,d(i)]);if(t)return{kind:`settled`,outcome:t};let o=await r(n).catch(()=>void 0);if(o)return{kind:`record`,record:o};if(Date.now()>=a)return{kind:`handoff`}}}function p(e,n,r){e.then(e=>{if(e.kind===`error`){n.onLateFailure?.(u(e.error),r);return}e.result.isError&&n.onLateFailure?.(Error(t.toolResultText(e.result)||`Workflow launch failed.`),r)})}function m(i){return{async execute(o,s,c){let d=(0,n.resolveRootSessionId)(s.sessionManager.getSessionId(),i.environment);i.observeSession?.(d);let m=o.env?.AGIFLOW_JOB_KIND?.trim(),h=o.env?.AGIFLOW_JOB_ID?.trim(),g=o.env?.[r]?.trim();if(!!m!=!!h)throw Error(`Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.`);if(m&&m!==`task`&&m!==`work-unit`)throw Error(`Agiflow workflow launches require AGIFLOW_JOB_KIND to be task or work-unit.`);if(m&&!g)throw Error(`Agiflow workflow launches require AGIFLOW_PROJECT_ID with the job identity.`);if(m&&!o.prompt?.trim())throw Error(`Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.`);a(c,e.LAUNCH_WORKFLOW_TOOL_NAME,`Checking workflow capacity...`);let _=t.resolveMaxConcurrent(i.environment),v=await i.activeRunCount?.()??0;if(v>=_)throw Error(t.withOptions(`This session is at capacity: ${v}/${_} workflows running.`,[`Wait for a running workflow to finish, then launch again. The extension reports each one as it ends.`,`workflow_run with action status: show what is running, so the user can judge whether something is stuck.`,`workflow_run with action stop: free a slot by stopping a run the user no longer wants.`]));if(o.runner){let e=i.rejectRunner?.(o.workflowPath,o.runner);if(e)throw Error(e)}a(c,e.LAUNCH_WORKFLOW_TOOL_NAME,`Launching workflow ${o.workflowPath}...`);let y={...o.env},b=i.environment.AGIFLOW_DISPATCH_CONTEXT_FILE,x=i.environment[r]?.trim();if(b&&m&&x&&g&&x!==g)throw Error(`Agiflow workflow project identity conflicts with the dispatcher context (${g} versus ${x}).`);if(b)for(let e of[`AGIFLOW_ORGANIZATION_ID`,`AGIFLOW_PROJECT_ID`,`AGIFLOW_DEVICE_ID`,`BACKEND_AGIFLOW_API_ENDPOINT`,`AGIFLOW_DISPATCH_SECRET_FILE`]){let t=i.environment[e];t&&(y[e]=t)}let S=Date.now(),C=i.trackPendingRun(i.runTool.execute({...o,env:{...y,[t.PI_SESSION_ENV]:d}})).then(e=>({kind:`value`,result:e}),e=>({kind:`error`,error:e})),w=await f(C,i,{sessionId:d,since:S,workflowPath:o.workflowPath});if(w.kind===`settled`){let e=w.outcome;if(e.kind===`error`)throw u(e.error);let n=e.result;if(n.isError)throw Error(t.withOptions(t.toolResultText(n)||`Workflow launch failed.`,l));return await i.onLaunch?.(s),n}return p(C,i,s),await i.onLaunch?.(s),{content:[{type:`text`,text:w.kind===`record`?t.launchedRunSummary(w.record):t.launchHandoffSummary(o.workflowPath)}]}}}}exports.actionGuidance=c,exports.appendGuidance=o,exports.createWorkflowLaunchExecutor=m,exports.launchFailureOptions=l,exports.reportProgress=a,exports.workflowToolRestriction=i;
2
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","names":["WORKFLOW_PI_TOOL_NAMES","toolResultText","resolveRootSessionId","LAUNCH_WORKFLOW_TOOL_NAME","resolveMaxConcurrent","withOptions","PI_SESSION_ENV","launchedRunSummary","launchHandoffSummary"],"sources":["../../../../src/services/workflowExecution/index.ts"],"sourcesContent":["import { resolveRootSessionId } from '@agimon-ai/doompi-core/child-process';\nimport type { DoomToolRestriction } from '@agimon-ai/doompi-core/tool-surface';\nimport { type EmbeddedWorkflowFeature, type WorkflowRunRecord } from '@agimon-ai/workflow-mcp';\nimport type { AgentToolResult, AgentToolUpdateCallback, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod';\n\nimport { type WorkflowRunInput } from '../../schemas/workflowPi';\nimport {\n launchedRunSummary,\n launchHandoffSummary,\n PI_SESSION_ENV,\n resolveMaxConcurrent,\n toolResultText,\n withOptions,\n} from '../piToolBridge';\n\nconst AGIFLOW_JOB_ID_ENV = 'AGIFLOW_JOB_ID';\nconst AGIFLOW_JOB_KIND_ENV = 'AGIFLOW_JOB_KIND';\nconst AGIFLOW_PROJECT_ID_ENV = 'AGIFLOW_PROJECT_ID';\n\nimport { WORKFLOW_PI_TOOL_NAMES, LAUNCH_WORKFLOW_TOOL_NAME } from '../../constants/workflow';\n\n/**\n * What workflow mode does to the tool surface: nothing but hide its own tools.\n *\n * The tools are registered for the whole session because Pi has no other way to\n * add one later, so the mode is expressed as a restriction rather than as a\n * registration.\n */\nexport function workflowToolRestriction(enabled: boolean): DoomToolRestriction {\n const hidden = new Set<string>(WORKFLOW_PI_TOOL_NAMES);\n return (incoming) => (enabled ? incoming : incoming.filter((name) => !hidden.has(name)));\n}\n\nexport type WorkflowRunSelector = Pick<WorkflowRunInput, 'runKey' | 'workspace'>;\n\nexport type WorkflowLaunchInput = z.infer<ReturnType<EmbeddedWorkflowFeature['runTool']['getInputSchema']>>;\n\n/**\n * Where a launch was answered from, which decides what the caller is told.\n *\n * Tagged rather than inferred from an absent field: the failure carries an\n * `unknown`, and `unknown` includes `undefined`, so only a tag can tell a\n * rejection apart from a result.\n */\ntype LaunchOutcome = { kind: 'value'; result: CallToolResult } | { kind: 'error'; error: unknown };\ntype LaunchAck =\n | { kind: 'settled'; outcome: LaunchOutcome }\n | { kind: 'record'; record: WorkflowRunRecord }\n | { kind: 'handoff' };\n\n/** How often the registry is asked whether the launch has produced a run. */\nconst LAUNCH_ACK_POLL_MS = 500;\n/**\n * How long to wait for that before answering without a run key.\n *\n * Long enough to cover a launcher that is merely slow, short enough that a\n * caller is never left holding a turn open for a launcher that will not answer.\n */\nconst LAUNCH_ACK_TIMEOUT_MS = 15_000;\n\nexport interface LaunchedRunQuery {\n sessionId: string | undefined;\n /** Epoch milliseconds the launch began, so an earlier run cannot match. */\n since: number;\n workflowPath: string;\n}\n\nexport interface WorkflowLaunchExecutorDependencies {\n readonly environment: Readonly<Record<string, string | undefined>>;\n activeRunCount?: () => Promise<number>;\n onLaunch?: (ctx: ExtensionContext) => Promise<void> | void;\n observeSession?: (sessionId: string | undefined) => void;\n rejectRunner?: (workflowPath: string, runner: string) => string | undefined;\n runTool: EmbeddedWorkflowFeature['runTool'];\n trackPendingRun: <T>(run: Promise<T>) => Promise<T>;\n /**\n * The run this launch registered, or undefined while none has appeared.\n *\n * Its presence is what lets a launch answer on the registry instead of on the\n * launcher process exiting. Left out, the launch waits for the process, which\n * is the behaviour every caller had before.\n */\n findLaunchedRun?: (query: LaunchedRunQuery) => Promise<WorkflowRunRecord | undefined>;\n /** Report a launch that fails after this call already answered \"started\". */\n onLateFailure?: (error: unknown, ctx: ExtensionContext) => void;\n launchAckPollMs?: number;\n launchAckTimeoutMs?: number;\n}\n\nexport interface WorkflowLaunchExecutor {\n execute(\n input: WorkflowLaunchInput,\n ctx: ExtensionContext,\n onUpdate?: AgentToolUpdateCallback<{ tool: string }>,\n ): Promise<CallToolResult>;\n}\n\nexport interface WorkflowPiToolDependencies {\n readonly environment: Readonly<Record<string, string | undefined>>;\n launchExecutor?: WorkflowLaunchExecutor;\n onLaunch?: (ctx: ExtensionContext) => Promise<void> | void;\n activeRunCount?: () => Promise<number>;\n followRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n tailRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n openRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n rejectRunner?: (workflowPath: string, runner: string) => string | undefined;\n requireSessionRun?: (input: WorkflowRunSelector, sessionId: string | undefined) => Promise<WorkflowRunRecord>;\n /**\n * Resolve a failed recovery target globally.\n *\n * Recovery is the one ownership-transferring action: a replacement Pi\n * session must be able to adopt a terminal run whose launching session is\n * gone. Every non-recovery action remains session-scoped.\n */\n requireRecoverableRun?: (input: WorkflowRunSelector) => Promise<WorkflowRunRecord>;\n readRecoveryEvidence?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n /**\n * Hand a recovery to a terminal launcher, returning what to tell the caller.\n *\n * Undefined means this recovery cannot be delegated, and the in-process\n * replay below is used instead.\n */\n delegateRecovery?: (\n input: { runKey: string; workspace?: string; runner?: string },\n ctx: ExtensionContext,\n ) => Promise<string | undefined>;\n observeSession?: (sessionId: string | undefined) => void;\n trackPendingRun?: <T>(run: Promise<T>) => Promise<T>;\n feature?: EmbeddedWorkflowFeature;\n recoverTool?: {\n execute(input: {\n dryRun?: boolean;\n job?: string;\n runKey: string;\n runner?: string;\n workspace?: string;\n }): Promise<CallToolResult>;\n };\n controlTool?: ReturnType<EmbeddedWorkflowFeature['createControlTool']>;\n runTool?: EmbeddedWorkflowFeature['runTool'];\n listWorkflowsTool?: EmbeddedWorkflowFeature['listWorkflowsTool'];\n}\n\nexport function reportProgress(\n onUpdate: AgentToolUpdateCallback<{ tool: string }> | undefined,\n tool: string,\n message: string,\n): void {\n onUpdate?.({ content: [{ type: 'text', text: message }], details: { tool } });\n}\n\nexport function appendGuidance(\n result: AgentToolResult<{ tool: string }>,\n guidance: string,\n): AgentToolResult<{ tool: string }> {\n return { ...result, content: [...result.content, { type: 'text', text: guidance }] };\n}\n\nfunction statusAction(runKey: string): string {\n return `workflow_run {\"action\":\"status\",\"runKey\":${JSON.stringify(runKey)}}`;\n}\n\nexport function actionGuidance(action: WorkflowRunInput['action'], runKey: string): string[] {\n const status = statusAction(runKey);\n if (action === 'stop') return [`Next: call ${status} to confirm the run reached a terminal stage.`];\n if (action === 'recover') {\n return [\n `Report the recovery failure and do not retry it blindly: call ${status} first.`,\n 'Ask the user before launching fresh work when the recorded recovery is no longer valid.',\n ];\n }\n return [`Next: call ${status} to verify the recorded execution state.`];\n}\n\nexport const launchFailureOptions = [\n 'Report the failure to the user with the error above. A launch failure is usually a bad workflow path, a missing launcher, or a workflow whose own pre-conditions refused.',\n 'list_workflows: confirm the workflow path exists and its description matches the work, if the path may be wrong.',\n 'workflow_run with action status: check whether an earlier attempt is already running before launching again.',\n];\n\n/**\n * An Error, whatever the launch rejected with.\n *\n * A rejection reaches here as `unknown` because it crossed a promise boundary,\n * and rethrowing that as-is hands Pi's tool layer something it cannot read a\n * message or a stack off. The original is kept as the cause rather than\n * flattened into a string.\n */\nfunction asError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value), { cause: value });\n}\n\n/** Resolves to undefined, so a race against it reads as \"nothing settled yet\". */\nfunction sleep(ms: number): Promise<undefined> {\n return new Promise((settle) => {\n const timer = setTimeout(() => settle(undefined), ms);\n timer.unref?.();\n });\n}\n\n/**\n * Wait for whichever answer arrives first: the launch settling, the run\n * appearing in the registry, or the budget running out.\n *\n * A launch that delegates to a terminal launcher settles when that launcher's\n * whole process chain closes, which on a loaded machine has been measured\n * minutes after the run itself was registered and working. The registry is the\n * earlier and more truthful signal: a run recorded there is a run that started.\n */\nasync function awaitLaunchAck(\n settled: Promise<LaunchOutcome>,\n dependencies: WorkflowLaunchExecutorDependencies,\n query: LaunchedRunQuery,\n): Promise<LaunchAck> {\n const findLaunchedRun = dependencies.findLaunchedRun;\n if (!findLaunchedRun) return { kind: 'settled', outcome: await settled };\n\n const pollMs = dependencies.launchAckPollMs ?? LAUNCH_ACK_POLL_MS;\n const deadline = Date.now() + (dependencies.launchAckTimeoutMs ?? LAUNCH_ACK_TIMEOUT_MS);\n for (;;) {\n const outcome = await Promise.race([settled, sleep(pollMs)]);\n if (outcome) return { kind: 'settled', outcome };\n // A registry read that throws must not fail the launch: the run may be\n // perfectly healthy, and the settled promise is still the fallback.\n const record = await findLaunchedRun(query).catch(() => undefined);\n if (record) return { kind: 'record', record };\n if (Date.now() >= deadline) return { kind: 'handoff' };\n }\n}\n\n/**\n * Carry a failure that lands after the caller was told the run started.\n *\n * Answering early trades the launcher's exit code for timeliness. Dropping that\n * code entirely is not part of the trade: a launch that fails a minute later\n * would otherwise leave a user waiting on a run that never existed.\n */\nfunction reportLateFailure(\n settled: Promise<LaunchOutcome>,\n dependencies: WorkflowLaunchExecutorDependencies,\n ctx: ExtensionContext,\n): void {\n void settled.then((outcome) => {\n if (outcome.kind === 'error') {\n dependencies.onLateFailure?.(asError(outcome.error), ctx);\n return;\n }\n if (outcome.result.isError) {\n dependencies.onLateFailure?.(new Error(toolResultText(outcome.result) || 'Workflow launch failed.'), ctx);\n }\n });\n}\n\nexport function createWorkflowLaunchExecutor(dependencies: WorkflowLaunchExecutorDependencies): WorkflowLaunchExecutor {\n return {\n async execute(input, ctx, onUpdate) {\n const sessionId = resolveRootSessionId(ctx.sessionManager.getSessionId(), dependencies.environment);\n dependencies.observeSession?.(sessionId);\n const agiflowJobKind = input.env?.[AGIFLOW_JOB_KIND_ENV]?.trim();\n const agiflowJobId = input.env?.[AGIFLOW_JOB_ID_ENV]?.trim();\n const explicitProjectId = input.env?.[AGIFLOW_PROJECT_ID_ENV]?.trim();\n if (Boolean(agiflowJobKind) !== Boolean(agiflowJobId)) {\n throw new Error('Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.');\n }\n if (agiflowJobKind && agiflowJobKind !== 'task' && agiflowJobKind !== 'work-unit') {\n throw new Error('Agiflow workflow launches require AGIFLOW_JOB_KIND to be task or work-unit.');\n }\n if (agiflowJobKind && !explicitProjectId) {\n throw new Error('Agiflow workflow launches require AGIFLOW_PROJECT_ID with the job identity.');\n }\n if (agiflowJobKind && !input.prompt?.trim()) {\n throw new Error(\n 'Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.',\n );\n }\n reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, 'Checking workflow capacity...');\n const maxConcurrent = resolveMaxConcurrent(dependencies.environment);\n const active = (await dependencies.activeRunCount?.()) ?? 0;\n if (active >= maxConcurrent) {\n throw new Error(\n withOptions(`This session is at capacity: ${active}/${maxConcurrent} workflows running.`, [\n 'Wait for a running workflow to finish, then launch again. The extension reports each one as it ends.',\n 'workflow_run with action status: show what is running, so the user can judge whether something is stuck.',\n 'workflow_run with action stop: free a slot by stopping a run the user no longer wants.',\n ]),\n );\n }\n\n if (input.runner) {\n const rejection = dependencies.rejectRunner?.(input.workflowPath, input.runner);\n if (rejection) throw new Error(rejection);\n }\n reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, `Launching workflow ${input.workflowPath}...`);\n const workflowEnv = { ...input.env };\n const dispatcherContextFile = dependencies.environment.AGIFLOW_DISPATCH_CONTEXT_FILE;\n const dispatcherProjectId = dependencies.environment[AGIFLOW_PROJECT_ID_ENV]?.trim();\n if (\n dispatcherContextFile &&\n agiflowJobKind &&\n dispatcherProjectId &&\n explicitProjectId &&\n dispatcherProjectId !== explicitProjectId\n ) {\n throw new Error(\n `Agiflow workflow project identity conflicts with the dispatcher context (${explicitProjectId} versus ${dispatcherProjectId}).`,\n );\n }\n if (dispatcherContextFile) {\n for (const key of [\n 'AGIFLOW_ORGANIZATION_ID',\n 'AGIFLOW_PROJECT_ID',\n 'AGIFLOW_DEVICE_ID',\n 'BACKEND_AGIFLOW_API_ENDPOINT',\n 'AGIFLOW_DISPATCH_SECRET_FILE',\n ] as const) {\n const hostValue = dependencies.environment[key];\n if (hostValue) workflowEnv[key] = hostValue;\n }\n }\n const since = Date.now();\n const launch = dependencies.trackPendingRun(\n dependencies.runTool.execute({\n ...input,\n env: { ...workflowEnv, [PI_SESSION_ENV]: sessionId },\n }),\n );\n // Folded into a value before anything races it. A promise this function\n // may stop awaiting must never be able to reject unobserved, which in\n // Node ends the whole process, Pi's TUI included.\n const settled: Promise<LaunchOutcome> = launch.then(\n (value): LaunchOutcome => ({ kind: 'value', result: value }),\n (error: unknown): LaunchOutcome => ({ kind: 'error', error }),\n );\n\n const ack = await awaitLaunchAck(settled, dependencies, {\n sessionId,\n since,\n workflowPath: input.workflowPath,\n });\n\n if (ack.kind === 'settled') {\n const outcome = ack.outcome;\n if (outcome.kind === 'error') throw asError(outcome.error);\n const result = outcome.result;\n if (result.isError)\n throw new Error(withOptions(toolResultText(result) || 'Workflow launch failed.', launchFailureOptions));\n await dependencies.onLaunch?.(ctx);\n return result;\n }\n\n reportLateFailure(settled, dependencies, ctx);\n await dependencies.onLaunch?.(ctx);\n return {\n content: [\n {\n type: 'text',\n text: ack.kind === 'record' ? launchedRunSummary(ack.record) : launchHandoffSummary(input.workflowPath),\n },\n ],\n };\n },\n };\n}\n"],"mappings":"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"}
@@ -1,6 +0,0 @@
1
- import "../../schemas/workflowPi.cjs";
2
- import { EmbeddedWorkflowFeature } from "@agimon-ai/workflow-mcp";
3
- import "@earendil-works/pi-coding-agent";
4
- import "@agimon-ai/doompi-core/tool-surface";
5
- import "@modelcontextprotocol/sdk/types.js";
6
- import "zod";
@@ -1,6 +0,0 @@
1
- import "../../schemas/workflowPi.mjs";
2
- import { EmbeddedWorkflowFeature } from "@agimon-ai/workflow-mcp";
3
- import { z } from "zod";
4
- import "@earendil-works/pi-coding-agent";
5
- import "@agimon-ai/doompi-core/tool-surface";
6
- import "@modelcontextprotocol/sdk/types.js";
@@ -1,2 +0,0 @@
1
- import{LAUNCH_WORKFLOW_TOOL_NAME as e,WORKFLOW_PI_TOOL_NAMES as t}from"../../constants/workflow.mjs";import{PI_SESSION_ENV as n,launchHandoffSummary as r,launchedRunSummary as i,resolveMaxConcurrent as a,toolResultText as o,withOptions as s}from"../piToolBridge/index.mjs";import{resolveRootSessionId as c}from"@agimon-ai/doompi-core/child-process";const l=`AGIFLOW_PROJECT_ID`;function u(e){let n=new Set(t);return t=>e?t:t.filter(e=>!n.has(e))}function d(e,t,n){e?.({content:[{type:`text`,text:n}],details:{tool:t}})}function f(e,t){return{...e,content:[...e.content,{type:`text`,text:t}]}}function p(e){return`workflow_run {"action":"status","runKey":${JSON.stringify(e)}}`}function m(e,t){let n=p(t);return e===`stop`?[`Next: call ${n} to confirm the run reached a terminal stage.`]:e===`recover`?[`Report the recovery failure and do not retry it blindly: call ${n} first.`,`Ask the user before launching fresh work when the recorded recovery is no longer valid.`]:[`Next: call ${n} to verify the recorded execution state.`]}const h=[`Report the failure to the user with the error above. A launch failure is usually a bad workflow path, a missing launcher, or a workflow whose own pre-conditions refused.`,`list_workflows: confirm the workflow path exists and its description matches the work, if the path may be wrong.`,`workflow_run with action status: check whether an earlier attempt is already running before launching again.`];function g(e){return e instanceof Error?e:Error(String(e),{cause:e})}function _(e){return new Promise(t=>{setTimeout(()=>t(void 0),e).unref?.()})}async function v(e,t,n){let r=t.findLaunchedRun;if(!r)return{kind:`settled`,outcome:await e};let i=t.launchAckPollMs??500,a=Date.now()+(t.launchAckTimeoutMs??15e3);for(;;){let t=await Promise.race([e,_(i)]);if(t)return{kind:`settled`,outcome:t};let o=await r(n).catch(()=>void 0);if(o)return{kind:`record`,record:o};if(Date.now()>=a)return{kind:`handoff`}}}function y(e,t,n){e.then(e=>{if(e.kind===`error`){t.onLateFailure?.(g(e.error),n);return}e.result.isError&&t.onLateFailure?.(Error(o(e.result)||`Workflow launch failed.`),n)})}function b(t){return{async execute(u,f,p){let m=c(f.sessionManager.getSessionId(),t.environment);t.observeSession?.(m);let _=u.env?.AGIFLOW_JOB_KIND?.trim(),b=u.env?.AGIFLOW_JOB_ID?.trim(),x=u.env?.[l]?.trim();if(!!_!=!!b)throw Error(`Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.`);if(_&&_!==`task`&&_!==`work-unit`)throw Error(`Agiflow workflow launches require AGIFLOW_JOB_KIND to be task or work-unit.`);if(_&&!x)throw Error(`Agiflow workflow launches require AGIFLOW_PROJECT_ID with the job identity.`);if(_&&!u.prompt?.trim())throw Error(`Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.`);d(p,e,`Checking workflow capacity...`);let S=a(t.environment),C=await t.activeRunCount?.()??0;if(C>=S)throw Error(s(`This session is at capacity: ${C}/${S} workflows running.`,[`Wait for a running workflow to finish, then launch again. The extension reports each one as it ends.`,`workflow_run with action status: show what is running, so the user can judge whether something is stuck.`,`workflow_run with action stop: free a slot by stopping a run the user no longer wants.`]));if(u.runner){let e=t.rejectRunner?.(u.workflowPath,u.runner);if(e)throw Error(e)}d(p,e,`Launching workflow ${u.workflowPath}...`);let w={...u.env},T=t.environment.AGIFLOW_DISPATCH_CONTEXT_FILE,E=t.environment[l]?.trim();if(T&&_&&E&&x&&E!==x)throw Error(`Agiflow workflow project identity conflicts with the dispatcher context (${x} versus ${E}).`);if(T)for(let e of[`AGIFLOW_ORGANIZATION_ID`,`AGIFLOW_PROJECT_ID`,`AGIFLOW_DEVICE_ID`,`BACKEND_AGIFLOW_API_ENDPOINT`,`AGIFLOW_DISPATCH_SECRET_FILE`]){let n=t.environment[e];n&&(w[e]=n)}let D=Date.now(),O=t.trackPendingRun(t.runTool.execute({...u,env:{...w,[n]:m}})).then(e=>({kind:`value`,result:e}),e=>({kind:`error`,error:e})),k=await v(O,t,{sessionId:m,since:D,workflowPath:u.workflowPath});if(k.kind===`settled`){let e=k.outcome;if(e.kind===`error`)throw g(e.error);let n=e.result;if(n.isError)throw Error(s(o(n)||`Workflow launch failed.`,h));return await t.onLaunch?.(f),n}return y(O,t,f),await t.onLaunch?.(f),{content:[{type:`text`,text:k.kind===`record`?i(k.record):r(u.workflowPath)}]}}}}export{m as actionGuidance,f as appendGuidance,b as createWorkflowLaunchExecutor,h as launchFailureOptions,d as reportProgress,u as workflowToolRestriction};
2
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/workflowExecution/index.ts"],"sourcesContent":["import { resolveRootSessionId } from '@agimon-ai/doompi-core/child-process';\nimport type { DoomToolRestriction } from '@agimon-ai/doompi-core/tool-surface';\nimport { type EmbeddedWorkflowFeature, type WorkflowRunRecord } from '@agimon-ai/workflow-mcp';\nimport type { AgentToolResult, AgentToolUpdateCallback, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport { z } from 'zod';\n\nimport { type WorkflowRunInput } from '../../schemas/workflowPi';\nimport {\n launchedRunSummary,\n launchHandoffSummary,\n PI_SESSION_ENV,\n resolveMaxConcurrent,\n toolResultText,\n withOptions,\n} from '../piToolBridge';\n\nconst AGIFLOW_JOB_ID_ENV = 'AGIFLOW_JOB_ID';\nconst AGIFLOW_JOB_KIND_ENV = 'AGIFLOW_JOB_KIND';\nconst AGIFLOW_PROJECT_ID_ENV = 'AGIFLOW_PROJECT_ID';\n\nimport { WORKFLOW_PI_TOOL_NAMES, LAUNCH_WORKFLOW_TOOL_NAME } from '../../constants/workflow';\n\n/**\n * What workflow mode does to the tool surface: nothing but hide its own tools.\n *\n * The tools are registered for the whole session because Pi has no other way to\n * add one later, so the mode is expressed as a restriction rather than as a\n * registration.\n */\nexport function workflowToolRestriction(enabled: boolean): DoomToolRestriction {\n const hidden = new Set<string>(WORKFLOW_PI_TOOL_NAMES);\n return (incoming) => (enabled ? incoming : incoming.filter((name) => !hidden.has(name)));\n}\n\nexport type WorkflowRunSelector = Pick<WorkflowRunInput, 'runKey' | 'workspace'>;\n\nexport type WorkflowLaunchInput = z.infer<ReturnType<EmbeddedWorkflowFeature['runTool']['getInputSchema']>>;\n\n/**\n * Where a launch was answered from, which decides what the caller is told.\n *\n * Tagged rather than inferred from an absent field: the failure carries an\n * `unknown`, and `unknown` includes `undefined`, so only a tag can tell a\n * rejection apart from a result.\n */\ntype LaunchOutcome = { kind: 'value'; result: CallToolResult } | { kind: 'error'; error: unknown };\ntype LaunchAck =\n | { kind: 'settled'; outcome: LaunchOutcome }\n | { kind: 'record'; record: WorkflowRunRecord }\n | { kind: 'handoff' };\n\n/** How often the registry is asked whether the launch has produced a run. */\nconst LAUNCH_ACK_POLL_MS = 500;\n/**\n * How long to wait for that before answering without a run key.\n *\n * Long enough to cover a launcher that is merely slow, short enough that a\n * caller is never left holding a turn open for a launcher that will not answer.\n */\nconst LAUNCH_ACK_TIMEOUT_MS = 15_000;\n\nexport interface LaunchedRunQuery {\n sessionId: string | undefined;\n /** Epoch milliseconds the launch began, so an earlier run cannot match. */\n since: number;\n workflowPath: string;\n}\n\nexport interface WorkflowLaunchExecutorDependencies {\n readonly environment: Readonly<Record<string, string | undefined>>;\n activeRunCount?: () => Promise<number>;\n onLaunch?: (ctx: ExtensionContext) => Promise<void> | void;\n observeSession?: (sessionId: string | undefined) => void;\n rejectRunner?: (workflowPath: string, runner: string) => string | undefined;\n runTool: EmbeddedWorkflowFeature['runTool'];\n trackPendingRun: <T>(run: Promise<T>) => Promise<T>;\n /**\n * The run this launch registered, or undefined while none has appeared.\n *\n * Its presence is what lets a launch answer on the registry instead of on the\n * launcher process exiting. Left out, the launch waits for the process, which\n * is the behaviour every caller had before.\n */\n findLaunchedRun?: (query: LaunchedRunQuery) => Promise<WorkflowRunRecord | undefined>;\n /** Report a launch that fails after this call already answered \"started\". */\n onLateFailure?: (error: unknown, ctx: ExtensionContext) => void;\n launchAckPollMs?: number;\n launchAckTimeoutMs?: number;\n}\n\nexport interface WorkflowLaunchExecutor {\n execute(\n input: WorkflowLaunchInput,\n ctx: ExtensionContext,\n onUpdate?: AgentToolUpdateCallback<{ tool: string }>,\n ): Promise<CallToolResult>;\n}\n\nexport interface WorkflowPiToolDependencies {\n readonly environment: Readonly<Record<string, string | undefined>>;\n launchExecutor?: WorkflowLaunchExecutor;\n onLaunch?: (ctx: ExtensionContext) => Promise<void> | void;\n activeRunCount?: () => Promise<number>;\n followRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n tailRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n openRun?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n rejectRunner?: (workflowPath: string, runner: string) => string | undefined;\n requireSessionRun?: (input: WorkflowRunSelector, sessionId: string | undefined) => Promise<WorkflowRunRecord>;\n /**\n * Resolve a failed recovery target globally.\n *\n * Recovery is the one ownership-transferring action: a replacement Pi\n * session must be able to adopt a terminal run whose launching session is\n * gone. Every non-recovery action remains session-scoped.\n */\n requireRecoverableRun?: (input: WorkflowRunSelector) => Promise<WorkflowRunRecord>;\n readRecoveryEvidence?: (input: WorkflowRunSelector, ctx: ExtensionContext) => Promise<string>;\n /**\n * Hand a recovery to a terminal launcher, returning what to tell the caller.\n *\n * Undefined means this recovery cannot be delegated, and the in-process\n * replay below is used instead.\n */\n delegateRecovery?: (\n input: { runKey: string; workspace?: string; runner?: string },\n ctx: ExtensionContext,\n ) => Promise<string | undefined>;\n observeSession?: (sessionId: string | undefined) => void;\n trackPendingRun?: <T>(run: Promise<T>) => Promise<T>;\n feature?: EmbeddedWorkflowFeature;\n recoverTool?: {\n execute(input: {\n dryRun?: boolean;\n job?: string;\n runKey: string;\n runner?: string;\n workspace?: string;\n }): Promise<CallToolResult>;\n };\n controlTool?: ReturnType<EmbeddedWorkflowFeature['createControlTool']>;\n runTool?: EmbeddedWorkflowFeature['runTool'];\n listWorkflowsTool?: EmbeddedWorkflowFeature['listWorkflowsTool'];\n}\n\nexport function reportProgress(\n onUpdate: AgentToolUpdateCallback<{ tool: string }> | undefined,\n tool: string,\n message: string,\n): void {\n onUpdate?.({ content: [{ type: 'text', text: message }], details: { tool } });\n}\n\nexport function appendGuidance(\n result: AgentToolResult<{ tool: string }>,\n guidance: string,\n): AgentToolResult<{ tool: string }> {\n return { ...result, content: [...result.content, { type: 'text', text: guidance }] };\n}\n\nfunction statusAction(runKey: string): string {\n return `workflow_run {\"action\":\"status\",\"runKey\":${JSON.stringify(runKey)}}`;\n}\n\nexport function actionGuidance(action: WorkflowRunInput['action'], runKey: string): string[] {\n const status = statusAction(runKey);\n if (action === 'stop') return [`Next: call ${status} to confirm the run reached a terminal stage.`];\n if (action === 'recover') {\n return [\n `Report the recovery failure and do not retry it blindly: call ${status} first.`,\n 'Ask the user before launching fresh work when the recorded recovery is no longer valid.',\n ];\n }\n return [`Next: call ${status} to verify the recorded execution state.`];\n}\n\nexport const launchFailureOptions = [\n 'Report the failure to the user with the error above. A launch failure is usually a bad workflow path, a missing launcher, or a workflow whose own pre-conditions refused.',\n 'list_workflows: confirm the workflow path exists and its description matches the work, if the path may be wrong.',\n 'workflow_run with action status: check whether an earlier attempt is already running before launching again.',\n];\n\n/**\n * An Error, whatever the launch rejected with.\n *\n * A rejection reaches here as `unknown` because it crossed a promise boundary,\n * and rethrowing that as-is hands Pi's tool layer something it cannot read a\n * message or a stack off. The original is kept as the cause rather than\n * flattened into a string.\n */\nfunction asError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value), { cause: value });\n}\n\n/** Resolves to undefined, so a race against it reads as \"nothing settled yet\". */\nfunction sleep(ms: number): Promise<undefined> {\n return new Promise((settle) => {\n const timer = setTimeout(() => settle(undefined), ms);\n timer.unref?.();\n });\n}\n\n/**\n * Wait for whichever answer arrives first: the launch settling, the run\n * appearing in the registry, or the budget running out.\n *\n * A launch that delegates to a terminal launcher settles when that launcher's\n * whole process chain closes, which on a loaded machine has been measured\n * minutes after the run itself was registered and working. The registry is the\n * earlier and more truthful signal: a run recorded there is a run that started.\n */\nasync function awaitLaunchAck(\n settled: Promise<LaunchOutcome>,\n dependencies: WorkflowLaunchExecutorDependencies,\n query: LaunchedRunQuery,\n): Promise<LaunchAck> {\n const findLaunchedRun = dependencies.findLaunchedRun;\n if (!findLaunchedRun) return { kind: 'settled', outcome: await settled };\n\n const pollMs = dependencies.launchAckPollMs ?? LAUNCH_ACK_POLL_MS;\n const deadline = Date.now() + (dependencies.launchAckTimeoutMs ?? LAUNCH_ACK_TIMEOUT_MS);\n for (;;) {\n const outcome = await Promise.race([settled, sleep(pollMs)]);\n if (outcome) return { kind: 'settled', outcome };\n // A registry read that throws must not fail the launch: the run may be\n // perfectly healthy, and the settled promise is still the fallback.\n const record = await findLaunchedRun(query).catch(() => undefined);\n if (record) return { kind: 'record', record };\n if (Date.now() >= deadline) return { kind: 'handoff' };\n }\n}\n\n/**\n * Carry a failure that lands after the caller was told the run started.\n *\n * Answering early trades the launcher's exit code for timeliness. Dropping that\n * code entirely is not part of the trade: a launch that fails a minute later\n * would otherwise leave a user waiting on a run that never existed.\n */\nfunction reportLateFailure(\n settled: Promise<LaunchOutcome>,\n dependencies: WorkflowLaunchExecutorDependencies,\n ctx: ExtensionContext,\n): void {\n void settled.then((outcome) => {\n if (outcome.kind === 'error') {\n dependencies.onLateFailure?.(asError(outcome.error), ctx);\n return;\n }\n if (outcome.result.isError) {\n dependencies.onLateFailure?.(new Error(toolResultText(outcome.result) || 'Workflow launch failed.'), ctx);\n }\n });\n}\n\nexport function createWorkflowLaunchExecutor(dependencies: WorkflowLaunchExecutorDependencies): WorkflowLaunchExecutor {\n return {\n async execute(input, ctx, onUpdate) {\n const sessionId = resolveRootSessionId(ctx.sessionManager.getSessionId(), dependencies.environment);\n dependencies.observeSession?.(sessionId);\n const agiflowJobKind = input.env?.[AGIFLOW_JOB_KIND_ENV]?.trim();\n const agiflowJobId = input.env?.[AGIFLOW_JOB_ID_ENV]?.trim();\n const explicitProjectId = input.env?.[AGIFLOW_PROJECT_ID_ENV]?.trim();\n if (Boolean(agiflowJobKind) !== Boolean(agiflowJobId)) {\n throw new Error('Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.');\n }\n if (agiflowJobKind && agiflowJobKind !== 'task' && agiflowJobKind !== 'work-unit') {\n throw new Error('Agiflow workflow launches require AGIFLOW_JOB_KIND to be task or work-unit.');\n }\n if (agiflowJobKind && !explicitProjectId) {\n throw new Error('Agiflow workflow launches require AGIFLOW_PROJECT_ID with the job identity.');\n }\n if (agiflowJobKind && !input.prompt?.trim()) {\n throw new Error(\n 'Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.',\n );\n }\n reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, 'Checking workflow capacity...');\n const maxConcurrent = resolveMaxConcurrent(dependencies.environment);\n const active = (await dependencies.activeRunCount?.()) ?? 0;\n if (active >= maxConcurrent) {\n throw new Error(\n withOptions(`This session is at capacity: ${active}/${maxConcurrent} workflows running.`, [\n 'Wait for a running workflow to finish, then launch again. The extension reports each one as it ends.',\n 'workflow_run with action status: show what is running, so the user can judge whether something is stuck.',\n 'workflow_run with action stop: free a slot by stopping a run the user no longer wants.',\n ]),\n );\n }\n\n if (input.runner) {\n const rejection = dependencies.rejectRunner?.(input.workflowPath, input.runner);\n if (rejection) throw new Error(rejection);\n }\n reportProgress(onUpdate, LAUNCH_WORKFLOW_TOOL_NAME, `Launching workflow ${input.workflowPath}...`);\n const workflowEnv = { ...input.env };\n const dispatcherContextFile = dependencies.environment.AGIFLOW_DISPATCH_CONTEXT_FILE;\n const dispatcherProjectId = dependencies.environment[AGIFLOW_PROJECT_ID_ENV]?.trim();\n if (\n dispatcherContextFile &&\n agiflowJobKind &&\n dispatcherProjectId &&\n explicitProjectId &&\n dispatcherProjectId !== explicitProjectId\n ) {\n throw new Error(\n `Agiflow workflow project identity conflicts with the dispatcher context (${explicitProjectId} versus ${dispatcherProjectId}).`,\n );\n }\n if (dispatcherContextFile) {\n for (const key of [\n 'AGIFLOW_ORGANIZATION_ID',\n 'AGIFLOW_PROJECT_ID',\n 'AGIFLOW_DEVICE_ID',\n 'BACKEND_AGIFLOW_API_ENDPOINT',\n 'AGIFLOW_DISPATCH_SECRET_FILE',\n ] as const) {\n const hostValue = dependencies.environment[key];\n if (hostValue) workflowEnv[key] = hostValue;\n }\n }\n const since = Date.now();\n const launch = dependencies.trackPendingRun(\n dependencies.runTool.execute({\n ...input,\n env: { ...workflowEnv, [PI_SESSION_ENV]: sessionId },\n }),\n );\n // Folded into a value before anything races it. A promise this function\n // may stop awaiting must never be able to reject unobserved, which in\n // Node ends the whole process, Pi's TUI included.\n const settled: Promise<LaunchOutcome> = launch.then(\n (value): LaunchOutcome => ({ kind: 'value', result: value }),\n (error: unknown): LaunchOutcome => ({ kind: 'error', error }),\n );\n\n const ack = await awaitLaunchAck(settled, dependencies, {\n sessionId,\n since,\n workflowPath: input.workflowPath,\n });\n\n if (ack.kind === 'settled') {\n const outcome = ack.outcome;\n if (outcome.kind === 'error') throw asError(outcome.error);\n const result = outcome.result;\n if (result.isError)\n throw new Error(withOptions(toolResultText(result) || 'Workflow launch failed.', launchFailureOptions));\n await dependencies.onLaunch?.(ctx);\n return result;\n }\n\n reportLateFailure(settled, dependencies, ctx);\n await dependencies.onLaunch?.(ctx);\n return {\n content: [\n {\n type: 'text',\n text: ack.kind === 'record' ? launchedRunSummary(ack.record) : launchHandoffSummary(input.workflowPath),\n },\n ],\n };\n },\n };\n}\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- let e=require("node:async_hooks"),t=require("@agimon-ai/doompi-core/child-process");const n=`agiflow-dispatcher`,r=`The workflow runtime is no longer active.`,i=Symbol(`doom-workflow-cleanup`),a=new Set([`list_workflows`,`launch_workflow`]),o=new Set([`launch_workflow`]);function s(e,t){let n=Reflect.get(e,t,e);return typeof n==`function`?n.bind(e):n}function c(e,t){let n=new Proxy(e.sessionManager,{get(e,n){return n===`getSessionId`?()=>t:s(e,n)}});return new Proxy(e,{get(e,t){return t===`sessionManager`?n:s(e,t)}})}function l(){return e=>e.filter(e=>e!==`workflow_run`)}function u(e,n,r){let i=n?(0,t.resolveRootSessionId)(n,r):void 0;return e.filter(e=>a.has(e.name)).map(e=>!i||!o.has(e.name)?e:{...e,execute:(t,n,r,a,o)=>e.execute(t,n,r,a,c(o,i))})}function d(e){return(0,t.readChildProcessContext)(e)?.parentSessionId??(e[t.SUBAGENT_PARENT_SESSION_ENV]?.trim()||void 0)}function f(e){return(0,t.readChildProcessContext)(e)?.mode===n||e.PI_SUBAGENT_CHILD_AGENT===n&&!!e[t.SUBAGENT_PARENT_SESSION_ENV]?.trim()}function p(t,n){let a=new e.AsyncLocalStorage,o=!0,c=!1,l=0,u,d,f=()=>{let e=a.getStore();return!o||n?.aborted&&e!==i?!1:e===void 0||e===i||e===l},p=e=>{let t=a.getStore();return!o||n?.aborted&&t!==i?!1:t===i||e===l&&(t===void 0||t===e)},m=(e,t)=>{let n=new Proxy(e.ui,{get(e,n){let r=Reflect.get(e,n,e);return typeof r==`function`?(...i)=>{if(p(t))return Reflect.apply(r,e,i);if(n===`confirm`)return Promise.resolve(!1);if(n===`custom`||n===`editor`||n===`input`||n===`select`)return Promise.resolve(void 0)}:r}});return new Proxy(e,{get(e,t){return t===`ui`?n:s(e,t)}})};return{pi:new Proxy(t,{get(e,t){return t===`sendMessage`?(...t)=>{f()&&e.sendMessage(...t)}:t===`sendUserMessage`?(...t)=>{f()&&e.sendUserMessage(...t)}:t===`exec`?(...t)=>f()?e.exec(...t):Promise.resolve({code:1,killed:!1,stderr:r,stdout:``}):s(e,t)}}),tool:e=>({...e,execute:async(t,n,i,s,u)=>{let d=l;if(!o||c||i?.aborted)throw Error(r);let f=m(u,d),p=s?e=>{o&&d===l&&!i?.aborted&&s(e)}:void 0,h=await a.run(d,()=>e.execute(t,n,i,p,f));if(!o||c||d!==l)throw Error(r);return h}}),command:(e,t)=>[e,{...t,handler:async(e,n)=>{let r=l;o&&!c&&await a.run(r,()=>t.handler(e,m(n,r)))}}],shortcut:(e,t)=>[e,{...t,handler:e=>{let n=l;if(o&&!c)return a.run(n,()=>t.handler(m(e,n)))}}],events:e=>Object.fromEntries(Object.entries(e).map(([e,t])=>[e,(n,r)=>{if(!o||c)return;e===`session_start`&&(n!==u||r!==d)&&(l+=1,u=n,d=r);let i=l;return a.run(i,()=>t(n,m(r,i)))}])),beginDisposal(){!c&&o&&(c=!0,l+=1)},finishDisposal(){o=!1,c=!1,l+=1},isCurrentInvocation:f,runCleanup:e=>a.run(i,e)}}exports.createWorkflowFence=p,exports.dispatcherToolRestriction=l,exports.dispatcherTools=u,exports.isWorkflowDispatcherProcess=f,exports.resolveDispatcherParentSession=d;
2
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","names":["resolveRootSessionId","readChildProcessContext","SUBAGENT_PARENT_SESSION_ENV","AsyncLocalStorage"],"sources":["../../../../src/services/workflowFence/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\n\nimport {\n readChildProcessContext,\n resolveRootSessionId,\n SUBAGENT_PARENT_SESSION_ENV,\n} from '@agimon-ai/doompi-core/child-process';\nimport type { PiEventHandlers } from '@agimon-ai/doompi-core/pi-extension';\nimport type { DoomToolRestriction } from '@agimon-ai/doompi-core/tool-surface';\nimport type { ExtensionAPI, ExtensionContext, ToolDefinition } from '@earendil-works/pi-coding-agent';\n\nconst DISPATCHER_AGENT_NAME = 'agiflow-dispatcher';\nconst SESSION_START_EVENT = 'session_start';\nconst INACTIVE_RUNTIME_MESSAGE = 'The workflow runtime is no longer active.';\nconst CLEANUP_SCOPE = Symbol('doom-workflow-cleanup');\n\nconst DISPATCHER_TOOL_NAMES = new Set(['list_workflows', 'launch_workflow']);\nconst ROOT_SCOPED_TOOL_NAMES = new Set(['launch_workflow']);\nconst WORKFLOW_RUN_TOOL_NAME = 'workflow_run';\n\ntype ToolSchema = Parameters<ExtensionAPI['registerTool']>[0]['parameters'];\ntype CommandOptions = Parameters<ExtensionAPI['registerCommand']>[1];\ntype ShortcutOptions = Parameters<ExtensionAPI['registerShortcut']>[1];\ntype RuntimeScope = number | typeof CLEANUP_SCOPE;\ntype LifecycleHandler = (event: unknown, context: ExtensionContext) => unknown;\n\nexport interface WorkflowFence {\n tool<TSchema extends ToolSchema, TDetails>(\n tool: ToolDefinition<TSchema, TDetails>,\n ): ToolDefinition<TSchema, TDetails>;\n command(name: string, options: CommandOptions): readonly [string, CommandOptions];\n shortcut(\n shortcut: Parameters<ExtensionAPI['registerShortcut']>[0],\n options: ShortcutOptions,\n ): readonly [Parameters<ExtensionAPI['registerShortcut']>[0], ShortcutOptions];\n events(handlers: PiEventHandlers): PiEventHandlers;\n readonly pi: ExtensionAPI;\n readonly beginDisposal: () => void;\n readonly finishDisposal: () => void;\n readonly isCurrentInvocation: () => boolean;\n readonly runCleanup: <T>(operation: () => T) => T;\n}\n\nfunction boundValue(target: object, property: PropertyKey): unknown {\n const value = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n}\n\nfunction withParentSession(ctx: ExtensionContext, parentSessionId: string): ExtensionContext {\n const sessionManager = new Proxy(ctx.sessionManager, {\n get(target, property) {\n if (property === 'getSessionId') return () => parentSessionId;\n return boundValue(target, property);\n },\n });\n return new Proxy(ctx, {\n get(target, property) {\n if (property === 'sessionManager') return sessionManager;\n return boundValue(target, property);\n },\n });\n}\n\n/**\n * A dispatcher child launches workflows for its root session and never runs one\n * itself, so `workflow_run` stays off its surface even if something registers it.\n */\nexport function dispatcherToolRestriction(): DoomToolRestriction {\n return (incoming) => incoming.filter((name) => name !== WORKFLOW_RUN_TOOL_NAME);\n}\n\n/** Restrict a dispatcher child to discovery and root-owned launch. */\nexport function dispatcherTools<TSchema extends ToolSchema, TDetails>(\n tools: readonly ToolDefinition<TSchema, TDetails>[],\n parentSessionId: string | undefined,\n environment: Readonly<Record<string, string | undefined>>,\n): ToolDefinition<TSchema, TDetails>[] {\n const rootSessionId = parentSessionId ? resolveRootSessionId(parentSessionId, environment) : undefined;\n return tools\n .filter((tool) => DISPATCHER_TOOL_NAMES.has(tool.name))\n .map((tool) => {\n if (!rootSessionId || !ROOT_SCOPED_TOOL_NAMES.has(tool.name)) return tool;\n return {\n ...tool,\n execute: (toolCallId, params, signal, onUpdate, ctx) =>\n tool.execute(toolCallId, params, signal, onUpdate, withParentSession(ctx, rootSessionId)),\n };\n });\n}\n\nexport function resolveDispatcherParentSession(\n environment: Readonly<Record<string, string | undefined>>,\n): string | undefined {\n const childContext = readChildProcessContext(environment);\n return childContext?.parentSessionId ?? (environment[SUBAGENT_PARENT_SESSION_ENV]?.trim() || undefined);\n}\n\nexport function isWorkflowDispatcherProcess(environment: Readonly<Record<string, string | undefined>>): boolean {\n const childContext = readChildProcessContext(environment);\n if (childContext?.mode === DISPATCHER_AGENT_NAME) return true;\n return (\n environment.PI_SUBAGENT_CHILD_AGENT === DISPATCHER_AGENT_NAME &&\n Boolean(environment[SUBAGENT_PARENT_SESSION_ENV]?.trim())\n );\n}\n\n/** Fence every callback retained by Pi after this package root is replaced. */\nexport function createWorkflowFence(host: ExtensionAPI, signal?: AbortSignal): WorkflowFence {\n const scope = new AsyncLocalStorage<RuntimeScope>();\n let active = true;\n let disposing = false;\n let generation = 0;\n let lastSessionStartEvent: unknown;\n let lastSessionStartContext: ExtensionContext | undefined;\n\n const isCurrentInvocation = (): boolean => {\n const currentScope = scope.getStore();\n if (!active || (signal?.aborted && currentScope !== CLEANUP_SCOPE)) return false;\n return currentScope === undefined || currentScope === CLEANUP_SCOPE || currentScope === generation;\n };\n const ownsContext = (contextGeneration: number): boolean => {\n const currentScope = scope.getStore();\n if (!active || (signal?.aborted && currentScope !== CLEANUP_SCOPE)) return false;\n return (\n currentScope === CLEANUP_SCOPE ||\n (contextGeneration === generation && (currentScope === undefined || currentScope === contextGeneration))\n );\n };\n\n const guardContext = <TContext extends ExtensionContext>(context: TContext, contextGeneration: number): TContext => {\n const guardedUi = new Proxy(context.ui, {\n get(target, property) {\n const value = Reflect.get(target, property, target);\n if (typeof value !== 'function') return value;\n return (...args: unknown[]): unknown => {\n if (ownsContext(contextGeneration)) return Reflect.apply(value, target, args);\n if (property === 'confirm') return Promise.resolve(false);\n if (property === 'custom' || property === 'editor' || property === 'input' || property === 'select') {\n return Promise.resolve(undefined);\n }\n return undefined;\n };\n },\n });\n return new Proxy(context, {\n get(target, property) {\n if (property === 'ui') return guardedUi;\n return boundValue(target, property);\n },\n }) as TContext;\n };\n\n const tool = <TSchema extends ToolSchema, TDetails>(\n definition: ToolDefinition<TSchema, TDetails>,\n ): ToolDefinition<TSchema, TDetails> => {\n return {\n ...definition,\n execute: async (toolCallId, params, signal, onUpdate, context) => {\n const executionGeneration = generation;\n if (!active || disposing || signal?.aborted) throw new Error(INACTIVE_RUNTIME_MESSAGE);\n const guardedContext = guardContext(context, executionGeneration);\n const guardedUpdate = onUpdate\n ? (update: Parameters<NonNullable<typeof onUpdate>>[0]): void => {\n if (active && executionGeneration === generation && !signal?.aborted) onUpdate(update);\n }\n : undefined;\n const result = await scope.run(executionGeneration, () =>\n definition.execute(toolCallId, params, signal, guardedUpdate, guardedContext),\n );\n if (!active || disposing || executionGeneration !== generation) throw new Error(INACTIVE_RUNTIME_MESSAGE);\n return result;\n },\n };\n };\n\n const command: WorkflowFence['command'] = (name, options) => {\n return [\n name,\n {\n ...options,\n handler: async (args, context) => {\n const executionGeneration = generation;\n if (!active || disposing) return;\n await scope.run(executionGeneration, () => options.handler(args, guardContext(context, executionGeneration)));\n },\n },\n ];\n };\n\n const shortcut: WorkflowFence['shortcut'] = (key, options) => {\n return [\n key,\n {\n ...options,\n handler: (context) => {\n const executionGeneration = generation;\n if (!active || disposing) return;\n return scope.run(executionGeneration, () => options.handler(guardContext(context, executionGeneration)));\n },\n },\n ];\n };\n\n const events = (handlers: PiEventHandlers): PiEventHandlers =>\n Object.fromEntries(\n Object.entries(handlers).map(([eventName, handler]) => [\n eventName,\n (event: unknown, context: ExtensionContext) => {\n if (!active || disposing) return undefined;\n if (\n eventName === SESSION_START_EVENT &&\n (event !== lastSessionStartEvent || context !== lastSessionStartContext)\n ) {\n generation += 1;\n lastSessionStartEvent = event;\n lastSessionStartContext = context;\n }\n const eventGeneration = generation;\n return scope.run(eventGeneration, () =>\n (handler as LifecycleHandler)(event, guardContext(context, eventGeneration)),\n );\n },\n ]),\n ) as PiEventHandlers;\n\n const guardedPi = new Proxy(host, {\n get(target, property) {\n if (property === 'sendMessage') {\n return (...args: Parameters<ExtensionAPI['sendMessage']>): void => {\n if (isCurrentInvocation()) target.sendMessage(...args);\n };\n }\n if (property === 'sendUserMessage') {\n return (...args: Parameters<ExtensionAPI['sendUserMessage']>): void => {\n if (isCurrentInvocation()) target.sendUserMessage(...args);\n };\n }\n if (property === 'exec') {\n return (...args: Parameters<ExtensionAPI['exec']>): ReturnType<ExtensionAPI['exec']> =>\n isCurrentInvocation()\n ? target.exec(...args)\n : Promise.resolve({ code: 1, killed: false, stderr: INACTIVE_RUNTIME_MESSAGE, stdout: '' });\n }\n return boundValue(target, property);\n },\n });\n\n return {\n pi: guardedPi,\n tool,\n command,\n shortcut,\n events,\n beginDisposal() {\n if (disposing || !active) return;\n disposing = true;\n generation += 1;\n },\n finishDisposal() {\n active = false;\n disposing = false;\n generation += 1;\n },\n isCurrentInvocation,\n runCleanup: (operation) => scope.run(CLEANUP_SCOPE, operation),\n };\n}\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{AsyncLocalStorage as e}from"node:async_hooks";import{SUBAGENT_PARENT_SESSION_ENV as t,readChildProcessContext as n,resolveRootSessionId as r}from"@agimon-ai/doompi-core/child-process";const i=`agiflow-dispatcher`,a=`The workflow runtime is no longer active.`,o=Symbol(`doom-workflow-cleanup`),s=new Set([`list_workflows`,`launch_workflow`]),c=new Set([`launch_workflow`]);function l(e,t){let n=Reflect.get(e,t,e);return typeof n==`function`?n.bind(e):n}function u(e,t){let n=new Proxy(e.sessionManager,{get(e,n){return n===`getSessionId`?()=>t:l(e,n)}});return new Proxy(e,{get(e,t){return t===`sessionManager`?n:l(e,t)}})}function d(){return e=>e.filter(e=>e!==`workflow_run`)}function f(e,t,n){let i=t?r(t,n):void 0;return e.filter(e=>s.has(e.name)).map(e=>!i||!c.has(e.name)?e:{...e,execute:(t,n,r,a,o)=>e.execute(t,n,r,a,u(o,i))})}function p(e){return n(e)?.parentSessionId??(e[t]?.trim()||void 0)}function m(e){return n(e)?.mode===i||e.PI_SUBAGENT_CHILD_AGENT===i&&!!e[t]?.trim()}function h(t,n){let r=new e,i=!0,s=!1,c=0,u,d,f=()=>{let e=r.getStore();return!i||n?.aborted&&e!==o?!1:e===void 0||e===o||e===c},p=e=>{let t=r.getStore();return!i||n?.aborted&&t!==o?!1:t===o||e===c&&(t===void 0||t===e)},m=(e,t)=>{let n=new Proxy(e.ui,{get(e,n){let r=Reflect.get(e,n,e);return typeof r==`function`?(...i)=>{if(p(t))return Reflect.apply(r,e,i);if(n===`confirm`)return Promise.resolve(!1);if(n===`custom`||n===`editor`||n===`input`||n===`select`)return Promise.resolve(void 0)}:r}});return new Proxy(e,{get(e,t){return t===`ui`?n:l(e,t)}})};return{pi:new Proxy(t,{get(e,t){return t===`sendMessage`?(...t)=>{f()&&e.sendMessage(...t)}:t===`sendUserMessage`?(...t)=>{f()&&e.sendUserMessage(...t)}:t===`exec`?(...t)=>f()?e.exec(...t):Promise.resolve({code:1,killed:!1,stderr:a,stdout:``}):l(e,t)}}),tool:e=>({...e,execute:async(t,n,o,l,u)=>{let d=c;if(!i||s||o?.aborted)throw Error(a);let f=m(u,d),p=l?e=>{i&&d===c&&!o?.aborted&&l(e)}:void 0,h=await r.run(d,()=>e.execute(t,n,o,p,f));if(!i||s||d!==c)throw Error(a);return h}}),command:(e,t)=>[e,{...t,handler:async(e,n)=>{let a=c;i&&!s&&await r.run(a,()=>t.handler(e,m(n,a)))}}],shortcut:(e,t)=>[e,{...t,handler:e=>{let n=c;if(i&&!s)return r.run(n,()=>t.handler(m(e,n)))}}],events:e=>Object.fromEntries(Object.entries(e).map(([e,t])=>[e,(n,a)=>{if(!i||s)return;e===`session_start`&&(n!==u||a!==d)&&(c+=1,u=n,d=a);let o=c;return r.run(o,()=>t(n,m(a,o)))}])),beginDisposal(){!s&&i&&(s=!0,c+=1)},finishDisposal(){i=!1,s=!1,c+=1},isCurrentInvocation:f,runCleanup:e=>r.run(o,e)}}export{h as createWorkflowFence,d as dispatcherToolRestriction,f as dispatcherTools,m as isWorkflowDispatcherProcess,p as resolveDispatcherParentSession};
2
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/workflowFence/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\n\nimport {\n readChildProcessContext,\n resolveRootSessionId,\n SUBAGENT_PARENT_SESSION_ENV,\n} from '@agimon-ai/doompi-core/child-process';\nimport type { PiEventHandlers } from '@agimon-ai/doompi-core/pi-extension';\nimport type { DoomToolRestriction } from '@agimon-ai/doompi-core/tool-surface';\nimport type { ExtensionAPI, ExtensionContext, ToolDefinition } from '@earendil-works/pi-coding-agent';\n\nconst DISPATCHER_AGENT_NAME = 'agiflow-dispatcher';\nconst SESSION_START_EVENT = 'session_start';\nconst INACTIVE_RUNTIME_MESSAGE = 'The workflow runtime is no longer active.';\nconst CLEANUP_SCOPE = Symbol('doom-workflow-cleanup');\n\nconst DISPATCHER_TOOL_NAMES = new Set(['list_workflows', 'launch_workflow']);\nconst ROOT_SCOPED_TOOL_NAMES = new Set(['launch_workflow']);\nconst WORKFLOW_RUN_TOOL_NAME = 'workflow_run';\n\ntype ToolSchema = Parameters<ExtensionAPI['registerTool']>[0]['parameters'];\ntype CommandOptions = Parameters<ExtensionAPI['registerCommand']>[1];\ntype ShortcutOptions = Parameters<ExtensionAPI['registerShortcut']>[1];\ntype RuntimeScope = number | typeof CLEANUP_SCOPE;\ntype LifecycleHandler = (event: unknown, context: ExtensionContext) => unknown;\n\nexport interface WorkflowFence {\n tool<TSchema extends ToolSchema, TDetails>(\n tool: ToolDefinition<TSchema, TDetails>,\n ): ToolDefinition<TSchema, TDetails>;\n command(name: string, options: CommandOptions): readonly [string, CommandOptions];\n shortcut(\n shortcut: Parameters<ExtensionAPI['registerShortcut']>[0],\n options: ShortcutOptions,\n ): readonly [Parameters<ExtensionAPI['registerShortcut']>[0], ShortcutOptions];\n events(handlers: PiEventHandlers): PiEventHandlers;\n readonly pi: ExtensionAPI;\n readonly beginDisposal: () => void;\n readonly finishDisposal: () => void;\n readonly isCurrentInvocation: () => boolean;\n readonly runCleanup: <T>(operation: () => T) => T;\n}\n\nfunction boundValue(target: object, property: PropertyKey): unknown {\n const value = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n}\n\nfunction withParentSession(ctx: ExtensionContext, parentSessionId: string): ExtensionContext {\n const sessionManager = new Proxy(ctx.sessionManager, {\n get(target, property) {\n if (property === 'getSessionId') return () => parentSessionId;\n return boundValue(target, property);\n },\n });\n return new Proxy(ctx, {\n get(target, property) {\n if (property === 'sessionManager') return sessionManager;\n return boundValue(target, property);\n },\n });\n}\n\n/**\n * A dispatcher child launches workflows for its root session and never runs one\n * itself, so `workflow_run` stays off its surface even if something registers it.\n */\nexport function dispatcherToolRestriction(): DoomToolRestriction {\n return (incoming) => incoming.filter((name) => name !== WORKFLOW_RUN_TOOL_NAME);\n}\n\n/** Restrict a dispatcher child to discovery and root-owned launch. */\nexport function dispatcherTools<TSchema extends ToolSchema, TDetails>(\n tools: readonly ToolDefinition<TSchema, TDetails>[],\n parentSessionId: string | undefined,\n environment: Readonly<Record<string, string | undefined>>,\n): ToolDefinition<TSchema, TDetails>[] {\n const rootSessionId = parentSessionId ? resolveRootSessionId(parentSessionId, environment) : undefined;\n return tools\n .filter((tool) => DISPATCHER_TOOL_NAMES.has(tool.name))\n .map((tool) => {\n if (!rootSessionId || !ROOT_SCOPED_TOOL_NAMES.has(tool.name)) return tool;\n return {\n ...tool,\n execute: (toolCallId, params, signal, onUpdate, ctx) =>\n tool.execute(toolCallId, params, signal, onUpdate, withParentSession(ctx, rootSessionId)),\n };\n });\n}\n\nexport function resolveDispatcherParentSession(\n environment: Readonly<Record<string, string | undefined>>,\n): string | undefined {\n const childContext = readChildProcessContext(environment);\n return childContext?.parentSessionId ?? (environment[SUBAGENT_PARENT_SESSION_ENV]?.trim() || undefined);\n}\n\nexport function isWorkflowDispatcherProcess(environment: Readonly<Record<string, string | undefined>>): boolean {\n const childContext = readChildProcessContext(environment);\n if (childContext?.mode === DISPATCHER_AGENT_NAME) return true;\n return (\n environment.PI_SUBAGENT_CHILD_AGENT === DISPATCHER_AGENT_NAME &&\n Boolean(environment[SUBAGENT_PARENT_SESSION_ENV]?.trim())\n );\n}\n\n/** Fence every callback retained by Pi after this package root is replaced. */\nexport function createWorkflowFence(host: ExtensionAPI, signal?: AbortSignal): WorkflowFence {\n const scope = new AsyncLocalStorage<RuntimeScope>();\n let active = true;\n let disposing = false;\n let generation = 0;\n let lastSessionStartEvent: unknown;\n let lastSessionStartContext: ExtensionContext | undefined;\n\n const isCurrentInvocation = (): boolean => {\n const currentScope = scope.getStore();\n if (!active || (signal?.aborted && currentScope !== CLEANUP_SCOPE)) return false;\n return currentScope === undefined || currentScope === CLEANUP_SCOPE || currentScope === generation;\n };\n const ownsContext = (contextGeneration: number): boolean => {\n const currentScope = scope.getStore();\n if (!active || (signal?.aborted && currentScope !== CLEANUP_SCOPE)) return false;\n return (\n currentScope === CLEANUP_SCOPE ||\n (contextGeneration === generation && (currentScope === undefined || currentScope === contextGeneration))\n );\n };\n\n const guardContext = <TContext extends ExtensionContext>(context: TContext, contextGeneration: number): TContext => {\n const guardedUi = new Proxy(context.ui, {\n get(target, property) {\n const value = Reflect.get(target, property, target);\n if (typeof value !== 'function') return value;\n return (...args: unknown[]): unknown => {\n if (ownsContext(contextGeneration)) return Reflect.apply(value, target, args);\n if (property === 'confirm') return Promise.resolve(false);\n if (property === 'custom' || property === 'editor' || property === 'input' || property === 'select') {\n return Promise.resolve(undefined);\n }\n return undefined;\n };\n },\n });\n return new Proxy(context, {\n get(target, property) {\n if (property === 'ui') return guardedUi;\n return boundValue(target, property);\n },\n }) as TContext;\n };\n\n const tool = <TSchema extends ToolSchema, TDetails>(\n definition: ToolDefinition<TSchema, TDetails>,\n ): ToolDefinition<TSchema, TDetails> => {\n return {\n ...definition,\n execute: async (toolCallId, params, signal, onUpdate, context) => {\n const executionGeneration = generation;\n if (!active || disposing || signal?.aborted) throw new Error(INACTIVE_RUNTIME_MESSAGE);\n const guardedContext = guardContext(context, executionGeneration);\n const guardedUpdate = onUpdate\n ? (update: Parameters<NonNullable<typeof onUpdate>>[0]): void => {\n if (active && executionGeneration === generation && !signal?.aborted) onUpdate(update);\n }\n : undefined;\n const result = await scope.run(executionGeneration, () =>\n definition.execute(toolCallId, params, signal, guardedUpdate, guardedContext),\n );\n if (!active || disposing || executionGeneration !== generation) throw new Error(INACTIVE_RUNTIME_MESSAGE);\n return result;\n },\n };\n };\n\n const command: WorkflowFence['command'] = (name, options) => {\n return [\n name,\n {\n ...options,\n handler: async (args, context) => {\n const executionGeneration = generation;\n if (!active || disposing) return;\n await scope.run(executionGeneration, () => options.handler(args, guardContext(context, executionGeneration)));\n },\n },\n ];\n };\n\n const shortcut: WorkflowFence['shortcut'] = (key, options) => {\n return [\n key,\n {\n ...options,\n handler: (context) => {\n const executionGeneration = generation;\n if (!active || disposing) return;\n return scope.run(executionGeneration, () => options.handler(guardContext(context, executionGeneration)));\n },\n },\n ];\n };\n\n const events = (handlers: PiEventHandlers): PiEventHandlers =>\n Object.fromEntries(\n Object.entries(handlers).map(([eventName, handler]) => [\n eventName,\n (event: unknown, context: ExtensionContext) => {\n if (!active || disposing) return undefined;\n if (\n eventName === SESSION_START_EVENT &&\n (event !== lastSessionStartEvent || context !== lastSessionStartContext)\n ) {\n generation += 1;\n lastSessionStartEvent = event;\n lastSessionStartContext = context;\n }\n const eventGeneration = generation;\n return scope.run(eventGeneration, () =>\n (handler as LifecycleHandler)(event, guardContext(context, eventGeneration)),\n );\n },\n ]),\n ) as PiEventHandlers;\n\n const guardedPi = new Proxy(host, {\n get(target, property) {\n if (property === 'sendMessage') {\n return (...args: Parameters<ExtensionAPI['sendMessage']>): void => {\n if (isCurrentInvocation()) target.sendMessage(...args);\n };\n }\n if (property === 'sendUserMessage') {\n return (...args: Parameters<ExtensionAPI['sendUserMessage']>): void => {\n if (isCurrentInvocation()) target.sendUserMessage(...args);\n };\n }\n if (property === 'exec') {\n return (...args: Parameters<ExtensionAPI['exec']>): ReturnType<ExtensionAPI['exec']> =>\n isCurrentInvocation()\n ? target.exec(...args)\n : Promise.resolve({ code: 1, killed: false, stderr: INACTIVE_RUNTIME_MESSAGE, stdout: '' });\n }\n return boundValue(target, property);\n },\n });\n\n return {\n pi: guardedPi,\n tool,\n command,\n shortcut,\n events,\n beginDisposal() {\n if (disposing || !active) return;\n disposing = true;\n generation += 1;\n },\n finishDisposal() {\n active = false;\n disposing = false;\n generation += 1;\n },\n isCurrentInvocation,\n runCleanup: (operation) => scope.run(CLEANUP_SCOPE, operation),\n };\n}\n"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- const e=/^([A-Za-z_][\w.-]*)=(.*)$/s;function t(e){return`error`in e}function n(e){return e.length>1&&e.startsWith(`"`)&&e.endsWith(`"`)?e.slice(1,-1):e}function r(e){let t=[],n=0;for(;n<e.length;){for(;n<e.length&&/\s/.test(e[n]);)n+=1;if(n>=e.length)break;let r=n,i=!1;for(;n<e.length&&(i||!/\s/.test(e[n]));)e[n]===`"`&&(i=!i),n+=1;t.push({text:e.slice(r,n),start:r})}return t}function i(t){let i=r(t.trim()),a=i[0];if(a===void 0)return{error:`Usage: /workflow-launch <workflow> [key=value …] [prompt]`};let o={},s,c=1;for(;c<i.length;c+=1){let t=i[c],r=e.exec(t.text);if(!r)break;let a=r[1],l=n(r[2]);a===`runner`?s=l:o[a]=l}let l=i[c],u=l===void 0?void 0:t.trim().slice(l.start).trim();return{workflow:n(a.text),...s===void 0?{}:{runner:s},inputs:o,...u===void 0||u===``?{}:{prompt:u}}}function a(e,t){let n=t.trim(),r=n.toLocaleLowerCase();return e.find(e=>e.name.toLocaleLowerCase()===r)??e.find(e=>e.relativePath===n||e.path===n)??e.find(e=>e.relativePath.endsWith(`/${n}`)||e.path.endsWith(`/${n}`))}function o(e,t){let n=[];e.triggers.includes(`user_prompt`)&&(t.prompt??``)===``&&n.push(`This workflow is triggered by a prompt, so it needs one.`);let r=e.inputs.filter(e=>e.required===!0&&(t.inputs[e.name]??``)===``).map(e=>e.name);r.length>0&&n.push(`Missing required input${r.length>1?`s`:``}: ${r.join(`, `)}.`);for(let r of e.inputs){let e=t.inputs[r.name];e===void 0||r.options===void 0||r.options.includes(e)||n.push(`Input ${r.name} must be one of: ${r.options.join(`, `)}.`)}let i=e.runners;return t.runner!==void 0&&i!==void 0&&!i.includes(t.runner)&&n.push(i.length===0?`This workflow declares no runner its steps agree on.`:`Runner ${t.runner} is not one this workflow declares: ${i.join(`, `)}.`),n}exports.isLaunchParseFailure=t,exports.parseWorkflowLaunchCommand=i,exports.resolveWorkflowEntry=a,exports.validateWorkflowLaunch=o;
2
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/services/workflowLaunchCommand/index.ts"],"sourcesContent":["/**\n * The `/workflow-launch` line, parsed.\n *\n * WHY A VERB AT ALL:\n * A browser can only send a session a prompt frame, so every cockpit action\n * that has to run inside the session travels as a slash line, the way the\n * agent catalog sends `/run`. The workflow tools answer to the agent, not to a\n * page, and the leader board answers to a keyboard, so neither of them can be\n * what the launch dialog sends.\n *\n * THE SHAPE:\n * /workflow-launch <workflow> [key=value ...] [prompt words]\n *\n * Key-value pairs come first and stop at the first token that is not one, so\n * everything after them is the prompt exactly as it was typed, including any\n * `=` inside it. `runner` is the one reserved key; the rest are the workflow's\n * own `workflow_dispatch` inputs. Values may be double quoted to hold spaces.\n */\n\n/** The reserved key: which runner map entry the run executes with. */\nconst RUNNER_KEY = 'runner';\nconst KEY_VALUE = /^([A-Za-z_][\\w.-]*)=(.*)$/s;\n\nexport interface ParsedWorkflowLaunch {\n /** The workflow's name or its path, as typed; resolution belongs to the caller. */\n workflow: string;\n runner?: string;\n inputs: Record<string, string>;\n prompt?: string;\n}\n\nexport interface WorkflowLaunchParseFailure {\n error: string;\n}\n\nexport function isLaunchParseFailure(\n value: ParsedWorkflowLaunch | WorkflowLaunchParseFailure,\n): value is WorkflowLaunchParseFailure {\n return 'error' in value;\n}\n\n/** Strips one layer of surrounding double quotes, which is how a value holds spaces. */\nfunction unquote(value: string): string {\n return value.length > 1 && value.startsWith('\"') && value.endsWith('\"') ? value.slice(1, -1) : value;\n}\n\n/**\n * Splits on whitespace, keeping double-quoted runs together.\n *\n * Returns each token with the offset it started at, so the caller can take the\n * rest of the line verbatim rather than rebuilding it from tokens and losing\n * the spacing a prompt was written with.\n */\nfunction tokenize(line: string): { text: string; start: number }[] {\n const tokens: { text: string; start: number }[] = [];\n let index = 0;\n while (index < line.length) {\n while (index < line.length && /\\s/.test(line[index] as string)) index += 1;\n if (index >= line.length) break;\n const start = index;\n let quoted = false;\n while (index < line.length && (quoted || !/\\s/.test(line[index] as string))) {\n if (line[index] === '\"') quoted = !quoted;\n index += 1;\n }\n tokens.push({ text: line.slice(start, index), start });\n }\n return tokens;\n}\n\nexport function parseWorkflowLaunchCommand(args: string): ParsedWorkflowLaunch | WorkflowLaunchParseFailure {\n const tokens = tokenize(args.trim());\n const first = tokens[0];\n if (first === undefined) return { error: 'Usage: /workflow-launch <workflow> [key=value …] [prompt]' };\n\n const inputs: Record<string, string> = {};\n let runner: string | undefined;\n let index = 1;\n for (; index < tokens.length; index += 1) {\n const token = tokens[index] as { text: string; start: number };\n const match = KEY_VALUE.exec(token.text);\n if (!match) break;\n const key = match[1] as string;\n const value = unquote(match[2] as string);\n if (key === RUNNER_KEY) runner = value;\n else inputs[key] = value;\n }\n\n const rest = tokens[index];\n const prompt = rest === undefined ? undefined : args.trim().slice(rest.start).trim();\n return {\n workflow: unquote(first.text),\n ...(runner === undefined ? {} : { runner }),\n inputs,\n ...(prompt === undefined || prompt === '' ? {} : { prompt }),\n };\n}\n\n/** The line the cockpit sends, built from what its dialog collected. */\nexport function workflowLaunchCommand(request: ParsedWorkflowLaunch): string {\n const quote = (value: string): string => (/\\s/.test(value) ? `\"${value}\"` : value);\n const pairs = [\n ...(request.runner === undefined ? [] : [`${RUNNER_KEY}=${quote(request.runner)}`]),\n ...Object.entries(request.inputs).map(([key, value]) => `${key}=${quote(value)}`),\n ];\n const prompt = request.prompt?.trim();\n return ['/workflow-launch', quote(request.workflow), ...pairs, ...(prompt ? [prompt] : [])].join(' ');\n}\n\n/** One catalog row, as much of it as resolution needs. */\nexport interface LaunchResolvableWorkflow {\n name: string;\n path: string;\n relativePath: string;\n}\n\n/**\n * The workflow a token names: its name, its path in the repository, or the\n * tail of that path. Case-insensitive on the name, because the catalog shows\n * names as their author capitalised them and nobody types that back exactly.\n */\nexport function resolveWorkflowEntry<T extends LaunchResolvableWorkflow>(\n entries: readonly T[],\n token: string,\n): T | undefined {\n const needle = token.trim();\n const lower = needle.toLocaleLowerCase();\n return (\n entries.find((entry) => entry.name.toLocaleLowerCase() === lower) ??\n entries.find((entry) => entry.relativePath === needle || entry.path === needle) ??\n entries.find((entry) => entry.relativePath.endsWith(`/${needle}`) || entry.path.endsWith(`/${needle}`))\n );\n}\n\n/** What a workflow declares, as much of it as a launch has to satisfy. */\nexport interface LaunchRequirements {\n triggers: readonly string[];\n inputs: readonly { name: string; required?: boolean; options?: readonly string[] }[];\n /** Absent means the workflow names no runner map, so any runner will do. */\n runners?: readonly string[];\n}\n\n/** The trigger that makes a prompt mandatory: the run waits for one otherwise. */\nconst USER_PROMPT_TRIGGER = 'user_prompt';\n\n/**\n * Everything wrong with a launch, before it is started.\n *\n * Checked here rather than left to the engine because the failure would\n * otherwise be a run that starts and then waits forever for terminal input, or\n * one that dies on a runner its steps never declared. Both read as the launch\n * having worked.\n */\nexport function validateWorkflowLaunch(\n requirements: LaunchRequirements,\n parsed: Pick<ParsedWorkflowLaunch, 'inputs' | 'prompt' | 'runner'>,\n): string[] {\n const problems: string[] = [];\n if (requirements.triggers.includes(USER_PROMPT_TRIGGER) && (parsed.prompt ?? '') === '') {\n problems.push('This workflow is triggered by a prompt, so it needs one.');\n }\n const missing = requirements.inputs\n .filter((input) => input.required === true && (parsed.inputs[input.name] ?? '') === '')\n .map((input) => input.name);\n if (missing.length > 0)\n problems.push(`Missing required input${missing.length > 1 ? 's' : ''}: ${missing.join(', ')}.`);\n for (const input of requirements.inputs) {\n const value = parsed.inputs[input.name];\n if (value === undefined || input.options === undefined || input.options.includes(value)) continue;\n problems.push(`Input ${input.name} must be one of: ${input.options.join(', ')}.`);\n }\n const declared = requirements.runners;\n if (parsed.runner !== undefined && declared !== undefined && !declared.includes(parsed.runner)) {\n problems.push(\n declared.length === 0\n ? 'This workflow declares no runner its steps agree on.'\n : `Runner ${parsed.runner} is not one this workflow declares: ${declared.join(', ')}.`,\n );\n }\n return problems;\n}\n"],"mappings":"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"}
@@ -1,52 +0,0 @@
1
- //#region src/services/workflowLaunchCommand/index.d.ts
2
- /**
3
- * The `/workflow-launch` line, parsed.
4
- *
5
- * WHY A VERB AT ALL:
6
- * A browser can only send a session a prompt frame, so every cockpit action
7
- * that has to run inside the session travels as a slash line, the way the
8
- * agent catalog sends `/run`. The workflow tools answer to the agent, not to a
9
- * page, and the leader board answers to a keyboard, so neither of them can be
10
- * what the launch dialog sends.
11
- *
12
- * THE SHAPE:
13
- * /workflow-launch <workflow> [key=value ...] [prompt words]
14
- *
15
- * Key-value pairs come first and stop at the first token that is not one, so
16
- * everything after them is the prompt exactly as it was typed, including any
17
- * `=` inside it. `runner` is the one reserved key; the rest are the workflow's
18
- * own `workflow_dispatch` inputs. Values may be double quoted to hold spaces.
19
- */
20
- export interface ParsedWorkflowLaunch {
21
- /** The workflow's name or its path, as typed; resolution belongs to the caller. */
22
- workflow: string;
23
- runner?: string;
24
- inputs: Record<string, string>;
25
- prompt?: string;
26
- }
27
- export interface WorkflowLaunchParseFailure {
28
- error: string;
29
- }
30
- export declare function parseWorkflowLaunchCommand(args: string): ParsedWorkflowLaunch | WorkflowLaunchParseFailure;
31
- /** What a workflow declares, as much of it as a launch has to satisfy. */
32
- export interface LaunchRequirements {
33
- triggers: readonly string[];
34
- inputs: readonly {
35
- name: string;
36
- required?: boolean;
37
- options?: readonly string[];
38
- }[];
39
- /** Absent means the workflow names no runner map, so any runner will do. */
40
- runners?: readonly string[];
41
- }
42
- /**
43
- * Everything wrong with a launch, before it is started.
44
- *
45
- * Checked here rather than left to the engine because the failure would
46
- * otherwise be a run that starts and then waits forever for terminal input, or
47
- * one that dies on a runner its steps never declared. Both read as the launch
48
- * having worked.
49
- */
50
- export declare function validateWorkflowLaunch(requirements: LaunchRequirements, parsed: Pick<ParsedWorkflowLaunch, 'inputs' | 'prompt' | 'runner'>): string[];
51
- //#endregion
52
- //# sourceMappingURL=index.d.cts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../../../../src/services/workflowLaunchCommand/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;iBAuBiB;;EAEf;EACA;EACA,QAAQ;EACR;;iBAGe;EACf;;wBAsCc,2BAA2B,eAAe,uBAAuB;;iBAiEhE;EACf;EACA;IAAmB;IAAc;IAAoB;;;EAErD;;;;;;;;;;wBAcc,uBACd,cAAc,oBACd,QAAQ,KAAK"}
@@ -1,52 +0,0 @@
1
- //#region src/services/workflowLaunchCommand/index.d.ts
2
- /**
3
- * The `/workflow-launch` line, parsed.
4
- *
5
- * WHY A VERB AT ALL:
6
- * A browser can only send a session a prompt frame, so every cockpit action
7
- * that has to run inside the session travels as a slash line, the way the
8
- * agent catalog sends `/run`. The workflow tools answer to the agent, not to a
9
- * page, and the leader board answers to a keyboard, so neither of them can be
10
- * what the launch dialog sends.
11
- *
12
- * THE SHAPE:
13
- * /workflow-launch <workflow> [key=value ...] [prompt words]
14
- *
15
- * Key-value pairs come first and stop at the first token that is not one, so
16
- * everything after them is the prompt exactly as it was typed, including any
17
- * `=` inside it. `runner` is the one reserved key; the rest are the workflow's
18
- * own `workflow_dispatch` inputs. Values may be double quoted to hold spaces.
19
- */
20
- export interface ParsedWorkflowLaunch {
21
- /** The workflow's name or its path, as typed; resolution belongs to the caller. */
22
- workflow: string;
23
- runner?: string;
24
- inputs: Record<string, string>;
25
- prompt?: string;
26
- }
27
- export interface WorkflowLaunchParseFailure {
28
- error: string;
29
- }
30
- export declare function parseWorkflowLaunchCommand(args: string): ParsedWorkflowLaunch | WorkflowLaunchParseFailure;
31
- /** What a workflow declares, as much of it as a launch has to satisfy. */
32
- export interface LaunchRequirements {
33
- triggers: readonly string[];
34
- inputs: readonly {
35
- name: string;
36
- required?: boolean;
37
- options?: readonly string[];
38
- }[];
39
- /** Absent means the workflow names no runner map, so any runner will do. */
40
- runners?: readonly string[];
41
- }
42
- /**
43
- * Everything wrong with a launch, before it is started.
44
- *
45
- * Checked here rather than left to the engine because the failure would
46
- * otherwise be a run that starts and then waits forever for terminal input, or
47
- * one that dies on a runner its steps never declared. Both read as the launch
48
- * having worked.
49
- */
50
- export declare function validateWorkflowLaunch(requirements: LaunchRequirements, parsed: Pick<ParsedWorkflowLaunch, 'inputs' | 'prompt' | 'runner'>): string[];
51
- //#endregion
52
- //# sourceMappingURL=index.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/services/workflowLaunchCommand/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;iBAuBiB;;EAEf;EACA;EACA,QAAQ;EACR;;iBAGe;EACf;;wBAsCc,2BAA2B,eAAe,uBAAuB;;iBAiEhE;EACf;EACA;IAAmB;IAAc;IAAoB;;;EAErD;;;;;;;;;;wBAcc,uBACd,cAAc,oBACd,QAAQ,KAAK"}
@@ -1,2 +0,0 @@
1
- const e=/^([A-Za-z_][\w.-]*)=(.*)$/s;function t(e){return`error`in e}function n(e){return e.length>1&&e.startsWith(`"`)&&e.endsWith(`"`)?e.slice(1,-1):e}function r(e){let t=[],n=0;for(;n<e.length;){for(;n<e.length&&/\s/.test(e[n]);)n+=1;if(n>=e.length)break;let r=n,i=!1;for(;n<e.length&&(i||!/\s/.test(e[n]));)e[n]===`"`&&(i=!i),n+=1;t.push({text:e.slice(r,n),start:r})}return t}function i(t){let i=r(t.trim()),a=i[0];if(a===void 0)return{error:`Usage: /workflow-launch <workflow> [key=value …] [prompt]`};let o={},s,c=1;for(;c<i.length;c+=1){let t=i[c],r=e.exec(t.text);if(!r)break;let a=r[1],l=n(r[2]);a===`runner`?s=l:o[a]=l}let l=i[c],u=l===void 0?void 0:t.trim().slice(l.start).trim();return{workflow:n(a.text),...s===void 0?{}:{runner:s},inputs:o,...u===void 0||u===``?{}:{prompt:u}}}function a(e,t){let n=t.trim(),r=n.toLocaleLowerCase();return e.find(e=>e.name.toLocaleLowerCase()===r)??e.find(e=>e.relativePath===n||e.path===n)??e.find(e=>e.relativePath.endsWith(`/${n}`)||e.path.endsWith(`/${n}`))}function o(e,t){let n=[];e.triggers.includes(`user_prompt`)&&(t.prompt??``)===``&&n.push(`This workflow is triggered by a prompt, so it needs one.`);let r=e.inputs.filter(e=>e.required===!0&&(t.inputs[e.name]??``)===``).map(e=>e.name);r.length>0&&n.push(`Missing required input${r.length>1?`s`:``}: ${r.join(`, `)}.`);for(let r of e.inputs){let e=t.inputs[r.name];e===void 0||r.options===void 0||r.options.includes(e)||n.push(`Input ${r.name} must be one of: ${r.options.join(`, `)}.`)}let i=e.runners;return t.runner!==void 0&&i!==void 0&&!i.includes(t.runner)&&n.push(i.length===0?`This workflow declares no runner its steps agree on.`:`Runner ${t.runner} is not one this workflow declares: ${i.join(`, `)}.`),n}export{t as isLaunchParseFailure,i as parseWorkflowLaunchCommand,a as resolveWorkflowEntry,o as validateWorkflowLaunch};
2
- //# sourceMappingURL=index.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/workflowLaunchCommand/index.ts"],"sourcesContent":["/**\n * The `/workflow-launch` line, parsed.\n *\n * WHY A VERB AT ALL:\n * A browser can only send a session a prompt frame, so every cockpit action\n * that has to run inside the session travels as a slash line, the way the\n * agent catalog sends `/run`. The workflow tools answer to the agent, not to a\n * page, and the leader board answers to a keyboard, so neither of them can be\n * what the launch dialog sends.\n *\n * THE SHAPE:\n * /workflow-launch <workflow> [key=value ...] [prompt words]\n *\n * Key-value pairs come first and stop at the first token that is not one, so\n * everything after them is the prompt exactly as it was typed, including any\n * `=` inside it. `runner` is the one reserved key; the rest are the workflow's\n * own `workflow_dispatch` inputs. Values may be double quoted to hold spaces.\n */\n\n/** The reserved key: which runner map entry the run executes with. */\nconst RUNNER_KEY = 'runner';\nconst KEY_VALUE = /^([A-Za-z_][\\w.-]*)=(.*)$/s;\n\nexport interface ParsedWorkflowLaunch {\n /** The workflow's name or its path, as typed; resolution belongs to the caller. */\n workflow: string;\n runner?: string;\n inputs: Record<string, string>;\n prompt?: string;\n}\n\nexport interface WorkflowLaunchParseFailure {\n error: string;\n}\n\nexport function isLaunchParseFailure(\n value: ParsedWorkflowLaunch | WorkflowLaunchParseFailure,\n): value is WorkflowLaunchParseFailure {\n return 'error' in value;\n}\n\n/** Strips one layer of surrounding double quotes, which is how a value holds spaces. */\nfunction unquote(value: string): string {\n return value.length > 1 && value.startsWith('\"') && value.endsWith('\"') ? value.slice(1, -1) : value;\n}\n\n/**\n * Splits on whitespace, keeping double-quoted runs together.\n *\n * Returns each token with the offset it started at, so the caller can take the\n * rest of the line verbatim rather than rebuilding it from tokens and losing\n * the spacing a prompt was written with.\n */\nfunction tokenize(line: string): { text: string; start: number }[] {\n const tokens: { text: string; start: number }[] = [];\n let index = 0;\n while (index < line.length) {\n while (index < line.length && /\\s/.test(line[index] as string)) index += 1;\n if (index >= line.length) break;\n const start = index;\n let quoted = false;\n while (index < line.length && (quoted || !/\\s/.test(line[index] as string))) {\n if (line[index] === '\"') quoted = !quoted;\n index += 1;\n }\n tokens.push({ text: line.slice(start, index), start });\n }\n return tokens;\n}\n\nexport function parseWorkflowLaunchCommand(args: string): ParsedWorkflowLaunch | WorkflowLaunchParseFailure {\n const tokens = tokenize(args.trim());\n const first = tokens[0];\n if (first === undefined) return { error: 'Usage: /workflow-launch <workflow> [key=value …] [prompt]' };\n\n const inputs: Record<string, string> = {};\n let runner: string | undefined;\n let index = 1;\n for (; index < tokens.length; index += 1) {\n const token = tokens[index] as { text: string; start: number };\n const match = KEY_VALUE.exec(token.text);\n if (!match) break;\n const key = match[1] as string;\n const value = unquote(match[2] as string);\n if (key === RUNNER_KEY) runner = value;\n else inputs[key] = value;\n }\n\n const rest = tokens[index];\n const prompt = rest === undefined ? undefined : args.trim().slice(rest.start).trim();\n return {\n workflow: unquote(first.text),\n ...(runner === undefined ? {} : { runner }),\n inputs,\n ...(prompt === undefined || prompt === '' ? {} : { prompt }),\n };\n}\n\n/** The line the cockpit sends, built from what its dialog collected. */\nexport function workflowLaunchCommand(request: ParsedWorkflowLaunch): string {\n const quote = (value: string): string => (/\\s/.test(value) ? `\"${value}\"` : value);\n const pairs = [\n ...(request.runner === undefined ? [] : [`${RUNNER_KEY}=${quote(request.runner)}`]),\n ...Object.entries(request.inputs).map(([key, value]) => `${key}=${quote(value)}`),\n ];\n const prompt = request.prompt?.trim();\n return ['/workflow-launch', quote(request.workflow), ...pairs, ...(prompt ? [prompt] : [])].join(' ');\n}\n\n/** One catalog row, as much of it as resolution needs. */\nexport interface LaunchResolvableWorkflow {\n name: string;\n path: string;\n relativePath: string;\n}\n\n/**\n * The workflow a token names: its name, its path in the repository, or the\n * tail of that path. Case-insensitive on the name, because the catalog shows\n * names as their author capitalised them and nobody types that back exactly.\n */\nexport function resolveWorkflowEntry<T extends LaunchResolvableWorkflow>(\n entries: readonly T[],\n token: string,\n): T | undefined {\n const needle = token.trim();\n const lower = needle.toLocaleLowerCase();\n return (\n entries.find((entry) => entry.name.toLocaleLowerCase() === lower) ??\n entries.find((entry) => entry.relativePath === needle || entry.path === needle) ??\n entries.find((entry) => entry.relativePath.endsWith(`/${needle}`) || entry.path.endsWith(`/${needle}`))\n );\n}\n\n/** What a workflow declares, as much of it as a launch has to satisfy. */\nexport interface LaunchRequirements {\n triggers: readonly string[];\n inputs: readonly { name: string; required?: boolean; options?: readonly string[] }[];\n /** Absent means the workflow names no runner map, so any runner will do. */\n runners?: readonly string[];\n}\n\n/** The trigger that makes a prompt mandatory: the run waits for one otherwise. */\nconst USER_PROMPT_TRIGGER = 'user_prompt';\n\n/**\n * Everything wrong with a launch, before it is started.\n *\n * Checked here rather than left to the engine because the failure would\n * otherwise be a run that starts and then waits forever for terminal input, or\n * one that dies on a runner its steps never declared. Both read as the launch\n * having worked.\n */\nexport function validateWorkflowLaunch(\n requirements: LaunchRequirements,\n parsed: Pick<ParsedWorkflowLaunch, 'inputs' | 'prompt' | 'runner'>,\n): string[] {\n const problems: string[] = [];\n if (requirements.triggers.includes(USER_PROMPT_TRIGGER) && (parsed.prompt ?? '') === '') {\n problems.push('This workflow is triggered by a prompt, so it needs one.');\n }\n const missing = requirements.inputs\n .filter((input) => input.required === true && (parsed.inputs[input.name] ?? '') === '')\n .map((input) => input.name);\n if (missing.length > 0)\n problems.push(`Missing required input${missing.length > 1 ? 's' : ''}: ${missing.join(', ')}.`);\n for (const input of requirements.inputs) {\n const value = parsed.inputs[input.name];\n if (value === undefined || input.options === undefined || input.options.includes(value)) continue;\n problems.push(`Input ${input.name} must be one of: ${input.options.join(', ')}.`);\n }\n const declared = requirements.runners;\n if (parsed.runner !== undefined && declared !== undefined && !declared.includes(parsed.runner)) {\n problems.push(\n declared.length === 0\n ? 'This workflow declares no runner its steps agree on.'\n : `Runner ${parsed.runner} is not one this workflow declares: ${declared.join(', ')}.`,\n );\n }\n return problems;\n}\n"],"mappings":"AAoBA,MACM,EAAY,6BAclB,SAAgB,EACd,EACqC,CACrC,MAAO,UAAW,CACpB,CAGA,SAAS,EAAQ,EAAuB,CACtC,OAAO,EAAM,OAAS,GAAK,EAAM,WAAW,GAAG,GAAK,EAAM,SAAS,GAAG,EAAI,EAAM,MAAM,EAAG,EAAE,EAAI,CACjG,CASA,SAAS,EAAS,EAAiD,CACjE,IAAM,EAA4C,CAAC,EAC/C,EAAQ,EACZ,KAAO,EAAQ,EAAK,QAAQ,CAC1B,KAAO,EAAQ,EAAK,QAAU,KAAK,KAAK,EAAK,EAAgB,GAAG,GAAS,EACzE,GAAI,GAAS,EAAK,OAAQ,MAC1B,IAAM,EAAQ,EACV,EAAS,GACb,KAAO,EAAQ,EAAK,SAAW,GAAU,CAAC,KAAK,KAAK,EAAK,EAAgB,IACnE,EAAK,KAAW,MAAK,EAAS,CAAC,GACnC,GAAS,EAEX,EAAO,KAAK,CAAE,KAAM,EAAK,MAAM,EAAO,CAAK,EAAG,OAAM,CAAC,CACvD,CACA,OAAO,CACT,CAEA,SAAgB,EAA2B,EAAiE,CAC1G,IAAM,EAAS,EAAS,EAAK,KAAK,CAAC,EAC7B,EAAQ,EAAO,GACrB,GAAI,IAAU,IAAA,GAAW,MAAO,CAAE,MAAO,2DAA4D,EAErG,IAAM,EAAiC,CAAC,EACpC,EACA,EAAQ,EACZ,KAAO,EAAQ,EAAO,OAAQ,GAAS,EAAG,CACxC,IAAM,EAAQ,EAAO,GACf,EAAQ,EAAU,KAAK,EAAM,IAAI,EACvC,GAAI,CAAC,EAAO,MACZ,IAAM,EAAM,EAAM,GACZ,EAAQ,EAAQ,EAAM,EAAY,EACpC,IAAQ,SAAY,EAAS,EAC5B,EAAO,GAAO,CACrB,CAEA,IAAM,EAAO,EAAO,GACd,EAAS,IAAS,IAAA,GAAY,IAAA,GAAY,EAAK,KAAK,CAAC,CAAC,MAAM,EAAK,KAAK,CAAC,CAAC,KAAK,EACnF,MAAO,CACL,SAAU,EAAQ,EAAM,IAAI,EAC5B,GAAI,IAAW,IAAA,GAAY,CAAC,EAAI,CAAE,QAAO,EACzC,SACA,GAAI,IAAW,IAAA,IAAa,IAAW,GAAK,CAAC,EAAI,CAAE,QAAO,CAC5D,CACF,CAyBA,SAAgB,EACd,EACA,EACe,CACf,IAAM,EAAS,EAAM,KAAK,EACpB,EAAQ,EAAO,kBAAkB,EACvC,OACE,EAAQ,KAAM,GAAU,EAAM,KAAK,kBAAkB,IAAM,CAAK,GAChE,EAAQ,KAAM,GAAU,EAAM,eAAiB,GAAU,EAAM,OAAS,CAAM,GAC9E,EAAQ,KAAM,GAAU,EAAM,aAAa,SAAS,IAAI,GAAQ,GAAK,EAAM,KAAK,SAAS,IAAI,GAAQ,CAAC,CAE1G,CAqBA,SAAgB,EACd,EACA,EACU,CACV,IAAM,EAAqB,CAAC,EACxB,EAAa,SAAS,SAAS,aAAmB,IAAM,EAAO,QAAU,MAAQ,IACnF,EAAS,KAAK,0DAA0D,EAE1E,IAAM,EAAU,EAAa,OAC1B,OAAQ,GAAU,EAAM,WAAa,KAAS,EAAO,OAAO,EAAM,OAAS,MAAQ,EAAE,CAAC,CACtF,IAAK,GAAU,EAAM,IAAI,EACxB,EAAQ,OAAS,GACnB,EAAS,KAAK,yBAAyB,EAAQ,OAAS,EAAI,IAAM,GAAG,IAAI,EAAQ,KAAK,IAAI,EAAE,EAAE,EAChG,IAAK,IAAM,KAAS,EAAa,OAAQ,CACvC,IAAM,EAAQ,EAAO,OAAO,EAAM,MAC9B,IAAU,IAAA,IAAa,EAAM,UAAY,IAAA,IAAa,EAAM,QAAQ,SAAS,CAAK,GACtF,EAAS,KAAK,SAAS,EAAM,KAAK,mBAAmB,EAAM,QAAQ,KAAK,IAAI,EAAE,EAAE,CAClF,CACA,IAAM,EAAW,EAAa,QAQ9B,OAPI,EAAO,SAAW,IAAA,IAAa,IAAa,IAAA,IAAa,CAAC,EAAS,SAAS,EAAO,MAAM,GAC3F,EAAS,KACP,EAAS,SAAW,EAChB,uDACA,UAAU,EAAO,OAAO,sCAAsC,EAAS,KAAK,IAAI,EAAE,EACxF,EAEK,CACT"}
@@ -1,2 +0,0 @@
1
- const e=require("../../schemas/workflowPi.cjs"),t=require("../piToolBridge/index.cjs");let n=require("node:path");function r(e){return e&&typeof e==`object`&&!Array.isArray(e)?{...e}:{}}function i(e){return r(e.on)}function a(t){let n=i(t).workflow_dispatch;if(n==null)return{};let r=e.workflowDispatchInputsSchema.safeParse(n);if(!r.success)throw Error(`Invalid workflow_dispatch input definitions: ${r.error.message}`);return r.data.inputs??{}}function o(e){return Error(t.toolResultText(e)||`Workflow launch failed.`)}function s(r){if(r.isError)throw o(r);let i;try{i=JSON.parse(t.toolResultText(r))}catch(e){throw Error(`Workflow catalog returned invalid JSON.`,{cause:e})}let a=e.workflowCatalogPageSchema.safeParse(i);if(!a.success)throw Error(`Workflow catalog response was invalid: ${a.error.message}`);return{directory:a.data.directory,hasNextPage:a.data.hasNextPage,page:a.data.page,pageSize:a.data.pageSize,workflows:a.data.workflows.map(e=>({description:e.description,name:e.name,path:(0,n.resolve)(a.data.directory,e.path),relativePath:e.path,tags:e.tags}))}}async function c(e,t){let n=[],r=1,i=!0;for(;i;){let a={directory:t,page:r,pageSize:100},o=s(await e.execute(a));n.push(...o.workflows),i=o.hasNextPage,r+=1}return n}async function l(e){for(;;){let t=await e.editor(`Workflow prompt`);if(t===void 0)return;let n=t.trim();if(n)return n;e.notify(`A workflow prompt is required.`,`warning`)}}async function u(e,t,n){let r=n.description?`${t}: ${n.description}`:`Workflow input: ${t}`;if(n.options&&n.options.length>0)return e.select(r,n.options);if(n.type===`boolean`)return e.select(r,[`true`,`false`]);for(;;){let i=await e.input(r,n.default);if(i===void 0)return;let a=i.trim()||n.default;if(a||!n.required)return a||void 0;e.notify(`A value is required for workflow input ${t}.`,`warning`)}}async function d(e,t){let n=i(e),r=Object.hasOwn(n,`user_prompt`),o=r?await l(t):void 0;if(r&&o===void 0)return;let s=a(e),c={};for(let[e,n]of Object.entries(s)){let r=await u(t,e,n);if(r===void 0&&n.required)return;r!==void 0&&(c[e]=r)}return{...o===void 0?{}:{prompt:o},...Object.keys(c).length===0?{}:{inputs:c}}}function f(e,t){return e.name?e.name:e.uses?e.uses:e.id?e.id:`step ${t+1}`}function p(e){return Object.entries(a(e)).map(([e,t])=>({name:e,...t.description?{description:t.description}:{},...t.required===void 0?{}:{required:t.required},...t.default===void 0?{}:{default:t.default},...t.options?{options:t.options}:{}}))}function m(e){return Object.entries(e.jobs??{}).map(([e,t])=>({name:e,steps:(t.steps??[]).map((e,t)=>f(e,t))}))}function h(e,t){try{let n=t.parseWorkflow(e),r=t.compatibleRunners(n);return{triggers:Object.keys(i(n)),inputs:p(n),jobs:m(n),...r?{runners:r}:{}}}catch(e){return{triggers:[],inputs:[],jobs:[],error:e instanceof Error?e.message:String(e)}}}async function g(e,t,n){let r=e.parseWorkflow(t.path),i=e.compatibleRunners(r);if(i&&i.length===0)throw Error(`No compatible runner is available for this workflow.`);let a;if(i&&(a=await e.ui.select(`Select a workflow runner`,i),a===void 0))return;let o=await d(r,e.ui);if(o===void 0)return;let s={workflowPath:t.path,...a===void 0?{}:{runner:a},...typeof r.workspace==`string`?{workspace:r.workspace}:{},...o};return e.launch(s,n)}exports.collectWorkflowInputs=d,exports.launchWorkflowEntry=g,exports.loadWorkflowCatalog=c,exports.parseWorkflowCatalogPage=s,exports.summarizeWorkflowFile=h;
2
- //# sourceMappingURL=index.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","names":["workflowDispatchInputsSchema","toolResultText","workflowCatalogPageSchema","resolve"],"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
- import{workflowCatalogPageSchema as e,workflowDispatchInputsSchema as t}from"../../schemas/workflowPi.mjs";import{toolResultText as n}from"../piToolBridge/index.mjs";import{resolve as r}from"node:path";function i(e){return e&&typeof e==`object`&&!Array.isArray(e)?{...e}:{}}function a(e){return i(e.on)}function o(e){let n=a(e).workflow_dispatch;if(n==null)return{};let r=t.safeParse(n);if(!r.success)throw Error(`Invalid workflow_dispatch input definitions: ${r.error.message}`);return r.data.inputs??{}}function s(e){return Error(n(e)||`Workflow launch failed.`)}function c(t){if(t.isError)throw s(t);let i;try{i=JSON.parse(n(t))}catch(e){throw Error(`Workflow catalog returned invalid JSON.`,{cause:e})}let a=e.safeParse(i);if(!a.success)throw Error(`Workflow catalog response was invalid: ${a.error.message}`);return{directory:a.data.directory,hasNextPage:a.data.hasNextPage,page:a.data.page,pageSize:a.data.pageSize,workflows:a.data.workflows.map(e=>({description:e.description,name:e.name,path:r(a.data.directory,e.path),relativePath:e.path,tags:e.tags}))}}async function l(e,t){let n=[],r=1,i=!0;for(;i;){let a={directory:t,page:r,pageSize:100},o=c(await e.execute(a));n.push(...o.workflows),i=o.hasNextPage,r+=1}return n}async function u(e){for(;;){let t=await e.editor(`Workflow prompt`);if(t===void 0)return;let n=t.trim();if(n)return n;e.notify(`A workflow prompt is required.`,`warning`)}}async function d(e,t,n){let r=n.description?`${t}: ${n.description}`:`Workflow input: ${t}`;if(n.options&&n.options.length>0)return e.select(r,n.options);if(n.type===`boolean`)return e.select(r,[`true`,`false`]);for(;;){let i=await e.input(r,n.default);if(i===void 0)return;let a=i.trim()||n.default;if(a||!n.required)return a||void 0;e.notify(`A value is required for workflow input ${t}.`,`warning`)}}async function f(e,t){let n=a(e),r=Object.hasOwn(n,`user_prompt`),i=r?await u(t):void 0;if(r&&i===void 0)return;let s=o(e),c={};for(let[e,n]of Object.entries(s)){let r=await d(t,e,n);if(r===void 0&&n.required)return;r!==void 0&&(c[e]=r)}return{...i===void 0?{}:{prompt:i},...Object.keys(c).length===0?{}:{inputs:c}}}function p(e,t){return e.name?e.name:e.uses?e.uses:e.id?e.id:`step ${t+1}`}function m(e){return Object.entries(o(e)).map(([e,t])=>({name:e,...t.description?{description:t.description}:{},...t.required===void 0?{}:{required:t.required},...t.default===void 0?{}:{default:t.default},...t.options?{options:t.options}:{}}))}function h(e){return Object.entries(e.jobs??{}).map(([e,t])=>({name:e,steps:(t.steps??[]).map((e,t)=>p(e,t))}))}function g(e,t){try{let n=t.parseWorkflow(e),r=t.compatibleRunners(n);return{triggers:Object.keys(a(n)),inputs:m(n),jobs:h(n),...r?{runners:r}:{}}}catch(e){return{triggers:[],inputs:[],jobs:[],error:e instanceof Error?e.message:String(e)}}}async function _(e,t,n){let r=e.parseWorkflow(t.path),i=e.compatibleRunners(r);if(i&&i.length===0)throw Error(`No compatible runner is available for this workflow.`);let a;if(i&&(a=await e.ui.select(`Select a workflow runner`,i),a===void 0))return;let o=await f(r,e.ui);if(o===void 0)return;let s={workflowPath:t.path,...a===void 0?{}:{runner:a},...typeof r.workspace==`string`?{workspace:r.workspace}:{},...o};return e.launch(s,n)}export{f as collectWorkflowInputs,_ as launchWorkflowEntry,l as loadWorkflowCatalog,c as parseWorkflowCatalogPage,g as summarizeWorkflowFile};
2
- //# sourceMappingURL=index.mjs.map