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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/LICENSE +6 -0
  2. package/README.md +14 -8
  3. package/dist/_virtual/_rolldown/runtime.cjs +1 -0
  4. package/dist/adapters/pi/extension.cjs +1 -1
  5. package/dist/adapters/pi/extension.mjs +1 -1
  6. package/dist/adapters/pi/workflow/piExtension.cjs +6 -8
  7. package/dist/adapters/pi/workflow/piExtension.cjs.map +1 -1
  8. package/dist/adapters/pi/workflow/piExtension.mjs +6 -8
  9. package/dist/adapters/pi/workflow/piExtension.mjs.map +1 -1
  10. package/dist/adapters/pi/workflow/piTools.cjs +3 -3
  11. package/dist/adapters/pi/workflow/piTools.mjs +3 -3
  12. package/dist/adapters/pi/workflow/workflowLauncher.cjs +1 -1
  13. package/dist/adapters/pi/workflow/workflowLauncher.cjs.map +1 -1
  14. package/dist/adapters/pi/workflow/workflowLauncher.mjs +1 -1
  15. package/dist/adapters/pi/workflow/workflowLauncher.mjs.map +1 -1
  16. package/dist/adapters/workflowCatalogChannel.cjs +2 -0
  17. package/dist/adapters/workflowCatalogChannel.cjs.map +1 -0
  18. package/dist/adapters/workflowCatalogChannel.d.cts +23 -0
  19. package/dist/adapters/workflowCatalogChannel.d.cts.map +1 -0
  20. package/dist/adapters/workflowCatalogChannel.d.mts +23 -0
  21. package/dist/adapters/workflowCatalogChannel.d.mts.map +1 -0
  22. package/dist/adapters/workflowCatalogChannel.mjs +2 -0
  23. package/dist/adapters/workflowCatalogChannel.mjs.map +1 -0
  24. package/dist/adapters/workflowHubApi.cjs +2 -0
  25. package/dist/adapters/workflowHubApi.cjs.map +1 -0
  26. package/dist/adapters/workflowHubApi.d.cts +24 -0
  27. package/dist/adapters/workflowHubApi.d.cts.map +1 -0
  28. package/dist/adapters/workflowHubApi.d.mts +24 -0
  29. package/dist/adapters/workflowHubApi.d.mts.map +1 -0
  30. package/dist/adapters/workflowHubApi.mjs +2 -0
  31. package/dist/adapters/workflowHubApi.mjs.map +1 -0
  32. package/dist/adapters/workflowWatcher.cjs +2 -0
  33. package/dist/adapters/workflowWatcher.cjs.map +1 -0
  34. package/dist/adapters/workflowWatcher.d.cts +24 -0
  35. package/dist/adapters/workflowWatcher.d.cts.map +1 -0
  36. package/dist/adapters/workflowWatcher.d.mts +24 -0
  37. package/dist/adapters/workflowWatcher.d.mts.map +1 -0
  38. package/dist/adapters/workflowWatcher.mjs +2 -0
  39. package/dist/adapters/workflowWatcher.mjs.map +1 -0
  40. package/dist/adapters/workflowsHubChannel.cjs +2 -0
  41. package/dist/adapters/workflowsHubChannel.cjs.map +1 -0
  42. package/dist/adapters/workflowsHubChannel.d.cts +20 -0
  43. package/dist/adapters/workflowsHubChannel.d.cts.map +1 -0
  44. package/dist/adapters/workflowsHubChannel.d.mts +20 -0
  45. package/dist/adapters/workflowsHubChannel.d.mts.map +1 -0
  46. package/dist/adapters/workflowsHubChannel.mjs +2 -0
  47. package/dist/adapters/workflowsHubChannel.mjs.map +1 -0
  48. package/dist/hubApi.cjs +1 -0
  49. package/dist/hubApi.d.cts +2 -0
  50. package/dist/hubApi.d.mts +2 -0
  51. package/dist/hubApi.mjs +1 -0
  52. package/dist/schemas/workflowPi.cjs +1 -1
  53. package/dist/schemas/workflowPi.cjs.map +1 -1
  54. package/dist/schemas/workflowPi.mjs +1 -1
  55. package/dist/schemas/workflowPi.mjs.map +1 -1
  56. package/dist/services/webWorkflowCatalog.cjs +2 -0
  57. package/dist/services/webWorkflowCatalog.cjs.map +1 -0
  58. package/dist/services/webWorkflowCatalog.d.cts +49 -0
  59. package/dist/services/webWorkflowCatalog.d.cts.map +1 -0
  60. package/dist/services/webWorkflowCatalog.d.mts +49 -0
  61. package/dist/services/webWorkflowCatalog.d.mts.map +1 -0
  62. package/dist/services/webWorkflowCatalog.mjs +2 -0
  63. package/dist/services/webWorkflowCatalog.mjs.map +1 -0
  64. package/dist/services/workflowLaunchCommand.cjs +2 -0
  65. package/dist/services/workflowLaunchCommand.cjs.map +1 -0
  66. package/dist/services/workflowLaunchCommand.mjs +2 -0
  67. package/dist/services/workflowLaunchCommand.mjs.map +1 -0
  68. package/dist/services/workflowRuns.cjs +3 -0
  69. package/dist/services/workflowRuns.cjs.map +1 -0
  70. package/dist/services/workflowRuns.d.cts +17 -0
  71. package/dist/services/workflowRuns.d.cts.map +1 -0
  72. package/dist/services/workflowRuns.d.mts +17 -0
  73. package/dist/services/workflowRuns.d.mts.map +1 -0
  74. package/dist/services/workflowRuns.mjs +3 -0
  75. package/dist/services/workflowRuns.mjs.map +1 -0
  76. package/dist/services/workflowTerminal.cjs +2 -0
  77. package/dist/services/workflowTerminal.cjs.map +1 -0
  78. package/dist/services/workflowTerminal.mjs +2 -0
  79. package/dist/services/workflowTerminal.mjs.map +1 -0
  80. package/dist/tui/workflow/workflowCatalog.cjs +1 -1
  81. package/dist/tui/workflow/workflowCatalog.cjs.map +1 -1
  82. package/dist/tui/workflow/workflowCatalog.mjs +1 -1
  83. package/dist/tui/workflow/workflowCatalog.mjs.map +1 -1
  84. package/dist/types/webWorkflowTerminal.cjs +2 -0
  85. package/dist/types/webWorkflowTerminal.cjs.map +1 -0
  86. package/dist/types/webWorkflowTerminal.mjs +2 -0
  87. package/dist/types/webWorkflowTerminal.mjs.map +1 -0
  88. package/dist/types/webWorkflows.cjs +2 -0
  89. package/dist/types/webWorkflows.cjs.map +1 -0
  90. package/dist/types/webWorkflows.d.cts +95 -0
  91. package/dist/types/webWorkflows.d.cts.map +1 -0
  92. package/dist/types/webWorkflows.d.mts +95 -0
  93. package/dist/types/webWorkflows.d.mts.map +1 -0
  94. package/dist/types/webWorkflows.mjs +2 -0
  95. package/dist/types/webWorkflows.mjs.map +1 -0
  96. package/dist/webHub.cjs +1 -0
  97. package/dist/webHub.d.cts +3 -0
  98. package/dist/webHub.d.mts +3 -0
  99. package/dist/webHub.mjs +1 -0
  100. package/package.json +47 -11
  101. package/src/prompts/doompi-author-workflow/SKILL.md +1 -3
  102. package/src/types/webWorkflowTerminal.ts +92 -0
  103. package/src/types/webWorkflows.ts +145 -0
  104. package/web/ArtifactsPane.tsx +433 -0
  105. package/web/LICENSE +29 -0
  106. package/web/LaunchWorkflowDialog.tsx +249 -0
  107. package/web/StepTerminalPanel.tsx +251 -0
  108. package/web/WorkflowCatalogDrawer.tsx +228 -0
  109. package/web/WorkflowToolMessage.tsx +54 -0
  110. package/web/WorkflowsActivitySection.tsx +152 -0
  111. package/web/WorkflowsPanel.tsx +737 -0
  112. package/web/ansiSpans.ts +230 -0
  113. package/web/catalogStore.ts +112 -0
  114. package/web/index.ts +61 -0
  115. package/web/launchLine.ts +66 -0
  116. package/web/runDuration.ts +15 -0
  117. package/web/terminalApi.ts +151 -0
  118. package/web/tsconfig.json +22 -0
  119. package/web/workflowActivity.ts +102 -0
  120. package/web/workflowToolRender.ts +585 -0
  121. package/web/workflowsStore.ts +90 -0
  122. package/dist/tui/workflow/terminalGrid.cjs +0 -2
  123. package/dist/tui/workflow/terminalGrid.cjs.map +0 -1
  124. package/dist/tui/workflow/terminalGrid.mjs +0 -2
  125. package/dist/tui/workflow/terminalGrid.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
- const e=require("../../../schemas/workflowPi.cjs"),t=require("./piToolBridge.cjs"),n=require("../../../tui/workflow/workflowToolRender.cjs");let r=require("@agimon-ai/doompi-extension-contracts/child-process"),i=require("@agimon-ai/workflow-mcp"),a=require("zod");const o={workflowPath:!0,workspace:!0,runner:!0,name:!0,env:!0,prompt:!0,job:!0,inputs:!0},s=[`list_workflows`,`launch_workflow`,`workflow_run`],[c,l,u]=s;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,n,r){e.then(e=>{if(e.kind===`error`){n.onLateFailure?.(g(e.error),r);return}e.result.isError&&n.onLateFailure?.(Error(t.toolResultText(e.result)||`Workflow launch failed.`),r)})}function b(e){return{async execute(n,i,a){let o=(0,r.resolveRootSessionId)(i.sessionManager.getSessionId());e.observeSession?.(o);let s=n.env?.AGIFLOW_JOB_KIND?.trim(),c=n.env?.AGIFLOW_JOB_ID?.trim();if(!!s!=!!c)throw Error(`Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.`);if(s&&!n.prompt?.trim())throw Error(`Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.`);d(a,l,`Checking workflow capacity...`);let u=t.resolveMaxConcurrent(),f=await e.activeRunCount?.()??0;if(f>=u)throw Error(t.withOptions(`This session is at capacity: ${f}/${u} 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(n.runner){let t=e.rejectRunner?.(n.workflowPath,n.runner);if(t)throw Error(t)}d(a,l,`Launching workflow ${n.workflowPath}...`);let p={...n.env};if(process.env.AGIFLOW_DISPATCH_CONTEXT_FILE)for(let e of[`AGIFLOW_ORGANIZATION_ID`,`AGIFLOW_PROJECT_ID`,`AGIFLOW_DEVICE_ID`,`BACKEND_AGIFLOW_API_ENDPOINT`,`AGIFLOW_DISPATCH_SECRET_FILE`]){let t=process.env[e];t&&(p[e]=t)}let m=Date.now(),_=e.trackPendingRun(e.runTool.execute({...n,env:{...p,[t.PI_SESSION_ENV]:o}})).then(e=>({kind:`value`,result:e}),e=>({kind:`error`,error:e})),b=await v(_,e,{sessionId:o,since:m,workflowPath:n.workflowPath});if(b.kind===`settled`){let n=b.outcome;if(n.kind===`error`)throw g(n.error);let r=n.result;if(r.isError)throw Error(t.withOptions(t.toolResultText(r)||`Workflow launch failed.`,h));return await e.onLaunch?.(i),r}return y(_,e,i),await e.onLaunch?.(i),{content:[{type:`text`,text:b.kind===`record`?t.launchedRunSummary(b.record):t.launchHandoffSummary(n.workflowPath)}]}}}}function x(s,p={}){let g=p.feature??(0,i.createEmbeddedWorkflowFeature)(),_=p.runTool??g.runTool,v=p.listWorkflowsTool??g.listWorkflowsTool,y=p.recoverTool??g.createRecoverTool(),x=p.controlTool??g.createControlTool(),S=p.trackPendingRun??(e=>e),C=p.launchExecutor??b({activeRunCount:p.activeRunCount,onLaunch:p.onLaunch,observeSession:p.observeSession,rejectRunner:p.rejectRunner,runTool:_,trackPendingRun:S});s.registerTool({name:c,label:`List Workflows`,description:`List the workflow definitions available in this repository.`,promptSnippet:`List available workflow definitions`,parameters:a.z.toJSONSchema(v.getInputSchema()),renderShell:`self`,renderCall:(e,t)=>n.renderWorkflowToolCall(c,e,t),renderResult:(e,t,r,i)=>n.renderWorkflowToolResult(c,i.args,e,{...t,isError:i.isError},r),async execute(e,n){return t.toAgentToolResult(c,await v.execute(n))}}),s.registerTool({name:l,label:`Launch Workflow`,description:`Start a workflow run. Returns once the run has started, not once it finishes; use workflow_run with action status or wait for the completion notice.`,promptSnippet:`Start a workflow run and return its identifier`,promptGuidelines:[`Before launching, call list_workflows and pick the workflow whose own description matches the work. A workflow description is the source of truth; never infer one from its filename. If nothing matches, say so and stop rather than launching an approximate fit.`,`launch_workflow returns when the run has STARTED, not when it has finished. Never report success from its result. Poll workflow_run with action status, or wait for the completion notice the extension delivers on its own.`,`When dispatching Agiflow work, pass AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together through env plus a non-empty prompt. The id alone is not a key, and omitting prompt leaves user_prompt workflows waiting for terminal input.`,`A workflow claims its own job in its pre step. Never claim a job yourself. JOB_ALREADY_CLAIMED or HTTP 409 means another worker won the race, so report contention and do not unlock, release, or retry automatically.`,`If a result says WORKFLOW_NOT_OWNED or a release failed, inspect current ownership and ask the user before any release or unlock. Never force another worker’s lock.`,`If a running workflow is not found, call workflow_run with action status to re-check current state before retrying.`,`The per-session concurrency ceiling is a real limit. If launch_workflow refuses, wait for a run to finish; do not launch through the shell to get around it.`],parameters:a.z.toJSONSchema(_.getInputSchema().pick(o)),renderShell:`self`,renderCall:(e,t)=>n.renderWorkflowToolCall(l,e,t),renderResult:(e,t,r,i)=>n.renderWorkflowToolResult(l,i.args,e,{...t,isError:i.isError},r),async execute(e,n,r,i,a){let o=n,s=await C.execute(o,a,i);return f(t.toAgentToolResult(l,s,h),`Next: call workflow_run with action status and verify the recorded stage before reporting the workflow outcome.`)}}),s.registerTool({name:u,label:`Workflow Run`,description:`Inspect or control one workflow run. Use action status, tail, recovery-evidence, follow, open, pause, resume, stop, or recover. Every action requires an exact run key.`,promptSnippet:`Inspect or control a workflow run`,promptGuidelines:[`Use action status as the authority for stage, outcome, execution state, and pause cursor. A run that started is not a run that succeeded.`,`Use action pause or resume for cooperative lifecycle control. Pause is not stop: the run keeps its stage running, capacity, services, and heartbeat while paused.`,`Use action stop only when the user requests stopping a run. A user-requested stop is not a failure and needs no failure diagnosis.`,`Do not use follow or tail merely to wait for a healthy run; use status or the completion notice. Tail returns chat-safe status without raw PTY frames. Use follow only when the user asks to pin live progress, and open only when a human wants the recorded launcher foregrounded.`,`Use action recovery-evidence for terminal-only, durable status and artifacts before recovery. It is the only inspection path that may read a failed run from an earlier session and it never reads a live launcher.`,"Use action recover only after loading the `workflow-recovery` skill and gathering its required evidence. Recovery may adopt a terminal failed run from an earlier Pi session; it never grants cross-session control of running work. Never edit issue.md or repair.json, and verify real process and registry progress before recovery.",`Never unlock or force-release an Agiflow claim from this tool.`],parameters:a.z.toJSONSchema(e.workflowRunInputSchema),renderShell:`self`,renderCall:(e,t)=>n.renderWorkflowToolCall(u,e,t),renderResult:(e,t,r,i)=>n.renderWorkflowToolResult(u,i.args,e,{...t,isError:i.isError},r),async execute(n,i,a,o,s){let c=e.workflowRunInputSchema.parse(i),l=(0,r.resolveRootSessionId)(s.sessionManager.getSessionId());p.observeSession?.(l);let h={runKey:c.runKey,workspace:c.workspace},g=async()=>{if(!p.requireSessionRun)throw Error(`Workflow run ownership is not available in this session.`);return p.requireSessionRun(h,l)};if(c.action===`status`){let e=await g();return{content:[{type:`text`,text:JSON.stringify(e,null,2)}],details:{tool:u}}}if(c.action===`tail`){if(!p.tailRun)throw Error(`Reading workflow output is not available in this session.`);let e=await g();return d(o,u,`Reading output for workflow ${c.runKey}...`),await p.tailRun(h,s),{content:[{type:`text`,text:[`Workflow: ${e.displayName} (${e.runKey})`,`Stage: ${e.effectiveStage??e.stage}`,`Raw launcher output is hidden from chat. Use action follow or open to view it.`].join(`
2
- `)}],details:{tool:u}}}if(c.action===`recovery-evidence`){if(!p.readRecoveryEvidence)throw Error(`Reading workflow recovery evidence is not available in this session.`);return d(o,u,`Reading recovery evidence for workflow ${c.runKey}...`),{content:[{type:`text`,text:await p.readRecoveryEvidence(h,s)}],details:{tool:u}}}if(c.action===`follow`){if(!p.followRun)throw Error(`Following workflow output is not available in this session.`);return await g(),d(o,u,`Following workflow ${c.runKey}...`),{content:[{type:`text`,text:await p.followRun(h,s)}],details:{tool:u}}}if(c.action===`open`){if(!p.openRun)throw Error(`Opening a workflow launcher is not available in this session.`);return await g(),d(o,u,`Opening workflow ${c.runKey}...`),{content:[{type:`text`,text:await p.openRun(h,s)}],details:{tool:u}}}if(c.action===`recover`){if(!p.requireRecoverableRun)throw Error(`Workflow recovery ownership transfer is not available in this session.`);let e=await p.requireRecoverableRun(h),n={runKey:e.runKey,workspace:e.workspace};if(d(o,u,`Recovering workflow ${c.runKey}...`),!c.dryRun){let e=await p.delegateRecovery?.({...n,runner:c.runner},s);if(e){let t=m(c.action,c.runKey);return await p.onLaunch?.(s),{content:[{type:`text`,text:[e,t[0]].join(`
1
+ const e=require("./piToolBridge.cjs"),t=require("../../../schemas/workflowPi.cjs"),n=require("../../../tui/workflow/workflowToolRender.cjs");let r=require("@agimon-ai/workflow-mcp"),i=require("@agimon-ai/doompi-extension-contracts/child-process"),a=require("zod");const o={workflowPath:!0,workspace:!0,runner:!0,name:!0,env:!0,prompt:!0,job:!0,inputs:!0},s=[`list_workflows`,`launch_workflow`,`workflow_run`],[c,l,u]=s;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(t,n,r){t.then(t=>{if(t.kind===`error`){n.onLateFailure?.(g(t.error),r);return}t.result.isError&&n.onLateFailure?.(Error(e.toolResultText(t.result)||`Workflow launch failed.`),r)})}function b(t){return{async execute(n,r,a){let o=(0,i.resolveRootSessionId)(r.sessionManager.getSessionId());t.observeSession?.(o);let s=n.env?.AGIFLOW_JOB_KIND?.trim(),c=n.env?.AGIFLOW_JOB_ID?.trim();if(!!s!=!!c)throw Error(`Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.`);if(s&&!n.prompt?.trim())throw Error(`Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.`);d(a,l,`Checking workflow capacity...`);let u=e.resolveMaxConcurrent(),f=await t.activeRunCount?.()??0;if(f>=u)throw Error(e.withOptions(`This session is at capacity: ${f}/${u} 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(n.runner){let e=t.rejectRunner?.(n.workflowPath,n.runner);if(e)throw Error(e)}d(a,l,`Launching workflow ${n.workflowPath}...`);let p={...n.env};if(process.env.AGIFLOW_DISPATCH_CONTEXT_FILE)for(let e of[`AGIFLOW_ORGANIZATION_ID`,`AGIFLOW_PROJECT_ID`,`AGIFLOW_DEVICE_ID`,`BACKEND_AGIFLOW_API_ENDPOINT`,`AGIFLOW_DISPATCH_SECRET_FILE`]){let t=process.env[e];t&&(p[e]=t)}let m=Date.now(),_=t.trackPendingRun(t.runTool.execute({...n,env:{...p,[e.PI_SESSION_ENV]:o}})).then(e=>({kind:`value`,result:e}),e=>({kind:`error`,error:e})),b=await v(_,t,{sessionId:o,since:m,workflowPath:n.workflowPath});if(b.kind===`settled`){let n=b.outcome;if(n.kind===`error`)throw g(n.error);let i=n.result;if(i.isError)throw Error(e.withOptions(e.toolResultText(i)||`Workflow launch failed.`,h));return await t.onLaunch?.(r),i}return y(_,t,r),await t.onLaunch?.(r),{content:[{type:`text`,text:b.kind===`record`?e.launchedRunSummary(b.record):e.launchHandoffSummary(n.workflowPath)}]}}}}function x(s,p={}){let g=p.feature??(0,r.createEmbeddedWorkflowFeature)(),_=p.runTool??g.runTool,v=p.listWorkflowsTool??g.listWorkflowsTool,y=p.recoverTool??g.createRecoverTool(),x=p.controlTool??g.createControlTool(),S=p.trackPendingRun??(e=>e),C=p.launchExecutor??b({activeRunCount:p.activeRunCount,onLaunch:p.onLaunch,observeSession:p.observeSession,rejectRunner:p.rejectRunner,runTool:_,trackPendingRun:S});s.registerTool({name:c,label:`List Workflows`,description:`List the workflow definitions available in this repository.`,promptSnippet:`List available workflow definitions`,parameters:a.z.toJSONSchema(v.getInputSchema()),renderShell:`self`,renderCall:(e,t)=>n.renderWorkflowToolCall(c,e,t),renderResult:(e,t,r,i)=>n.renderWorkflowToolResult(c,i.args,e,{...t,isError:i.isError},r),async execute(t,n){return e.toAgentToolResult(c,await v.execute(n))}}),s.registerTool({name:l,label:`Launch Workflow`,description:`Start a workflow run. Returns once the run has started, not once it finishes; use workflow_run with action status or wait for the completion notice.`,promptSnippet:`Start a workflow run and return its identifier`,promptGuidelines:[`Before launching, call list_workflows and pick the workflow whose own description matches the work. A workflow description is the source of truth; never infer one from its filename. If nothing matches, say so and stop rather than launching an approximate fit.`,`launch_workflow returns when the run has STARTED, not when it has finished. Never report success from its result. Poll workflow_run with action status, or wait for the completion notice the extension delivers on its own.`,`When dispatching Agiflow work, pass AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together through env plus a non-empty prompt. The id alone is not a key, and omitting prompt leaves user_prompt workflows waiting for terminal input.`,`A workflow claims its own job in its pre step. Never claim a job yourself. JOB_ALREADY_CLAIMED or HTTP 409 means another worker won the race, so report contention and do not unlock, release, or retry automatically.`,`If a result says WORKFLOW_NOT_OWNED or a release failed, inspect current ownership and ask the user before any release or unlock. Never force another worker’s lock.`,`If a running workflow is not found, call workflow_run with action status to re-check current state before retrying.`,`The per-session concurrency ceiling is a real limit. If launch_workflow refuses, wait for a run to finish; do not launch through the shell to get around it.`],parameters:a.z.toJSONSchema(_.getInputSchema().pick(o)),renderShell:`self`,renderCall:(e,t)=>n.renderWorkflowToolCall(l,e,t),renderResult:(e,t,r,i)=>n.renderWorkflowToolResult(l,i.args,e,{...t,isError:i.isError},r),async execute(t,n,r,i,a){let o=n,s=await C.execute(o,a,i);return f(e.toAgentToolResult(l,s,h),`Next: call workflow_run with action status and verify the recorded stage before reporting the workflow outcome.`)}}),s.registerTool({name:u,label:`Workflow Run`,description:`Inspect or control one workflow run. Use action status, tail, recovery-evidence, follow, open, pause, resume, stop, or recover. Every action requires an exact run key.`,promptSnippet:`Inspect or control a workflow run`,promptGuidelines:[`Use action status as the authority for stage, outcome, execution state, and pause cursor. A run that started is not a run that succeeded.`,`Use action pause or resume for cooperative lifecycle control. Pause is not stop: the run keeps its stage running, capacity, services, and heartbeat while paused.`,`Use action stop only when the user requests stopping a run. A user-requested stop is not a failure and needs no failure diagnosis.`,`Do not use follow or tail merely to wait for a healthy run; use status or the completion notice. Tail returns chat-safe status without raw PTY frames. Use follow only when the user asks to pin live progress, and open only when a human wants the recorded launcher foregrounded.`,`Use action recovery-evidence for terminal-only, durable status and artifacts before recovery. It is the only inspection path that may read a failed run from an earlier session and it never reads a live launcher.`,"Use action recover only after loading the `workflow-recovery` skill and gathering its required evidence. Recovery may adopt a terminal failed run from an earlier Pi session; it never grants cross-session control of running work. Never edit issue.md or repair.json, and verify real process and registry progress before recovery.",`Never unlock or force-release an Agiflow claim from this tool.`],parameters:a.z.toJSONSchema(t.workflowRunInputSchema),renderShell:`self`,renderCall:(e,t)=>n.renderWorkflowToolCall(u,e,t),renderResult:(e,t,r,i)=>n.renderWorkflowToolResult(u,i.args,e,{...t,isError:i.isError},r),async execute(n,r,a,o,s){let c=t.workflowRunInputSchema.parse(r),l=(0,i.resolveRootSessionId)(s.sessionManager.getSessionId());p.observeSession?.(l);let h={runKey:c.runKey,workspace:c.workspace},g=async()=>{if(!p.requireSessionRun)throw Error(`Workflow run ownership is not available in this session.`);return p.requireSessionRun(h,l)};if(c.action===`status`){let e=await g();return{content:[{type:`text`,text:JSON.stringify(e,null,2)}],details:{tool:u}}}if(c.action===`tail`){if(!p.tailRun)throw Error(`Reading workflow output is not available in this session.`);let e=await g();return d(o,u,`Reading output for workflow ${c.runKey}...`),await p.tailRun(h,s),{content:[{type:`text`,text:[`Workflow: ${e.displayName} (${e.runKey})`,`Stage: ${e.effectiveStage??e.stage}`,`Raw launcher output is hidden from chat. Use action follow or open to view it.`].join(`
2
+ `)}],details:{tool:u}}}if(c.action===`recovery-evidence`){if(!p.readRecoveryEvidence)throw Error(`Reading workflow recovery evidence is not available in this session.`);return d(o,u,`Reading recovery evidence for workflow ${c.runKey}...`),{content:[{type:`text`,text:await p.readRecoveryEvidence(h,s)}],details:{tool:u}}}if(c.action===`follow`){if(!p.followRun)throw Error(`Following workflow output is not available in this session.`);return await g(),d(o,u,`Following workflow ${c.runKey}...`),{content:[{type:`text`,text:await p.followRun(h,s)}],details:{tool:u}}}if(c.action===`open`){if(!p.openRun)throw Error(`Opening a workflow launcher is not available in this session.`);return await g(),d(o,u,`Opening workflow ${c.runKey}...`),{content:[{type:`text`,text:await p.openRun(h,s)}],details:{tool:u}}}if(c.action===`recover`){if(!p.requireRecoverableRun)throw Error(`Workflow recovery ownership transfer is not available in this session.`);let t=await p.requireRecoverableRun(h),n={runKey:t.runKey,workspace:t.workspace};if(d(o,u,`Recovering workflow ${c.runKey}...`),!c.dryRun){let e=await p.delegateRecovery?.({...n,runner:c.runner},s);if(e){let t=m(c.action,c.runKey);return await p.onLaunch?.(s),{content:[{type:`text`,text:[e,t[0]].join(`
3
3
 
4
- `)}],details:{tool:u}}}}let r={dryRun:c.dryRun,job:c.job,runKey:n.runKey,runner:c.runner,workspace:n.workspace},i=await S(y.execute(r)),a=m(c.action,c.runKey),l=t.toAgentToolResult(u,i,a);return await p.onLaunch?.(s),f(l,a[0])}let _=await g();if(!_.runId)throw Error(`Workflow ${c.runKey} has no controllable run generation.`);d(o,u,`Requesting ${c.action} for workflow ${c.runKey}...`);let v=await x.execute({action:c.action,expectedRunId:_.runId,reason:c.reason,runKey:c.runKey,workspace:c.workspace}),b=m(c.action,c.runKey);return f(t.toAgentToolResult(u,v,b),b[0])}})}exports.WORKFLOW_PI_TOOL_NAMES=s,exports.createWorkflowLaunchExecutor=b,exports.registerWorkflowPiTools=x;
4
+ `)}],details:{tool:u}}}}let r={dryRun:c.dryRun,job:c.job,runKey:n.runKey,runner:c.runner,workspace:n.workspace},i=await S(y.execute(r)),a=m(c.action,c.runKey),l=e.toAgentToolResult(u,i,a);return await p.onLaunch?.(s),f(l,a[0])}let _=await g();if(!_.runId)throw Error(`Workflow ${c.runKey} has no controllable run generation.`);d(o,u,`Requesting ${c.action} for workflow ${c.runKey}...`);let v=await x.execute({action:c.action,expectedRunId:_.runId,reason:c.reason,runKey:c.runKey,workspace:c.workspace}),b=m(c.action,c.runKey);return f(e.toAgentToolResult(u,v,b),b[0])}})}exports.WORKFLOW_PI_TOOL_NAMES=s,exports.createWorkflowLaunchExecutor=b,exports.registerWorkflowPiTools=x;
5
5
  //# sourceMappingURL=piTools.cjs.map
@@ -1,5 +1,5 @@
1
- import{workflowRunInputSchema as e}from"../../../schemas/workflowPi.mjs";import{PI_SESSION_ENV as t,launchHandoffSummary as n,launchedRunSummary as r,resolveMaxConcurrent as i,toAgentToolResult as a,toolResultText as o,withOptions as s}from"./piToolBridge.mjs";import{renderWorkflowToolCall as c,renderWorkflowToolResult as l}from"../../../tui/workflow/workflowToolRender.mjs";import{resolveRootSessionId as u}from"@agimon-ai/doompi-extension-contracts/child-process";import{createEmbeddedWorkflowFeature as d}from"@agimon-ai/workflow-mcp";import{z as f}from"zod";const p={workflowPath:!0,workspace:!0,runner:!0,name:!0,env:!0,prompt:!0,job:!0,inputs:!0},m=[`list_workflows`,`launch_workflow`,`workflow_run`],[h,g,_]=m;function v(e,t,n){e?.({content:[{type:`text`,text:n}],details:{tool:t}})}function y(e,t){return{...e,content:[...e.content,{type:`text`,text:t}]}}function b(e){return`workflow_run {"action":"status","runKey":${JSON.stringify(e)}}`}function x(e,t){let n=b(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 S=[`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 C(e){return e instanceof Error?e:Error(String(e),{cause:e})}function w(e){return new Promise(t=>{setTimeout(()=>t(void 0),e).unref?.()})}async function T(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,w(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 E(e,t,n){e.then(e=>{if(e.kind===`error`){t.onLateFailure?.(C(e.error),n);return}e.result.isError&&t.onLateFailure?.(Error(o(e.result)||`Workflow launch failed.`),n)})}function D(e){return{async execute(a,c,l){let d=u(c.sessionManager.getSessionId());e.observeSession?.(d);let f=a.env?.AGIFLOW_JOB_KIND?.trim(),p=a.env?.AGIFLOW_JOB_ID?.trim();if(!!f!=!!p)throw Error(`Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.`);if(f&&!a.prompt?.trim())throw Error(`Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.`);v(l,g,`Checking workflow capacity...`);let m=i(),h=await e.activeRunCount?.()??0;if(h>=m)throw Error(s(`This session is at capacity: ${h}/${m} 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(a.runner){let t=e.rejectRunner?.(a.workflowPath,a.runner);if(t)throw Error(t)}v(l,g,`Launching workflow ${a.workflowPath}...`);let _={...a.env};if(process.env.AGIFLOW_DISPATCH_CONTEXT_FILE)for(let e of[`AGIFLOW_ORGANIZATION_ID`,`AGIFLOW_PROJECT_ID`,`AGIFLOW_DEVICE_ID`,`BACKEND_AGIFLOW_API_ENDPOINT`,`AGIFLOW_DISPATCH_SECRET_FILE`]){let t=process.env[e];t&&(_[e]=t)}let y=Date.now(),b=e.trackPendingRun(e.runTool.execute({...a,env:{..._,[t]:d}})).then(e=>({kind:`value`,result:e}),e=>({kind:`error`,error:e})),x=await T(b,e,{sessionId:d,since:y,workflowPath:a.workflowPath});if(x.kind===`settled`){let t=x.outcome;if(t.kind===`error`)throw C(t.error);let n=t.result;if(n.isError)throw Error(s(o(n)||`Workflow launch failed.`,S));return await e.onLaunch?.(c),n}return E(b,e,c),await e.onLaunch?.(c),{content:[{type:`text`,text:x.kind===`record`?r(x.record):n(a.workflowPath)}]}}}}function O(t,n={}){let r=n.feature??d(),i=n.runTool??r.runTool,o=n.listWorkflowsTool??r.listWorkflowsTool,s=n.recoverTool??r.createRecoverTool(),m=n.controlTool??r.createControlTool(),b=n.trackPendingRun??(e=>e),C=n.launchExecutor??D({activeRunCount:n.activeRunCount,onLaunch:n.onLaunch,observeSession:n.observeSession,rejectRunner:n.rejectRunner,runTool:i,trackPendingRun:b});t.registerTool({name:h,label:`List Workflows`,description:`List the workflow definitions available in this repository.`,promptSnippet:`List available workflow definitions`,parameters:f.toJSONSchema(o.getInputSchema()),renderShell:`self`,renderCall:(e,t)=>c(h,e,t),renderResult:(e,t,n,r)=>l(h,r.args,e,{...t,isError:r.isError},n),async execute(e,t){return a(h,await o.execute(t))}}),t.registerTool({name:g,label:`Launch Workflow`,description:`Start a workflow run. Returns once the run has started, not once it finishes; use workflow_run with action status or wait for the completion notice.`,promptSnippet:`Start a workflow run and return its identifier`,promptGuidelines:[`Before launching, call list_workflows and pick the workflow whose own description matches the work. A workflow description is the source of truth; never infer one from its filename. If nothing matches, say so and stop rather than launching an approximate fit.`,`launch_workflow returns when the run has STARTED, not when it has finished. Never report success from its result. Poll workflow_run with action status, or wait for the completion notice the extension delivers on its own.`,`When dispatching Agiflow work, pass AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together through env plus a non-empty prompt. The id alone is not a key, and omitting prompt leaves user_prompt workflows waiting for terminal input.`,`A workflow claims its own job in its pre step. Never claim a job yourself. JOB_ALREADY_CLAIMED or HTTP 409 means another worker won the race, so report contention and do not unlock, release, or retry automatically.`,`If a result says WORKFLOW_NOT_OWNED or a release failed, inspect current ownership and ask the user before any release or unlock. Never force another worker’s lock.`,`If a running workflow is not found, call workflow_run with action status to re-check current state before retrying.`,`The per-session concurrency ceiling is a real limit. If launch_workflow refuses, wait for a run to finish; do not launch through the shell to get around it.`],parameters:f.toJSONSchema(i.getInputSchema().pick(p)),renderShell:`self`,renderCall:(e,t)=>c(g,e,t),renderResult:(e,t,n,r)=>l(g,r.args,e,{...t,isError:r.isError},n),async execute(e,t,n,r,i){let o=t,s=await C.execute(o,i,r);return y(a(g,s,S),`Next: call workflow_run with action status and verify the recorded stage before reporting the workflow outcome.`)}}),t.registerTool({name:_,label:`Workflow Run`,description:`Inspect or control one workflow run. Use action status, tail, recovery-evidence, follow, open, pause, resume, stop, or recover. Every action requires an exact run key.`,promptSnippet:`Inspect or control a workflow run`,promptGuidelines:[`Use action status as the authority for stage, outcome, execution state, and pause cursor. A run that started is not a run that succeeded.`,`Use action pause or resume for cooperative lifecycle control. Pause is not stop: the run keeps its stage running, capacity, services, and heartbeat while paused.`,`Use action stop only when the user requests stopping a run. A user-requested stop is not a failure and needs no failure diagnosis.`,`Do not use follow or tail merely to wait for a healthy run; use status or the completion notice. Tail returns chat-safe status without raw PTY frames. Use follow only when the user asks to pin live progress, and open only when a human wants the recorded launcher foregrounded.`,`Use action recovery-evidence for terminal-only, durable status and artifacts before recovery. It is the only inspection path that may read a failed run from an earlier session and it never reads a live launcher.`,"Use action recover only after loading the `workflow-recovery` skill and gathering its required evidence. Recovery may adopt a terminal failed run from an earlier Pi session; it never grants cross-session control of running work. Never edit issue.md or repair.json, and verify real process and registry progress before recovery.",`Never unlock or force-release an Agiflow claim from this tool.`],parameters:f.toJSONSchema(e),renderShell:`self`,renderCall:(e,t)=>c(_,e,t),renderResult:(e,t,n,r)=>l(_,r.args,e,{...t,isError:r.isError},n),async execute(t,r,i,o,c){let l=e.parse(r),d=u(c.sessionManager.getSessionId());n.observeSession?.(d);let f={runKey:l.runKey,workspace:l.workspace},p=async()=>{if(!n.requireSessionRun)throw Error(`Workflow run ownership is not available in this session.`);return n.requireSessionRun(f,d)};if(l.action===`status`){let e=await p();return{content:[{type:`text`,text:JSON.stringify(e,null,2)}],details:{tool:_}}}if(l.action===`tail`){if(!n.tailRun)throw Error(`Reading workflow output is not available in this session.`);let e=await p();return v(o,_,`Reading output for workflow ${l.runKey}...`),await n.tailRun(f,c),{content:[{type:`text`,text:[`Workflow: ${e.displayName} (${e.runKey})`,`Stage: ${e.effectiveStage??e.stage}`,`Raw launcher output is hidden from chat. Use action follow or open to view it.`].join(`
2
- `)}],details:{tool:_}}}if(l.action===`recovery-evidence`){if(!n.readRecoveryEvidence)throw Error(`Reading workflow recovery evidence is not available in this session.`);return v(o,_,`Reading recovery evidence for workflow ${l.runKey}...`),{content:[{type:`text`,text:await n.readRecoveryEvidence(f,c)}],details:{tool:_}}}if(l.action===`follow`){if(!n.followRun)throw Error(`Following workflow output is not available in this session.`);return await p(),v(o,_,`Following workflow ${l.runKey}...`),{content:[{type:`text`,text:await n.followRun(f,c)}],details:{tool:_}}}if(l.action===`open`){if(!n.openRun)throw Error(`Opening a workflow launcher is not available in this session.`);return await p(),v(o,_,`Opening workflow ${l.runKey}...`),{content:[{type:`text`,text:await n.openRun(f,c)}],details:{tool:_}}}if(l.action===`recover`){if(!n.requireRecoverableRun)throw Error(`Workflow recovery ownership transfer is not available in this session.`);let e=await n.requireRecoverableRun(f),t={runKey:e.runKey,workspace:e.workspace};if(v(o,_,`Recovering workflow ${l.runKey}...`),!l.dryRun){let e=await n.delegateRecovery?.({...t,runner:l.runner},c);if(e){let t=x(l.action,l.runKey);return await n.onLaunch?.(c),{content:[{type:`text`,text:[e,t[0]].join(`
1
+ import{PI_SESSION_ENV as e,launchHandoffSummary as t,launchedRunSummary as n,resolveMaxConcurrent as r,toAgentToolResult as i,toolResultText as a,withOptions as o}from"./piToolBridge.mjs";import{workflowRunInputSchema as s}from"../../../schemas/workflowPi.mjs";import{renderWorkflowToolCall as c,renderWorkflowToolResult as l}from"../../../tui/workflow/workflowToolRender.mjs";import{createEmbeddedWorkflowFeature as u}from"@agimon-ai/workflow-mcp";import{resolveRootSessionId as d}from"@agimon-ai/doompi-extension-contracts/child-process";import{z as f}from"zod";const p={workflowPath:!0,workspace:!0,runner:!0,name:!0,env:!0,prompt:!0,job:!0,inputs:!0},m=[`list_workflows`,`launch_workflow`,`workflow_run`],[h,g,_]=m;function v(e,t,n){e?.({content:[{type:`text`,text:n}],details:{tool:t}})}function y(e,t){return{...e,content:[...e.content,{type:`text`,text:t}]}}function b(e){return`workflow_run {"action":"status","runKey":${JSON.stringify(e)}}`}function x(e,t){let n=b(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 S=[`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 C(e){return e instanceof Error?e:Error(String(e),{cause:e})}function w(e){return new Promise(t=>{setTimeout(()=>t(void 0),e).unref?.()})}async function T(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,w(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 E(e,t,n){e.then(e=>{if(e.kind===`error`){t.onLateFailure?.(C(e.error),n);return}e.result.isError&&t.onLateFailure?.(Error(a(e.result)||`Workflow launch failed.`),n)})}function D(i){return{async execute(s,c,l){let u=d(c.sessionManager.getSessionId());i.observeSession?.(u);let f=s.env?.AGIFLOW_JOB_KIND?.trim(),p=s.env?.AGIFLOW_JOB_ID?.trim();if(!!f!=!!p)throw Error(`Agiflow workflow launches require AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together.`);if(f&&!s.prompt?.trim())throw Error(`Agiflow workflow launches require a non-empty prompt so user_prompt runs do not wait for terminal input.`);v(l,g,`Checking workflow capacity...`);let m=r(),h=await i.activeRunCount?.()??0;if(h>=m)throw Error(o(`This session is at capacity: ${h}/${m} 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(s.runner){let e=i.rejectRunner?.(s.workflowPath,s.runner);if(e)throw Error(e)}v(l,g,`Launching workflow ${s.workflowPath}...`);let _={...s.env};if(process.env.AGIFLOW_DISPATCH_CONTEXT_FILE)for(let e of[`AGIFLOW_ORGANIZATION_ID`,`AGIFLOW_PROJECT_ID`,`AGIFLOW_DEVICE_ID`,`BACKEND_AGIFLOW_API_ENDPOINT`,`AGIFLOW_DISPATCH_SECRET_FILE`]){let t=process.env[e];t&&(_[e]=t)}let y=Date.now(),b=i.trackPendingRun(i.runTool.execute({...s,env:{..._,[e]:u}})).then(e=>({kind:`value`,result:e}),e=>({kind:`error`,error:e})),x=await T(b,i,{sessionId:u,since:y,workflowPath:s.workflowPath});if(x.kind===`settled`){let e=x.outcome;if(e.kind===`error`)throw C(e.error);let t=e.result;if(t.isError)throw Error(o(a(t)||`Workflow launch failed.`,S));return await i.onLaunch?.(c),t}return E(b,i,c),await i.onLaunch?.(c),{content:[{type:`text`,text:x.kind===`record`?n(x.record):t(s.workflowPath)}]}}}}function O(e,t={}){let n=t.feature??u(),r=t.runTool??n.runTool,a=t.listWorkflowsTool??n.listWorkflowsTool,o=t.recoverTool??n.createRecoverTool(),m=t.controlTool??n.createControlTool(),b=t.trackPendingRun??(e=>e),C=t.launchExecutor??D({activeRunCount:t.activeRunCount,onLaunch:t.onLaunch,observeSession:t.observeSession,rejectRunner:t.rejectRunner,runTool:r,trackPendingRun:b});e.registerTool({name:h,label:`List Workflows`,description:`List the workflow definitions available in this repository.`,promptSnippet:`List available workflow definitions`,parameters:f.toJSONSchema(a.getInputSchema()),renderShell:`self`,renderCall:(e,t)=>c(h,e,t),renderResult:(e,t,n,r)=>l(h,r.args,e,{...t,isError:r.isError},n),async execute(e,t){return i(h,await a.execute(t))}}),e.registerTool({name:g,label:`Launch Workflow`,description:`Start a workflow run. Returns once the run has started, not once it finishes; use workflow_run with action status or wait for the completion notice.`,promptSnippet:`Start a workflow run and return its identifier`,promptGuidelines:[`Before launching, call list_workflows and pick the workflow whose own description matches the work. A workflow description is the source of truth; never infer one from its filename. If nothing matches, say so and stop rather than launching an approximate fit.`,`launch_workflow returns when the run has STARTED, not when it has finished. Never report success from its result. Poll workflow_run with action status, or wait for the completion notice the extension delivers on its own.`,`When dispatching Agiflow work, pass AGIFLOW_JOB_KIND and AGIFLOW_JOB_ID together through env plus a non-empty prompt. The id alone is not a key, and omitting prompt leaves user_prompt workflows waiting for terminal input.`,`A workflow claims its own job in its pre step. Never claim a job yourself. JOB_ALREADY_CLAIMED or HTTP 409 means another worker won the race, so report contention and do not unlock, release, or retry automatically.`,`If a result says WORKFLOW_NOT_OWNED or a release failed, inspect current ownership and ask the user before any release or unlock. Never force another worker’s lock.`,`If a running workflow is not found, call workflow_run with action status to re-check current state before retrying.`,`The per-session concurrency ceiling is a real limit. If launch_workflow refuses, wait for a run to finish; do not launch through the shell to get around it.`],parameters:f.toJSONSchema(r.getInputSchema().pick(p)),renderShell:`self`,renderCall:(e,t)=>c(g,e,t),renderResult:(e,t,n,r)=>l(g,r.args,e,{...t,isError:r.isError},n),async execute(e,t,n,r,a){let o=t,s=await C.execute(o,a,r);return y(i(g,s,S),`Next: call workflow_run with action status and verify the recorded stage before reporting the workflow outcome.`)}}),e.registerTool({name:_,label:`Workflow Run`,description:`Inspect or control one workflow run. Use action status, tail, recovery-evidence, follow, open, pause, resume, stop, or recover. Every action requires an exact run key.`,promptSnippet:`Inspect or control a workflow run`,promptGuidelines:[`Use action status as the authority for stage, outcome, execution state, and pause cursor. A run that started is not a run that succeeded.`,`Use action pause or resume for cooperative lifecycle control. Pause is not stop: the run keeps its stage running, capacity, services, and heartbeat while paused.`,`Use action stop only when the user requests stopping a run. A user-requested stop is not a failure and needs no failure diagnosis.`,`Do not use follow or tail merely to wait for a healthy run; use status or the completion notice. Tail returns chat-safe status without raw PTY frames. Use follow only when the user asks to pin live progress, and open only when a human wants the recorded launcher foregrounded.`,`Use action recovery-evidence for terminal-only, durable status and artifacts before recovery. It is the only inspection path that may read a failed run from an earlier session and it never reads a live launcher.`,"Use action recover only after loading the `workflow-recovery` skill and gathering its required evidence. Recovery may adopt a terminal failed run from an earlier Pi session; it never grants cross-session control of running work. Never edit issue.md or repair.json, and verify real process and registry progress before recovery.",`Never unlock or force-release an Agiflow claim from this tool.`],parameters:f.toJSONSchema(s),renderShell:`self`,renderCall:(e,t)=>c(_,e,t),renderResult:(e,t,n,r)=>l(_,r.args,e,{...t,isError:r.isError},n),async execute(e,n,r,a,c){let l=s.parse(n),u=d(c.sessionManager.getSessionId());t.observeSession?.(u);let f={runKey:l.runKey,workspace:l.workspace},p=async()=>{if(!t.requireSessionRun)throw Error(`Workflow run ownership is not available in this session.`);return t.requireSessionRun(f,u)};if(l.action===`status`){let e=await p();return{content:[{type:`text`,text:JSON.stringify(e,null,2)}],details:{tool:_}}}if(l.action===`tail`){if(!t.tailRun)throw Error(`Reading workflow output is not available in this session.`);let e=await p();return v(a,_,`Reading output for workflow ${l.runKey}...`),await t.tailRun(f,c),{content:[{type:`text`,text:[`Workflow: ${e.displayName} (${e.runKey})`,`Stage: ${e.effectiveStage??e.stage}`,`Raw launcher output is hidden from chat. Use action follow or open to view it.`].join(`
2
+ `)}],details:{tool:_}}}if(l.action===`recovery-evidence`){if(!t.readRecoveryEvidence)throw Error(`Reading workflow recovery evidence is not available in this session.`);return v(a,_,`Reading recovery evidence for workflow ${l.runKey}...`),{content:[{type:`text`,text:await t.readRecoveryEvidence(f,c)}],details:{tool:_}}}if(l.action===`follow`){if(!t.followRun)throw Error(`Following workflow output is not available in this session.`);return await p(),v(a,_,`Following workflow ${l.runKey}...`),{content:[{type:`text`,text:await t.followRun(f,c)}],details:{tool:_}}}if(l.action===`open`){if(!t.openRun)throw Error(`Opening a workflow launcher is not available in this session.`);return await p(),v(a,_,`Opening workflow ${l.runKey}...`),{content:[{type:`text`,text:await t.openRun(f,c)}],details:{tool:_}}}if(l.action===`recover`){if(!t.requireRecoverableRun)throw Error(`Workflow recovery ownership transfer is not available in this session.`);let e=await t.requireRecoverableRun(f),n={runKey:e.runKey,workspace:e.workspace};if(v(a,_,`Recovering workflow ${l.runKey}...`),!l.dryRun){let e=await t.delegateRecovery?.({...n,runner:l.runner},c);if(e){let n=x(l.action,l.runKey);return await t.onLaunch?.(c),{content:[{type:`text`,text:[e,n[0]].join(`
3
3
 
4
- `)}],details:{tool:_}}}}let r={dryRun:l.dryRun,job:l.job,runKey:t.runKey,runner:l.runner,workspace:t.workspace},i=await b(s.execute(r)),u=x(l.action,l.runKey),d=a(_,i,u);return await n.onLaunch?.(c),y(d,u[0])}let h=await p();if(!h.runId)throw Error(`Workflow ${l.runKey} has no controllable run generation.`);v(o,_,`Requesting ${l.action} for workflow ${l.runKey}...`);let g=await m.execute({action:l.action,expectedRunId:h.runId,reason:l.reason,runKey:l.runKey,workspace:l.workspace}),S=x(l.action,l.runKey);return y(a(_,g,S),S[0])}})}export{m as WORKFLOW_PI_TOOL_NAMES,D as createWorkflowLaunchExecutor,O as registerWorkflowPiTools};
4
+ `)}],details:{tool:_}}}}let r={dryRun:l.dryRun,job:l.job,runKey:n.runKey,runner:l.runner,workspace:n.workspace},s=await b(o.execute(r)),u=x(l.action,l.runKey),d=i(_,s,u);return await t.onLaunch?.(c),y(d,u[0])}let h=await p();if(!h.runId)throw Error(`Workflow ${l.runKey} has no controllable run generation.`);v(a,_,`Requesting ${l.action} for workflow ${l.runKey}...`);let g=await m.execute({action:l.action,expectedRunId:h.runId,reason:l.reason,runKey:l.runKey,workspace:l.workspace}),S=x(l.action,l.runKey);return y(i(_,g,S),S[0])}})}export{m as WORKFLOW_PI_TOOL_NAMES,D as createWorkflowLaunchExecutor,O as registerWorkflowPiTools};
5
5
  //# sourceMappingURL=piTools.mjs.map
@@ -1,2 +1,2 @@
1
- const e=require("../../../schemas/workflowPi.cjs"),t=require("./piToolBridge.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,...t[`runs-on`]?{runsOn:t[`runs-on`]}:{},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;
1
+ const e=require("./piToolBridge.cjs"),t=require("../../../schemas/workflowPi.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(e){let n=i(e).workflow_dispatch;if(n==null)return{};let r=t.workflowDispatchInputsSchema.safeParse(n);if(!r.success)throw Error(`Invalid workflow_dispatch input definitions: ${r.error.message}`);return r.data.inputs??{}}function o(t){return Error(e.toolResultText(t)||`Workflow launch failed.`)}function s(r){if(r.isError)throw o(r);let i;try{i=JSON.parse(e.toolResultText(r))}catch(e){throw Error(`Workflow catalog returned invalid JSON.`,{cause:e})}let a=t.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
2
  //# sourceMappingURL=workflowLauncher.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"workflowLauncher.cjs","names":["workflowDispatchInputsSchema","toolResultText","workflowCatalogPageSchema","resolve"],"sources":["../../../../src/adapters/pi/workflow/workflowLauncher.ts"],"sourcesContent":["import { resolve } from 'node:path';\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';\nimport {\n type DispatchInput,\n workflowCatalogPageSchema,\n workflowDispatchInputsSchema,\n} from '../../../schemas/workflowPi.ts';\nimport type {\n WorkflowCatalogDetail,\n WorkflowInputSummary,\n WorkflowJobSummary,\n} from '../../../tui/workflow/workflowCatalog';\nimport { toolResultText } from './piToolBridge';\nimport { type WorkflowLaunchExecutor, type WorkflowLaunchInput } from './piTools';\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 ...(job['runs-on'] ? { runsOn: job['runs-on'] } : {}),\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
+ {"version":3,"file":"workflowLauncher.cjs","names":["workflowDispatchInputsSchema","toolResultText","workflowCatalogPageSchema","resolve"],"sources":["../../../../src/adapters/pi/workflow/workflowLauncher.ts"],"sourcesContent":["import { resolve } from 'node:path';\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';\nimport {\n type DispatchInput,\n workflowCatalogPageSchema,\n workflowDispatchInputsSchema,\n} from '../../../schemas/workflowPi.ts';\nimport type {\n WorkflowCatalogDetail,\n WorkflowInputSummary,\n WorkflowJobSummary,\n} from '../../../tui/workflow/workflowCatalog';\nimport { toolResultText } from './piToolBridge';\nimport { type WorkflowLaunchExecutor, type WorkflowLaunchInput } from './piTools';\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 +1,2 @@
1
- import{workflowCatalogPageSchema as e,workflowDispatchInputsSchema as t}from"../../../schemas/workflowPi.mjs";import{toolResultText as n}from"./piToolBridge.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,...t[`runs-on`]?{runsOn:t[`runs-on`]}:{},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};
1
+ import{toolResultText as e}from"./piToolBridge.mjs";import{workflowCatalogPageSchema as t,workflowDispatchInputsSchema as n}from"../../../schemas/workflowPi.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 t=a(e).workflow_dispatch;if(t==null)return{};let r=n.safeParse(t);if(!r.success)throw Error(`Invalid workflow_dispatch input definitions: ${r.error.message}`);return r.data.inputs??{}}function s(t){return Error(e(t)||`Workflow launch failed.`)}function c(n){if(n.isError)throw s(n);let i;try{i=JSON.parse(e(n))}catch(e){throw Error(`Workflow catalog returned invalid JSON.`,{cause:e})}let a=t.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
2
  //# sourceMappingURL=workflowLauncher.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"workflowLauncher.mjs","names":[],"sources":["../../../../src/adapters/pi/workflow/workflowLauncher.ts"],"sourcesContent":["import { resolve } from 'node:path';\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';\nimport {\n type DispatchInput,\n workflowCatalogPageSchema,\n workflowDispatchInputsSchema,\n} from '../../../schemas/workflowPi.ts';\nimport type {\n WorkflowCatalogDetail,\n WorkflowInputSummary,\n WorkflowJobSummary,\n} from '../../../tui/workflow/workflowCatalog';\nimport { toolResultText } from './piToolBridge';\nimport { type WorkflowLaunchExecutor, type WorkflowLaunchInput } from './piTools';\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 ...(job['runs-on'] ? { runsOn: job['runs-on'] } : {}),\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
+ {"version":3,"file":"workflowLauncher.mjs","names":[],"sources":["../../../../src/adapters/pi/workflow/workflowLauncher.ts"],"sourcesContent":["import { resolve } from 'node:path';\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';\nimport {\n type DispatchInput,\n workflowCatalogPageSchema,\n workflowDispatchInputsSchema,\n} from '../../../schemas/workflowPi.ts';\nimport type {\n WorkflowCatalogDetail,\n WorkflowInputSummary,\n WorkflowJobSummary,\n} from '../../../tui/workflow/workflowCatalog';\nimport { toolResultText } from './piToolBridge';\nimport { type WorkflowLaunchExecutor, type WorkflowLaunchInput } from './piTools';\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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"}
@@ -0,0 +1,2 @@
1
+ const e=require("../services/webWorkflowCatalog.cjs"),t=require("../types/webWorkflows.cjs");let n=require("node:fs"),r=require("@agimon-ai/workflow-mcp");function i(){return{list:e=>(0,r.readWorkflowCatalog)(e),summarize:e=>(0,r.summarizeWorkflow)(e),stamp:e=>{try{let t=(0,n.statSync)(e);return{size:t.size,modifiedAt:t.mtimeMs}}catch{return}}}}function a(n={}){let r=e.createWorkflowCatalogReader(n.deps??i()),a=n.refreshMs??15e3;return{frameType:t.WORKFLOW_CATALOG_TYPE,start(t){let n=new Map,i=new Map,s=async t=>{try{let n=e.presentWorkflowCatalog(await r.read(t.cwd));return{cwd:t.cwd,workflows:n}}catch(e){let n=e instanceof Error?e.message:String(e);return{cwd:t.cwd,workflows:[],warning:n}}},c=new Map,l=()=>{let e=new Set;for(let t of c.values())for(let n of t)e.add(n);r.forget(e)},u=async e=>{let n=await s(e);c.set(e.sessionId,new Set(n.workflows.map(e=>e.path))),l();let r=JSON.stringify(n);i.get(e.sessionId)!==r&&(i.set(e.sessionId,r),n.warning!==void 0&&t.onNotice(`workflow catalog for ${e.cwd} is unavailable (${n.warning})`),t.publish(e.sessionId,n))},d=setInterval(()=>{for(let e of n.values())u(e)},a);return d.unref?.(),{payloadFor(e){if(n.has(e.sessionId))return u(e),o(i,e.sessionId)},sessionAdded(e){n.set(e.sessionId,e),u(e)},sessionRemoved(e){n.delete(e),i.delete(e),c.delete(e),l()},close(){clearInterval(d)}}}}}function o(e,t){let n=e.get(t);if(n!==void 0)return JSON.parse(n)}exports.createWorkflowCatalogChannel=a,exports.defaultCatalogDeps=i;
2
+ //# sourceMappingURL=workflowCatalogChannel.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowCatalogChannel.cjs","names":["readWorkflowCatalog","summarizeWorkflow","statSync","createWorkflowCatalogReader","WORKFLOW_CATALOG_TYPE","presentWorkflowCatalog"],"sources":["../../src/adapters/workflowCatalogChannel.ts"],"sourcesContent":["import { statSync } from 'node:fs';\nimport { readWorkflowCatalog, summarizeWorkflow } from '@agimon-ai/workflow-mcp';\nimport type { HubChannelSource, HubSessionScope, WebHubChannel } from '@agimon-ai/doompi-web-contracts';\nimport {\n createWorkflowCatalogReader,\n presentWorkflowCatalog,\n type WorkflowCatalogReaderDeps,\n} from '../services/webWorkflowCatalog.ts';\nimport { WORKFLOW_CATALOG_TYPE, type WorkflowCatalogPayload } from '../types/webWorkflows.ts';\n\n/** Workflow files change a few times a week; this only has to beat a reload. */\nconst CATALOG_REFRESH_MS = 15_000;\n\n/** Reads the engine's own catalog, which is what `SPC w l` lists. */\nexport function defaultCatalogDeps(): WorkflowCatalogReaderDeps {\n return {\n list: (directory) => readWorkflowCatalog(directory),\n summarize: (path) => summarizeWorkflow(path),\n stamp: (path) => {\n try {\n const stats = statSync(path);\n return { size: stats.size, modifiedAt: stats.mtimeMs };\n } catch {\n // The file was listed and then removed. The caller parses it anyway\n // and gets that file's own error, it just cannot cache the answer.\n return undefined;\n }\n },\n };\n}\n\nexport interface WorkflowCatalogChannelOptions {\n deps?: WorkflowCatalogReaderDeps;\n refreshMs?: number;\n}\n\n/**\n * The workflow catalog data channel: what each managed session's directory can\n * launch, published as 'workflow_catalog' payloads.\n *\n * The catalog is read once per session on arrival, again at every subscribe,\n * and on a slow tick so a workflow file written while the page is open shows up\n * without a reload. A failed read publishes an empty list with the reason\n * rather than nothing, because a drawer that says why it is empty beats one\n * that looks like a repository with no workflows.\n */\nexport function createWorkflowCatalogChannel(options: WorkflowCatalogChannelOptions = {}): WebHubChannel {\n const reader = createWorkflowCatalogReader(options.deps ?? defaultCatalogDeps());\n const refreshMs = options.refreshMs ?? CATALOG_REFRESH_MS;\n return {\n frameType: WORKFLOW_CATALOG_TYPE,\n start(host) {\n const scopes = new Map<string, HubSessionScope>();\n const lastPublished = new Map<string, string>();\n\n const compute = async (scope: HubSessionScope): Promise<WorkflowCatalogPayload> => {\n try {\n const workflows = presentWorkflowCatalog(await reader.read(scope.cwd));\n return { cwd: scope.cwd, workflows };\n } catch (error) {\n const warning = error instanceof Error ? error.message : String(error);\n return { cwd: scope.cwd, workflows: [], warning };\n }\n };\n\n /** Which files each session's last read listed, so the parse cache can be trimmed. */\n const listed = new Map<string, Set<string>>();\n const forgetUnlisted = (): void => {\n const keep = new Set<string>();\n for (const paths of listed.values()) {\n for (const path of paths) keep.add(path);\n }\n reader.forget(keep);\n };\n\n /** Recomputes and remembers; publishes only what changed, so the tick stays quiet. */\n const refresh = async (scope: HubSessionScope): Promise<void> => {\n const payload = await compute(scope);\n listed.set(scope.sessionId, new Set(payload.workflows.map((workflow) => workflow.path)));\n forgetUnlisted();\n const json = JSON.stringify(payload);\n if (lastPublished.get(scope.sessionId) === json) return;\n lastPublished.set(scope.sessionId, json);\n if (payload.warning !== undefined) {\n host.onNotice(`workflow catalog for ${scope.cwd} is unavailable (${payload.warning})`);\n }\n host.publish(scope.sessionId, payload);\n };\n\n const timer = setInterval(() => {\n for (const scope of scopes.values()) void refresh(scope);\n }, refreshMs);\n timer.unref?.();\n\n const source: HubChannelSource = {\n /**\n * Reading a directory is asynchronous and a snapshot is not, so a page\n * subscribing gets the last catalog read for its session and the fresh\n * one a moment later on the channel. The first subscriber of a session\n * gets no snapshot and only the published read.\n */\n payloadFor(scope) {\n if (!scopes.has(scope.sessionId)) return undefined;\n void refresh(scope);\n return cached(lastPublished, scope.sessionId);\n },\n sessionAdded(scope) {\n scopes.set(scope.sessionId, scope);\n void refresh(scope);\n },\n sessionRemoved(sessionId) {\n scopes.delete(sessionId);\n lastPublished.delete(sessionId);\n listed.delete(sessionId);\n forgetUnlisted();\n },\n close() {\n clearInterval(timer);\n },\n };\n return source;\n },\n };\n}\n\n/** The last payload published for a session, so a subscribe answers at once. */\nfunction cached(published: Map<string, string>, sessionId: string): WorkflowCatalogPayload | undefined {\n const json = published.get(sessionId);\n if (json === undefined) return undefined;\n return JSON.parse(json) as WorkflowCatalogPayload;\n}\n"],"mappings":"2JAcA,SAAgB,GAAgD,CAC9D,MAAO,CACL,KAAO,IAAA,EAAcA,EAAAA,oBAAAA,CAAoB,CAAS,EAClD,UAAY,IAAA,EAASC,EAAAA,kBAAAA,CAAkB,CAAI,EAC3C,MAAQ,GAAS,CACf,GAAI,CACF,IAAM,GAAA,EAAQC,EAAAA,SAAAA,CAAS,CAAI,EAC3B,MAAO,CAAE,KAAM,EAAM,KAAM,WAAY,EAAM,OAAQ,CACvD,MAAQ,CAGN,MACF,CACF,CACF,CACF,CAiBA,SAAgB,EAA6B,EAAyC,CAAC,EAAkB,CACvG,IAAM,EAASC,EAAAA,4BAA4B,EAAQ,MAAQ,EAAmB,CAAC,EACzE,EAAY,EAAQ,WAAa,KACvC,MAAO,CACL,UAAWC,EAAAA,sBACX,MAAM,EAAM,CACV,IAAM,EAAS,IAAI,IACb,EAAgB,IAAI,IAEpB,EAAU,KAAO,IAA4D,CACjF,GAAI,CACF,IAAM,EAAYC,EAAAA,uBAAuB,MAAM,EAAO,KAAK,EAAM,GAAG,CAAC,EACrE,MAAO,CAAE,IAAK,EAAM,IAAK,WAAU,CACrC,OAAS,EAAO,CACd,IAAM,EAAU,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EACrE,MAAO,CAAE,IAAK,EAAM,IAAK,UAAW,CAAC,EAAG,SAAQ,CAClD,CACF,EAGM,EAAS,IAAI,IACb,MAA6B,CACjC,IAAM,EAAO,IAAI,IACjB,IAAK,IAAM,KAAS,EAAO,OAAO,EAChC,IAAK,IAAM,KAAQ,EAAO,EAAK,IAAI,CAAI,EAEzC,EAAO,OAAO,CAAI,CACpB,EAGM,EAAU,KAAO,IAA0C,CAC/D,IAAM,EAAU,MAAM,EAAQ,CAAK,EACnC,EAAO,IAAI,EAAM,UAAW,IAAI,IAAI,EAAQ,UAAU,IAAK,GAAa,EAAS,IAAI,CAAC,CAAC,EACvF,EAAe,EACf,IAAM,EAAO,KAAK,UAAU,CAAO,EAC/B,EAAc,IAAI,EAAM,SAAS,IAAM,IAC3C,EAAc,IAAI,EAAM,UAAW,CAAI,EACnC,EAAQ,UAAY,IAAA,IACtB,EAAK,SAAS,wBAAwB,EAAM,IAAI,mBAAmB,EAAQ,QAAQ,EAAE,EAEvF,EAAK,QAAQ,EAAM,UAAW,CAAO,EACvC,EAEM,EAAQ,gBAAkB,CAC9B,IAAK,IAAM,KAAS,EAAO,OAAO,EAAG,EAAa,CAAK,CACzD,EAAG,CAAS,EA6BZ,OA5BA,EAAM,QAAQ,EA4BP,CAnBL,WAAW,EAAO,CACX,KAAO,IAAI,EAAM,SAAS,EAE/B,OADA,EAAa,CAAK,EACX,EAAO,EAAe,EAAM,SAAS,CAC9C,EACA,aAAa,EAAO,CAClB,EAAO,IAAI,EAAM,UAAW,CAAK,EACjC,EAAa,CAAK,CACpB,EACA,eAAe,EAAW,CACxB,EAAO,OAAO,CAAS,EACvB,EAAc,OAAO,CAAS,EAC9B,EAAO,OAAO,CAAS,EACvB,EAAe,CACjB,EACA,OAAQ,CACN,cAAc,CAAK,CACrB,CAEU,CACd,CACF,CACF,CAGA,SAAS,EAAO,EAAgC,EAAuD,CACrG,IAAM,EAAO,EAAU,IAAI,CAAS,EAChC,OAAS,IAAA,GACb,OAAO,KAAK,MAAM,CAAI,CACxB"}
@@ -0,0 +1,23 @@
1
+ import { WorkflowCatalogReaderDeps } from "../services/webWorkflowCatalog.cjs";
2
+ import { WebHubChannel } from "@agimon-ai/doompi-web-contracts";
3
+ //#region src/adapters/workflowCatalogChannel.d.ts
4
+ /** Reads the engine's own catalog, which is what `SPC w l` lists. */
5
+ declare function defaultCatalogDeps(): WorkflowCatalogReaderDeps;
6
+ interface WorkflowCatalogChannelOptions {
7
+ deps?: WorkflowCatalogReaderDeps;
8
+ refreshMs?: number;
9
+ }
10
+ /**
11
+ * The workflow catalog data channel: what each managed session's directory can
12
+ * launch, published as 'workflow_catalog' payloads.
13
+ *
14
+ * The catalog is read once per session on arrival, again at every subscribe,
15
+ * and on a slow tick so a workflow file written while the page is open shows up
16
+ * without a reload. A failed read publishes an empty list with the reason
17
+ * rather than nothing, because a drawer that says why it is empty beats one
18
+ * that looks like a repository with no workflows.
19
+ */
20
+ declare function createWorkflowCatalogChannel(options?: WorkflowCatalogChannelOptions): WebHubChannel;
21
+ //#endregion
22
+ export { WorkflowCatalogChannelOptions, createWorkflowCatalogChannel, defaultCatalogDeps };
23
+ //# sourceMappingURL=workflowCatalogChannel.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowCatalogChannel.d.cts","names":[],"sources":["../../src/adapters/workflowCatalogChannel.ts"],"mappings":";;;;iBAcgB,sBAAsB;UAiBrB;EACf,OAAO;EACP;;;;;;;;;;;;iBAac,6BAA6B,UAAS,gCAAqC"}
@@ -0,0 +1,23 @@
1
+ import { WorkflowCatalogReaderDeps } from "../services/webWorkflowCatalog.mjs";
2
+ import { WebHubChannel } from "@agimon-ai/doompi-web-contracts";
3
+ //#region src/adapters/workflowCatalogChannel.d.ts
4
+ /** Reads the engine's own catalog, which is what `SPC w l` lists. */
5
+ declare function defaultCatalogDeps(): WorkflowCatalogReaderDeps;
6
+ interface WorkflowCatalogChannelOptions {
7
+ deps?: WorkflowCatalogReaderDeps;
8
+ refreshMs?: number;
9
+ }
10
+ /**
11
+ * The workflow catalog data channel: what each managed session's directory can
12
+ * launch, published as 'workflow_catalog' payloads.
13
+ *
14
+ * The catalog is read once per session on arrival, again at every subscribe,
15
+ * and on a slow tick so a workflow file written while the page is open shows up
16
+ * without a reload. A failed read publishes an empty list with the reason
17
+ * rather than nothing, because a drawer that says why it is empty beats one
18
+ * that looks like a repository with no workflows.
19
+ */
20
+ declare function createWorkflowCatalogChannel(options?: WorkflowCatalogChannelOptions): WebHubChannel;
21
+ //#endregion
22
+ export { WorkflowCatalogChannelOptions, createWorkflowCatalogChannel, defaultCatalogDeps };
23
+ //# sourceMappingURL=workflowCatalogChannel.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowCatalogChannel.d.mts","names":[],"sources":["../../src/adapters/workflowCatalogChannel.ts"],"mappings":";;;;iBAcgB,sBAAsB;UAiBrB;EACf,OAAO;EACP;;;;;;;;;;;;iBAac,6BAA6B,UAAS,gCAAqC"}
@@ -0,0 +1,2 @@
1
+ import{createWorkflowCatalogReader as e,presentWorkflowCatalog as t}from"../services/webWorkflowCatalog.mjs";import{WORKFLOW_CATALOG_TYPE as n}from"../types/webWorkflows.mjs";import{statSync as r}from"node:fs";import{readWorkflowCatalog as i,summarizeWorkflow as a}from"@agimon-ai/workflow-mcp";function o(){return{list:e=>i(e),summarize:e=>a(e),stamp:e=>{try{let t=r(e);return{size:t.size,modifiedAt:t.mtimeMs}}catch{return}}}}function s(r={}){let i=e(r.deps??o()),a=r.refreshMs??15e3;return{frameType:n,start(e){let n=new Map,r=new Map,o=async e=>{try{let n=t(await i.read(e.cwd));return{cwd:e.cwd,workflows:n}}catch(t){let n=t instanceof Error?t.message:String(t);return{cwd:e.cwd,workflows:[],warning:n}}},s=new Map,l=()=>{let e=new Set;for(let t of s.values())for(let n of t)e.add(n);i.forget(e)},u=async t=>{let n=await o(t);s.set(t.sessionId,new Set(n.workflows.map(e=>e.path))),l();let i=JSON.stringify(n);r.get(t.sessionId)!==i&&(r.set(t.sessionId,i),n.warning!==void 0&&e.onNotice(`workflow catalog for ${t.cwd} is unavailable (${n.warning})`),e.publish(t.sessionId,n))},d=setInterval(()=>{for(let e of n.values())u(e)},a);return d.unref?.(),{payloadFor(e){if(n.has(e.sessionId))return u(e),c(r,e.sessionId)},sessionAdded(e){n.set(e.sessionId,e),u(e)},sessionRemoved(e){n.delete(e),r.delete(e),s.delete(e),l()},close(){clearInterval(d)}}}}}function c(e,t){let n=e.get(t);if(n!==void 0)return JSON.parse(n)}export{s as createWorkflowCatalogChannel,o as defaultCatalogDeps};
2
+ //# sourceMappingURL=workflowCatalogChannel.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowCatalogChannel.mjs","names":[],"sources":["../../src/adapters/workflowCatalogChannel.ts"],"sourcesContent":["import { statSync } from 'node:fs';\nimport { readWorkflowCatalog, summarizeWorkflow } from '@agimon-ai/workflow-mcp';\nimport type { HubChannelSource, HubSessionScope, WebHubChannel } from '@agimon-ai/doompi-web-contracts';\nimport {\n createWorkflowCatalogReader,\n presentWorkflowCatalog,\n type WorkflowCatalogReaderDeps,\n} from '../services/webWorkflowCatalog.ts';\nimport { WORKFLOW_CATALOG_TYPE, type WorkflowCatalogPayload } from '../types/webWorkflows.ts';\n\n/** Workflow files change a few times a week; this only has to beat a reload. */\nconst CATALOG_REFRESH_MS = 15_000;\n\n/** Reads the engine's own catalog, which is what `SPC w l` lists. */\nexport function defaultCatalogDeps(): WorkflowCatalogReaderDeps {\n return {\n list: (directory) => readWorkflowCatalog(directory),\n summarize: (path) => summarizeWorkflow(path),\n stamp: (path) => {\n try {\n const stats = statSync(path);\n return { size: stats.size, modifiedAt: stats.mtimeMs };\n } catch {\n // The file was listed and then removed. The caller parses it anyway\n // and gets that file's own error, it just cannot cache the answer.\n return undefined;\n }\n },\n };\n}\n\nexport interface WorkflowCatalogChannelOptions {\n deps?: WorkflowCatalogReaderDeps;\n refreshMs?: number;\n}\n\n/**\n * The workflow catalog data channel: what each managed session's directory can\n * launch, published as 'workflow_catalog' payloads.\n *\n * The catalog is read once per session on arrival, again at every subscribe,\n * and on a slow tick so a workflow file written while the page is open shows up\n * without a reload. A failed read publishes an empty list with the reason\n * rather than nothing, because a drawer that says why it is empty beats one\n * that looks like a repository with no workflows.\n */\nexport function createWorkflowCatalogChannel(options: WorkflowCatalogChannelOptions = {}): WebHubChannel {\n const reader = createWorkflowCatalogReader(options.deps ?? defaultCatalogDeps());\n const refreshMs = options.refreshMs ?? CATALOG_REFRESH_MS;\n return {\n frameType: WORKFLOW_CATALOG_TYPE,\n start(host) {\n const scopes = new Map<string, HubSessionScope>();\n const lastPublished = new Map<string, string>();\n\n const compute = async (scope: HubSessionScope): Promise<WorkflowCatalogPayload> => {\n try {\n const workflows = presentWorkflowCatalog(await reader.read(scope.cwd));\n return { cwd: scope.cwd, workflows };\n } catch (error) {\n const warning = error instanceof Error ? error.message : String(error);\n return { cwd: scope.cwd, workflows: [], warning };\n }\n };\n\n /** Which files each session's last read listed, so the parse cache can be trimmed. */\n const listed = new Map<string, Set<string>>();\n const forgetUnlisted = (): void => {\n const keep = new Set<string>();\n for (const paths of listed.values()) {\n for (const path of paths) keep.add(path);\n }\n reader.forget(keep);\n };\n\n /** Recomputes and remembers; publishes only what changed, so the tick stays quiet. */\n const refresh = async (scope: HubSessionScope): Promise<void> => {\n const payload = await compute(scope);\n listed.set(scope.sessionId, new Set(payload.workflows.map((workflow) => workflow.path)));\n forgetUnlisted();\n const json = JSON.stringify(payload);\n if (lastPublished.get(scope.sessionId) === json) return;\n lastPublished.set(scope.sessionId, json);\n if (payload.warning !== undefined) {\n host.onNotice(`workflow catalog for ${scope.cwd} is unavailable (${payload.warning})`);\n }\n host.publish(scope.sessionId, payload);\n };\n\n const timer = setInterval(() => {\n for (const scope of scopes.values()) void refresh(scope);\n }, refreshMs);\n timer.unref?.();\n\n const source: HubChannelSource = {\n /**\n * Reading a directory is asynchronous and a snapshot is not, so a page\n * subscribing gets the last catalog read for its session and the fresh\n * one a moment later on the channel. The first subscriber of a session\n * gets no snapshot and only the published read.\n */\n payloadFor(scope) {\n if (!scopes.has(scope.sessionId)) return undefined;\n void refresh(scope);\n return cached(lastPublished, scope.sessionId);\n },\n sessionAdded(scope) {\n scopes.set(scope.sessionId, scope);\n void refresh(scope);\n },\n sessionRemoved(sessionId) {\n scopes.delete(sessionId);\n lastPublished.delete(sessionId);\n listed.delete(sessionId);\n forgetUnlisted();\n },\n close() {\n clearInterval(timer);\n },\n };\n return source;\n },\n };\n}\n\n/** The last payload published for a session, so a subscribe answers at once. */\nfunction cached(published: Map<string, string>, sessionId: string): WorkflowCatalogPayload | undefined {\n const json = published.get(sessionId);\n if (json === undefined) return undefined;\n return JSON.parse(json) as WorkflowCatalogPayload;\n}\n"],"mappings":"uSAcA,SAAgB,GAAgD,CAC9D,MAAO,CACL,KAAO,GAAc,EAAoB,CAAS,EAClD,UAAY,GAAS,EAAkB,CAAI,EAC3C,MAAQ,GAAS,CACf,GAAI,CACF,IAAM,EAAQ,EAAS,CAAI,EAC3B,MAAO,CAAE,KAAM,EAAM,KAAM,WAAY,EAAM,OAAQ,CACvD,MAAQ,CAGN,MACF,CACF,CACF,CACF,CAiBA,SAAgB,EAA6B,EAAyC,CAAC,EAAkB,CACvG,IAAM,EAAS,EAA4B,EAAQ,MAAQ,EAAmB,CAAC,EACzE,EAAY,EAAQ,WAAa,KACvC,MAAO,CACL,UAAW,EACX,MAAM,EAAM,CACV,IAAM,EAAS,IAAI,IACb,EAAgB,IAAI,IAEpB,EAAU,KAAO,IAA4D,CACjF,GAAI,CACF,IAAM,EAAY,EAAuB,MAAM,EAAO,KAAK,EAAM,GAAG,CAAC,EACrE,MAAO,CAAE,IAAK,EAAM,IAAK,WAAU,CACrC,OAAS,EAAO,CACd,IAAM,EAAU,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EACrE,MAAO,CAAE,IAAK,EAAM,IAAK,UAAW,CAAC,EAAG,SAAQ,CAClD,CACF,EAGM,EAAS,IAAI,IACb,MAA6B,CACjC,IAAM,EAAO,IAAI,IACjB,IAAK,IAAM,KAAS,EAAO,OAAO,EAChC,IAAK,IAAM,KAAQ,EAAO,EAAK,IAAI,CAAI,EAEzC,EAAO,OAAO,CAAI,CACpB,EAGM,EAAU,KAAO,IAA0C,CAC/D,IAAM,EAAU,MAAM,EAAQ,CAAK,EACnC,EAAO,IAAI,EAAM,UAAW,IAAI,IAAI,EAAQ,UAAU,IAAK,GAAa,EAAS,IAAI,CAAC,CAAC,EACvF,EAAe,EACf,IAAM,EAAO,KAAK,UAAU,CAAO,EAC/B,EAAc,IAAI,EAAM,SAAS,IAAM,IAC3C,EAAc,IAAI,EAAM,UAAW,CAAI,EACnC,EAAQ,UAAY,IAAA,IACtB,EAAK,SAAS,wBAAwB,EAAM,IAAI,mBAAmB,EAAQ,QAAQ,EAAE,EAEvF,EAAK,QAAQ,EAAM,UAAW,CAAO,EACvC,EAEM,EAAQ,gBAAkB,CAC9B,IAAK,IAAM,KAAS,EAAO,OAAO,EAAG,EAAa,CAAK,CACzD,EAAG,CAAS,EA6BZ,OA5BA,EAAM,QAAQ,EA4BP,CAnBL,WAAW,EAAO,CACX,KAAO,IAAI,EAAM,SAAS,EAE/B,OADA,EAAa,CAAK,EACX,EAAO,EAAe,EAAM,SAAS,CAC9C,EACA,aAAa,EAAO,CAClB,EAAO,IAAI,EAAM,UAAW,CAAK,EACjC,EAAa,CAAK,CACpB,EACA,eAAe,EAAW,CACxB,EAAO,OAAO,CAAS,EACvB,EAAc,OAAO,CAAS,EAC9B,EAAO,OAAO,CAAS,EACvB,EAAe,CACjB,EACA,OAAQ,CACN,cAAc,CAAK,CACrB,CAEU,CACd,CACF,CACF,CAGA,SAAS,EAAO,EAAgC,EAAuD,CACrG,IAAM,EAAO,EAAU,IAAI,CAAS,EAChC,OAAS,IAAA,GACb,OAAO,KAAK,MAAM,CAAI,CACxB"}
@@ -0,0 +1,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../services/workflowTerminal.cjs"),n=require("../types/webWorkflowTerminal.cjs");let r=require("node:crypto"),i=require("node:fs");i=e.__toESM(i,1);let a=require("node:path");a=e.__toESM(a,1);let o=require("node:stream"),s=require("@agimon-ai/workflow-mcp"),c=require("hono"),l=require("hono/streaming");const u=[`running`,`error`,`completed`],d=524288,f=/^[\w.@-]+$/,p={".aac":`audio/aac`,".bmp":`image/bmp`,".css":`text/css`,".csv":`text/csv`,".gif":`image/gif`,".htm":`text/html`,".html":`text/html`,".ico":`image/x-icon`,".jpeg":`image/jpeg`,".jpg":`image/jpeg`,".js":`text/javascript`,".json":`application/json`,".jsx":`text/jsx`,".m4a":`audio/mp4`,".markdown":`text/markdown`,".md":`text/markdown`,".mov":`video/quicktime`,".mp3":`audio/mpeg`,".mp4":`video/mp4`,".oga":`audio/ogg`,".ogg":`audio/ogg`,".ogv":`video/ogg`,".pdf":`application/pdf`,".png":`image/png`,".sh":`text/x-shellscript`,".svg":`image/svg+xml`,".toml":`text/toml`,".ts":`text/typescript`,".tsv":`text/tab-separated-values`,".tsx":`text/tsx`,".txt":`text/plain`,".wav":`audio/wav`,".webm":`video/webm`,".webp":`image/webp`,".xml":`application/xml`,".yaml":`text/yaml`,".yml":`text/yaml`};async function m(e){let t=await e.req.json().catch(()=>void 0);return typeof t==`object`&&t&&!Array.isArray(t)?t:{}}function h(e){return e!==``&&e!==`.`&&e!==`..`&&f.test(e)}function g(e,t){return`${e}/${t}`}function _(e={}){let a=e.registry??new s.WorkflowRegistryService,o=e.terminal??new s.WorkflowTerminalService,d=t.createWorkflowTerminalService({terminal:o,now:e.now??(()=>Date.now())}),f=new c.Hono,p=async(e,t)=>{if(!(!h(e)||!h(t)))for(let n of u)try{return await a.readRunByKey(e,n,t)}catch{}},_=(e,t)=>({error:`No workflow run '${t}' in workspace '${e}'.`});return f.get(`/runs/:workspace/:runKey/screen/stream`,async e=>{let t=e.req.param(`workspace`),r=e.req.param(`runKey`),i=await p(t,r);if(i===void 0)return e.json(_(t,r),404);let a=g(t,r);return(0,l.streamSSE)(e,async e=>{let o=0,s=!0;for(e.onAbort(()=>{s=!1});s;){let s=await p(t,r)??i,c=d.capabilities(s),l=c.readable?await d.screen(a,s,48):[],u=s.stage!==`running`;u&&(o+=1);let f=u&&o>=4,m={lines:l,capabilities:c,...f?{ended:!0}:{}};if(await e.writeSSE({event:n.WORKFLOW_SCREEN_EVENT,data:JSON.stringify(m)}),f)break;await e.sleep(500)}d.forget(new Set)})}),f.post(`/runs/:workspace/:runKey/control`,async e=>{let t=e.req.param(`workspace`),n=e.req.param(`runKey`),i=await p(t,n);if(i===void 0)return e.json(_(t,n),404);let a=g(t,n),o=await m(e),s=d.capabilities(i);if(o.release===!0)return typeof o.token==`string`&&d.releaseControl(a,o.token),e.json({held:!1});if(!s.writable){let t={held:!1,reason:s.reason};return e.json(t,409)}let c=typeof o.token==`string`&&o.token!==``?o.token:(0,r.randomUUID)();if(!d.takeControl(a,c))return e.json({held:!1,reason:`Another reader holds the keyboard.`},409);let l={held:!0,token:c};return e.json(l)}),f.post(`/runs/:workspace/:runKey/keys`,async e=>{let t=e.req.param(`workspace`),n=e.req.param(`runKey`),r=await p(t,n);if(r===void 0)return e.json(_(t,n),404);let i=await m(e);if(typeof i.token!=`string`||typeof i.data!=`string`)return e.json({error:`A keystroke needs a control token and its data.`},400);try{await d.write(g(t,n),r,i.token,i.data)}catch(t){return e.json({error:t instanceof Error?t.message:String(t)},409)}return e.body(null,204)}),f.post(`/runs/:workspace/:runKey/resize`,async e=>{let t=e.req.param(`workspace`),n=e.req.param(`runKey`),r=await p(t,n);if(r===void 0)return e.json(_(t,n),404);let{token:i,columns:a,rows:o}=await m(e);if(typeof i!=`string`||typeof a!=`number`||typeof o!=`number`)return e.json({error:`A resize needs a control token, columns and rows.`},400);try{let s=await d.resize(g(t,n),r,i,a,o);return e.json({resized:s})}catch(t){return e.json({error:t instanceof Error?t.message:String(t)},409)}}),f.delete(`/runs/:workspace/:runKey`,async e=>{let t=e.req.param(`workspace`),n=e.req.param(`runKey`),r=await p(t,n);if(r===void 0)return e.json(_(t,n),404);if(r.stage===`running`)return e.json({error:`A running workflow must be stopped before it can be deleted.`},409);try{i.default.rmSync(a.runDirectoryFor(r),{recursive:!0,force:!0})}catch(t){return e.json({error:t instanceof Error?t.message:String(t)},409)}return e.json({deleted:!0})}),f.get(`/runs/:workspace/:runKey/artifacts`,async e=>{let t=e.req.param(`workspace`),n=e.req.param(`runKey`),r=await p(t,n);if(r===void 0)return e.json(_(t,n),404);let i=a.runDirectoryFor(r),o={runDir:i,description:r.runDirectory?.description??``,artifacts:S(i,r)};return e.json(o)}),f.get(`/runs/:workspace/:runKey/artifacts/:name{.+}`,async e=>{let t=e.req.param(`workspace`),n=e.req.param(`runKey`),r=await p(t,n);if(r===void 0)return e.json(_(t,n),404);let i=a.runDirectoryFor(r),o=e.req.param(`name`),s=v(i,o);if(s===void 0)return e.json({error:`'${o}' is not inside this run.`},400);if(e.req.query(`raw`)===`1`)return E(s,o,e.req.header(`range`),e.req.query(`download`)===`1`)??e.json({error:`'${o}' has not been written.`},404);let c=T(s,o);return c===void 0?e.json({error:`'${o}' has not been written.`},404):e.json(c)}),f}function v(e,t){let n=a.default.resolve(e,t),r=a.default.resolve(e);if(!(n!==r&&!n.startsWith(`${r}${a.default.sep}`)))return n}function y(e){try{return i.default.statSync(e)}catch{return}}function b(e){return e===void 0?`pending`:e.isDirectory()?`written`:e.size===0?`empty`:`written`}function x(e,t){let n=y(a.default.join(e,t.path));return{...t,state:b(n),...n===void 0?{}:{size:n.size,modifiedAt:n.mtime.toISOString()}}}function S(e,t){let n=(t.runDirectory?.entries??[]).map(t=>x(e,{path:t.path,kind:t.kind,description:t.description,producedBy:t[`produced-by`]??[],declared:!0})),r=new Set(n.map(e=>e.path)),a;try{a=i.default.readdirSync(e,{withFileTypes:!0})}catch{return n}let o=a.filter(e=>!r.has(e.name)).map(t=>x(e,{path:t.name,kind:t.isDirectory()?`directory`:`file`,description:``,producedBy:[],declared:!1})).sort((e,t)=>e.path.localeCompare(t.path));return[...n,...o]}function C(e){return p[a.default.extname(e).toLowerCase()]??`application/octet-stream`}function w(e){return e.startsWith(`text/`)||e===`application/json`||e===`application/xml`}function T(e,t){let n=y(e);if(n===void 0||n.isDirectory())return;let r=C(t),a;if(w(r))try{let t=i.default.openSync(e,`r`);try{let e=Buffer.alloc(Math.min(n.size,d));i.default.readSync(t,e,0,e.length,0),a=e.toString(`utf8`)}finally{i.default.closeSync(t)}}catch{return}return{path:t,size:n.size,modifiedAt:n.mtime.toISOString(),mimeType:r,...a===void 0?{}:{text:a},truncated:a!==void 0&&n.size>d}}function E(e,t,n,r){let s=y(e);if(s===void 0||s.isDirectory())return;let c=C(t),l=0,u=Math.max(0,s.size-1),d=!1,f=n?.match(/^bytes=(\d*)-(\d*)$/);if(f!=null&&s.size>0){let e=f[1]===``?void 0:Number(f[1]),t=f[2]===``?void 0:Number(f[2]);if(e===void 0&&t!==void 0?l=Math.max(0,s.size-t):e!==void 0&&(l=e,u=t===void 0?u:Math.min(t,u)),l>=s.size||l>u)return new Response(null,{status:416,headers:{"content-range":`bytes */${String(s.size)}`}});d=!0}let p=a.default.basename(t).replace(/["\\\r\n]/g,`_`),m=new Headers({"accept-ranges":`bytes`,"content-disposition":`${r||c===`text/html`?`attachment`:`inline`}; filename="${p}"`,"content-length":String(s.size===0?0:u-l+1),"content-type":c,"x-content-type-options":`nosniff`});if(d&&m.set(`content-range`,`bytes ${String(l)}-${String(u)}/${String(s.size)}`),s.size===0)return new Response(null,{status:200,headers:m});let h=o.Readable.toWeb(i.default.createReadStream(e,{start:l,end:u}));return new Response(h,{status:d?206:200,headers:m})}const D={basePath:n.WORKFLOW_API_BASE_PATH,start(e){let t=_();return{fetch:e=>t.fetch(e),close:()=>void 0}}};exports.api=D,exports.createWorkflowHubApi=_;
2
+ //# sourceMappingURL=workflowHubApi.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowHubApi.cjs","names":["WorkflowRegistryService","WorkflowTerminalFacade","createWorkflowTerminalService","Hono","streamSSE","WORKFLOW_SCREEN_EVENT","randomUUID","path","fs","Readable","WORKFLOW_API_BASE_PATH"],"sources":["../../src/adapters/workflowHubApi.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { Readable } from 'node:stream';\nimport {\n WorkflowRegistryService,\n WorkflowTerminalService as WorkflowTerminalFacade,\n type WorkflowRunRecord,\n type WorkflowStage,\n} from '@agimon-ai/workflow-mcp';\nimport type { DoomApi, DoomApiContext, DoomApiHandler } from '@agimon-ai/doompi-extension-contracts/package-api';\nimport { type Context, Hono } from 'hono';\nimport { streamSSE } from 'hono/streaming';\nimport { createWorkflowTerminalService } from '../services/workflowTerminal.ts';\nimport {\n WORKFLOW_API_BASE_PATH,\n WORKFLOW_SCREEN_EVENT,\n type WorkflowArtifactContentResponse,\n type WorkflowArtifactsResponse,\n type WorkflowArtifactView,\n type WorkflowControlResponse,\n type WorkflowDeleteResponse,\n type WorkflowScreenEvent,\n} from '../types/webWorkflowTerminal.ts';\n\n/** Stages a run can be recorded under, newest first: a live run is the common case. */\nconst STAGES: readonly WorkflowStage[] = ['running', 'error', 'completed'];\n/** Lines of screen the stream sends, which is a terminal's visible height plus room. */\nconst SCREEN_LINES = 48;\n/** How often the stream re-reads; the service coalesces, so this is an upper bound. */\nconst STREAM_TICK_MS = 500;\n/** How often a settled run is re-checked before the stream closes itself. */\nconst SETTLED_POLL_TICKS = 4;\n/** Bytes of one textual artifact returned as JSON; binary previews use a stream. */\nconst MAX_ARTIFACT_BYTES = 512 * 1024;\n/** Path segments a client supplies are names, never paths. */\nconst SAFE_SEGMENT = /^[\\w.@-]+$/;\nconst ARTIFACT_MIME_TYPES: Readonly<Record<string, string>> = {\n '.aac': 'audio/aac',\n '.bmp': 'image/bmp',\n '.css': 'text/css',\n '.csv': 'text/csv',\n '.gif': 'image/gif',\n '.htm': 'text/html',\n '.html': 'text/html',\n '.ico': 'image/x-icon',\n '.jpeg': 'image/jpeg',\n '.jpg': 'image/jpeg',\n '.js': 'text/javascript',\n '.json': 'application/json',\n '.jsx': 'text/jsx',\n '.m4a': 'audio/mp4',\n '.markdown': 'text/markdown',\n '.md': 'text/markdown',\n '.mov': 'video/quicktime',\n '.mp3': 'audio/mpeg',\n '.mp4': 'video/mp4',\n '.oga': 'audio/ogg',\n '.ogg': 'audio/ogg',\n '.ogv': 'video/ogg',\n '.pdf': 'application/pdf',\n '.png': 'image/png',\n '.sh': 'text/x-shellscript',\n '.svg': 'image/svg+xml',\n '.toml': 'text/toml',\n '.ts': 'text/typescript',\n '.tsv': 'text/tab-separated-values',\n '.tsx': 'text/tsx',\n '.txt': 'text/plain',\n '.wav': 'audio/wav',\n '.webm': 'video/webm',\n '.webp': 'image/webp',\n '.xml': 'application/xml',\n '.yaml': 'text/yaml',\n '.yml': 'text/yaml',\n};\n\n/**\n * One request's JSON body as a plain record.\n *\n * A body that is absent, malformed, or not an object reads as empty rather\n * than failing the route: every field is checked by the route anyway, and a\n * 400 naming the missing field beats a parse error naming nothing.\n */\nasync function jsonBody(context: Context): Promise<Record<string, unknown>> {\n const parsed: unknown = await context.req.json().catch(() => undefined);\n return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n}\n\n/** Rejects anything that could climb out of the registry before a path is built. */\nfunction isSafeSegment(value: string): boolean {\n return value !== '' && value !== '.' && value !== '..' && SAFE_SEGMENT.test(value);\n}\n\n/** One run's identity for caches and leases, stable across stage moves. */\nfunction runIdentityOf(workspace: string, runKey: string): string {\n return `${workspace}/${runKey}`;\n}\n\nexport interface WorkflowHubApiOptions {\n registry?: WorkflowRegistryService;\n terminal?: WorkflowTerminalFacade;\n now?: () => number;\n}\n\n/**\n * This package's HTTP surface: one workflow run's terminal, and the files its\n * run directory holds.\n *\n * Mounted in the hub rather than in a session's server, because a run's\n * terminal belongs to its multiplexer rather than to the session that launched\n * it: any process can read a tmux pane, and the session's own server cannot\n * reach anything more. The routes therefore name a run, never a session.\n */\nexport function createWorkflowHubApi(options: WorkflowHubApiOptions = {}): Hono {\n const registry = options.registry ?? new WorkflowRegistryService();\n const facade = options.terminal ?? new WorkflowTerminalFacade();\n const terminal = createWorkflowTerminalService<WorkflowRunRecord>({\n terminal: facade,\n now: options.now ?? (() => Date.now()),\n });\n const app = new Hono();\n\n /** The record behind a request, whichever stage it now sits in. */\n const runOf = async (workspace: string, runKey: string): Promise<WorkflowRunRecord | undefined> => {\n if (!isSafeSegment(workspace) || !isSafeSegment(runKey)) return undefined;\n for (const stage of STAGES) {\n try {\n return await registry.readRunByKey(workspace, stage, runKey);\n } catch {\n // Not in this stage. A run moves between them as it settles, so a miss\n // here is ordinary; only a miss in every stage means no such run.\n }\n }\n return undefined;\n };\n\n const missing = (workspace: string, runKey: string): { error: string } => ({\n error: `No workflow run '${runKey}' in workspace '${workspace}'.`,\n });\n\n app.get('/runs/:workspace/:runKey/screen/stream', async (context) => {\n const workspace = context.req.param('workspace');\n const runKey = context.req.param('runKey');\n const record = await runOf(workspace, runKey);\n if (record === undefined) return context.json(missing(workspace, runKey), 404);\n const identity = runIdentityOf(workspace, runKey);\n\n return streamSSE(context, async (stream) => {\n let settledTicks = 0;\n let running = true;\n stream.onAbort(() => {\n running = false;\n });\n while (running) {\n const current = (await runOf(workspace, runKey)) ?? record;\n const capabilities = terminal.capabilities(current);\n const lines = capabilities.readable ? await terminal.screen(identity, current, SCREEN_LINES) : [];\n // A settled run is read a few more times before the stream closes: the\n // last thing a failing step printed is what the reader came for, and it\n // lands after the record has already moved to its final stage.\n const settled = current.stage !== 'running';\n if (settled) settledTicks += 1;\n const ended = settled && settledTicks >= SETTLED_POLL_TICKS;\n const event: WorkflowScreenEvent = { lines, capabilities, ...(ended ? { ended: true } : {}) };\n await stream.writeSSE({ event: WORKFLOW_SCREEN_EVENT, data: JSON.stringify(event) });\n if (ended) break;\n await stream.sleep(STREAM_TICK_MS);\n }\n terminal.forget(new Set());\n });\n });\n\n app.post('/runs/:workspace/:runKey/control', async (context) => {\n const workspace = context.req.param('workspace');\n const runKey = context.req.param('runKey');\n const record = await runOf(workspace, runKey);\n if (record === undefined) return context.json(missing(workspace, runKey), 404);\n const identity = runIdentityOf(workspace, runKey);\n const body = await jsonBody(context);\n const capabilities = terminal.capabilities(record);\n\n if (body.release === true) {\n if (typeof body.token === 'string') terminal.releaseControl(identity, body.token);\n const released: WorkflowControlResponse = { held: false };\n return context.json(released);\n }\n if (!capabilities.writable) {\n const refused: WorkflowControlResponse = { held: false, reason: capabilities.reason };\n return context.json(refused, 409);\n }\n const token = typeof body.token === 'string' && body.token !== '' ? body.token : randomUUID();\n if (!terminal.takeControl(identity, token)) {\n const taken: WorkflowControlResponse = { held: false, reason: 'Another reader holds the keyboard.' };\n return context.json(taken, 409);\n }\n const held: WorkflowControlResponse = { held: true, token };\n return context.json(held);\n });\n\n app.post('/runs/:workspace/:runKey/keys', async (context) => {\n const workspace = context.req.param('workspace');\n const runKey = context.req.param('runKey');\n const record = await runOf(workspace, runKey);\n if (record === undefined) return context.json(missing(workspace, runKey), 404);\n const body = await jsonBody(context);\n if (typeof body.token !== 'string' || typeof body.data !== 'string') {\n return context.json({ error: 'A keystroke needs a control token and its data.' }, 400);\n }\n try {\n await terminal.write(runIdentityOf(workspace, runKey), record, body.token, body.data);\n } catch (error) {\n return context.json({ error: error instanceof Error ? error.message : String(error) }, 409);\n }\n return context.body(null, 204);\n });\n\n app.post('/runs/:workspace/:runKey/resize', async (context) => {\n const workspace = context.req.param('workspace');\n const runKey = context.req.param('runKey');\n const record = await runOf(workspace, runKey);\n if (record === undefined) return context.json(missing(workspace, runKey), 404);\n const body = await jsonBody(context);\n const { token, columns, rows } = body;\n if (typeof token !== 'string' || typeof columns !== 'number' || typeof rows !== 'number') {\n return context.json({ error: 'A resize needs a control token, columns and rows.' }, 400);\n }\n try {\n const resized = await terminal.resize(runIdentityOf(workspace, runKey), record, token, columns, rows);\n return context.json({ resized });\n } catch (error) {\n return context.json({ error: error instanceof Error ? error.message : String(error) }, 409);\n }\n });\n\n app.delete('/runs/:workspace/:runKey', async (context) => {\n const workspace = context.req.param('workspace');\n const runKey = context.req.param('runKey');\n const record = await runOf(workspace, runKey);\n if (record === undefined) return context.json(missing(workspace, runKey), 404);\n if (record.stage === 'running') {\n return context.json({ error: 'A running workflow must be stopped before it can be deleted.' }, 409);\n }\n try {\n fs.rmSync(registry.runDirectoryFor(record), { recursive: true, force: true });\n } catch (error) {\n return context.json({ error: error instanceof Error ? error.message : String(error) }, 409);\n }\n const response: WorkflowDeleteResponse = { deleted: true };\n return context.json(response);\n });\n\n app.get('/runs/:workspace/:runKey/artifacts', async (context) => {\n const workspace = context.req.param('workspace');\n const runKey = context.req.param('runKey');\n const record = await runOf(workspace, runKey);\n if (record === undefined) return context.json(missing(workspace, runKey), 404);\n const runDir = registry.runDirectoryFor(record);\n const body: WorkflowArtifactsResponse = {\n runDir,\n description: record.runDirectory?.description ?? '',\n artifacts: readArtifacts(runDir, record),\n };\n return context.json(body);\n });\n\n app.get('/runs/:workspace/:runKey/artifacts/:name{.+}', async (context) => {\n const workspace = context.req.param('workspace');\n const runKey = context.req.param('runKey');\n const record = await runOf(workspace, runKey);\n if (record === undefined) return context.json(missing(workspace, runKey), 404);\n const runDir = registry.runDirectoryFor(record);\n const requested = context.req.param('name');\n const resolved = resolveInside(runDir, requested);\n if (resolved === undefined) return context.json({ error: `'${requested}' is not inside this run.` }, 400);\n if (context.req.query('raw') === '1') {\n const response = streamArtifact(\n resolved,\n requested,\n context.req.header('range'),\n context.req.query('download') === '1',\n );\n return response ?? context.json({ error: `'${requested}' has not been written.` }, 404);\n }\n const body = readArtifact(resolved, requested);\n if (body === undefined) return context.json({ error: `'${requested}' has not been written.` }, 404);\n return context.json(body);\n });\n\n return app;\n}\n\n/**\n * A path inside the run directory, or undefined when it points anywhere else.\n *\n * The client supplies this one verbatim, unlike the run key, so it is resolved\n * and then checked rather than trusted: without the check the route would read\n * any file the hub can reach.\n */\nfunction resolveInside(runDir: string, requested: string): string | undefined {\n const resolved = path.resolve(runDir, requested);\n const root = path.resolve(runDir);\n if (resolved !== root && !resolved.startsWith(`${root}${path.sep}`)) return undefined;\n return resolved;\n}\n\n/** Size and modification time of one path, or undefined when it is not there. */\nfunction statOf(target: string): fs.Stats | undefined {\n try {\n return fs.statSync(target);\n } catch {\n return undefined; // Not written yet, which is an ordinary state for an artifact.\n }\n}\n\nfunction stateOf(stats: fs.Stats | undefined): WorkflowArtifactView['state'] {\n if (stats === undefined) return 'pending';\n if (stats.isDirectory()) return 'written';\n return stats.size === 0 ? 'empty' : 'written';\n}\n\nfunction viewOf(\n runDir: string,\n entry: Omit<WorkflowArtifactView, 'state' | 'size' | 'modifiedAt'>,\n): WorkflowArtifactView {\n const stats = statOf(path.join(runDir, entry.path));\n return {\n ...entry,\n state: stateOf(stats),\n ...(stats === undefined ? {} : { size: stats.size, modifiedAt: stats.mtime.toISOString() }),\n };\n}\n\n/**\n * What the run directory holds: the workflow's own declaration first, then the\n * files nobody declared.\n *\n * The declaration is the point of the folder, so it leads and keeps its order\n * even for entries no job has written yet. The rest follows rather than being\n * hidden: the engine's own context.md and progress log are often exactly what a\n * reader is looking for.\n */\nfunction readArtifacts(runDir: string, record: WorkflowRunRecord): WorkflowArtifactView[] {\n const declared = (record.runDirectory?.entries ?? []).map((entry) =>\n viewOf(runDir, {\n path: entry.path,\n kind: entry.kind,\n description: entry.description,\n producedBy: entry['produced-by'] ?? [],\n declared: true,\n }),\n );\n const claimed = new Set(declared.map((entry) => entry.path));\n let names: fs.Dirent[];\n try {\n names = fs.readdirSync(runDir, { withFileTypes: true });\n } catch {\n return declared; // The directory is gone; the declaration is still worth showing.\n }\n const found = names\n .filter((entry) => !claimed.has(entry.name))\n .map((entry) =>\n viewOf(runDir, {\n path: entry.name,\n kind: entry.isDirectory() ? 'directory' : 'file',\n description: '',\n producedBy: [],\n declared: false,\n }),\n )\n .sort((left, right) => left.path.localeCompare(right.path));\n return [...declared, ...found];\n}\n\n/** Browser content type inferred from a filename without trusting client input. */\nfunction artifactMimeType(requested: string): string {\n return ARTIFACT_MIME_TYPES[path.extname(requested).toLowerCase()] ?? 'application/octet-stream';\n}\n\nfunction isTextArtifact(mimeType: string): boolean {\n return mimeType.startsWith('text/') || mimeType === 'application/json' || mimeType === 'application/xml';\n}\n\n/** One artifact's metadata and bounded text, or undefined when it is not readable. */\nfunction readArtifact(resolved: string, requested: string): WorkflowArtifactContentResponse | undefined {\n const stats = statOf(resolved);\n if (stats === undefined || stats.isDirectory()) return undefined;\n const mimeType = artifactMimeType(requested);\n let text: string | undefined;\n if (isTextArtifact(mimeType)) {\n try {\n const handle = fs.openSync(resolved, 'r');\n try {\n const buffer = Buffer.alloc(Math.min(stats.size, MAX_ARTIFACT_BYTES));\n fs.readSync(handle, buffer, 0, buffer.length, 0);\n text = buffer.toString('utf8');\n } finally {\n fs.closeSync(handle);\n }\n } catch {\n return undefined;\n }\n }\n return {\n path: requested,\n size: stats.size,\n modifiedAt: stats.mtime.toISOString(),\n mimeType,\n ...(text === undefined ? {} : { text }),\n truncated: text !== undefined && stats.size > MAX_ARTIFACT_BYTES,\n };\n}\n\n/** Streams media with byte-range support so browser audio, video, and PDF controls can seek. */\nfunction streamArtifact(\n resolved: string,\n requested: string,\n rangeHeader: string | undefined,\n download: boolean,\n): Response | undefined {\n const stats = statOf(resolved);\n if (stats === undefined || stats.isDirectory()) return undefined;\n const mimeType = artifactMimeType(requested);\n let start = 0;\n let end = Math.max(0, stats.size - 1);\n let partial = false;\n const match = rangeHeader?.match(/^bytes=(\\d*)-(\\d*)$/);\n if (match !== null && match !== undefined && stats.size > 0) {\n const requestedStart = match[1] === '' ? undefined : Number(match[1]);\n const requestedEnd = match[2] === '' ? undefined : Number(match[2]);\n if (requestedStart === undefined && requestedEnd !== undefined) {\n start = Math.max(0, stats.size - requestedEnd);\n } else if (requestedStart !== undefined) {\n start = requestedStart;\n end = requestedEnd === undefined ? end : Math.min(requestedEnd, end);\n }\n if (start >= stats.size || start > end) {\n return new Response(null, { status: 416, headers: { 'content-range': `bytes */${String(stats.size)}` } });\n }\n partial = true;\n }\n const filename = path.basename(requested).replace(/[\"\\\\\\r\\n]/g, '_');\n const forceDownload = download || mimeType === 'text/html';\n const headers = new Headers({\n 'accept-ranges': 'bytes',\n 'content-disposition': `${forceDownload ? 'attachment' : 'inline'}; filename=\"${filename}\"`,\n 'content-length': String(stats.size === 0 ? 0 : end - start + 1),\n 'content-type': mimeType,\n 'x-content-type-options': 'nosniff',\n });\n if (partial) headers.set('content-range', `bytes ${String(start)}-${String(end)}/${String(stats.size)}`);\n if (stats.size === 0) return new Response(null, { status: 200, headers });\n const body = Readable.toWeb(fs.createReadStream(resolved, { start, end }));\n return new Response(body, { status: partial ? 206 : 200, headers });\n}\n\n/** The named export a host imports from this package's built hub entry. */\nexport const api: DoomApi = {\n basePath: WORKFLOW_API_BASE_PATH,\n start(_context: DoomApiContext): DoomApiHandler {\n // Hub-scoped: the host hands no session, and these routes want none. A run\n // is addressed by its registry identity, which is machine-wide.\n const app = createWorkflowHubApi();\n return {\n fetch: (request) => app.fetch(request),\n // Nothing outlives a request: a stream's loop ends when its own socket\n // aborts, and the caches it touched are per run rather than per client.\n close: () => undefined,\n };\n },\n};\n"],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { WorkflowRegistryService, WorkflowTerminalService } from "@agimon-ai/workflow-mcp";
2
+ import { DoomApi } from "@agimon-ai/doompi-extension-contracts/package-api";
3
+ import { Hono } from "hono";
4
+ //#region src/adapters/workflowHubApi.d.ts
5
+ interface WorkflowHubApiOptions {
6
+ registry?: WorkflowRegistryService;
7
+ terminal?: WorkflowTerminalService;
8
+ now?: () => number;
9
+ }
10
+ /**
11
+ * This package's HTTP surface: one workflow run's terminal, and the files its
12
+ * run directory holds.
13
+ *
14
+ * Mounted in the hub rather than in a session's server, because a run's
15
+ * terminal belongs to its multiplexer rather than to the session that launched
16
+ * it: any process can read a tmux pane, and the session's own server cannot
17
+ * reach anything more. The routes therefore name a run, never a session.
18
+ */
19
+ declare function createWorkflowHubApi(options?: WorkflowHubApiOptions): Hono;
20
+ /** The named export a host imports from this package's built hub entry. */
21
+ declare const api: DoomApi;
22
+ //#endregion
23
+ export { WorkflowHubApiOptions, api, createWorkflowHubApi };
24
+ //# sourceMappingURL=workflowHubApi.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowHubApi.d.cts","names":[],"sources":["../../src/adapters/workflowHubApi.ts"],"mappings":";;;;UAqGiB;EACf,WAAW;EACX,WAAW;EACX;;;;;;;;;;;iBAYc,qBAAqB,UAAS,wBAA6B;;cAuV9D,KAAK"}
@@ -0,0 +1,24 @@
1
+ import { WorkflowRegistryService, WorkflowTerminalService } from "@agimon-ai/workflow-mcp";
2
+ import { Hono } from "hono";
3
+ import { DoomApi } from "@agimon-ai/doompi-extension-contracts/package-api";
4
+ //#region src/adapters/workflowHubApi.d.ts
5
+ interface WorkflowHubApiOptions {
6
+ registry?: WorkflowRegistryService;
7
+ terminal?: WorkflowTerminalService;
8
+ now?: () => number;
9
+ }
10
+ /**
11
+ * This package's HTTP surface: one workflow run's terminal, and the files its
12
+ * run directory holds.
13
+ *
14
+ * Mounted in the hub rather than in a session's server, because a run's
15
+ * terminal belongs to its multiplexer rather than to the session that launched
16
+ * it: any process can read a tmux pane, and the session's own server cannot
17
+ * reach anything more. The routes therefore name a run, never a session.
18
+ */
19
+ declare function createWorkflowHubApi(options?: WorkflowHubApiOptions): Hono;
20
+ /** The named export a host imports from this package's built hub entry. */
21
+ declare const api: DoomApi;
22
+ //#endregion
23
+ export { WorkflowHubApiOptions, api, createWorkflowHubApi };
24
+ //# sourceMappingURL=workflowHubApi.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowHubApi.d.mts","names":[],"sources":["../../src/adapters/workflowHubApi.ts"],"mappings":";;;;UAqGiB;EACf,WAAW;EACX,WAAW;EACX;;;;;;;;;;;iBAYc,qBAAqB,UAAS,wBAA6B;;cAuV9D,KAAK"}