@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
@@ -0,0 +1,2 @@
1
+ import{createWorkflowTerminalService as e}from"../services/workflowTerminal.mjs";import{WORKFLOW_API_BASE_PATH as t,WORKFLOW_SCREEN_EVENT as n}from"../types/webWorkflowTerminal.mjs";import{randomUUID as r}from"node:crypto";import i from"node:fs";import a from"node:path";import{Readable as o}from"node:stream";import{WorkflowRegistryService as s,WorkflowTerminalService as c}from"@agimon-ai/workflow-mcp";import{Hono as l}from"hono";import{streamSSE as u}from"hono/streaming";const d=[`running`,`error`,`completed`],f=524288,p=/^[\w.@-]+$/,m={".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 h(e){let t=await e.req.json().catch(()=>void 0);return typeof t==`object`&&t&&!Array.isArray(t)?t:{}}function g(e){return e!==``&&e!==`.`&&e!==`..`&&p.test(e)}function _(e,t){return`${e}/${t}`}function v(t={}){let a=t.registry??new s,o=t.terminal??new c,f=e({terminal:o,now:t.now??(()=>Date.now())}),p=new l,m=async(e,t)=>{if(!(!g(e)||!g(t)))for(let n of d)try{return await a.readRunByKey(e,n,t)}catch{}},v=(e,t)=>({error:`No workflow run '${t}' in workspace '${e}'.`});return p.get(`/runs/:workspace/:runKey/screen/stream`,async e=>{let t=e.req.param(`workspace`),r=e.req.param(`runKey`),i=await m(t,r);if(i===void 0)return e.json(v(t,r),404);let a=_(t,r);return u(e,async e=>{let o=0,s=!0;for(e.onAbort(()=>{s=!1});s;){let s=await m(t,r)??i,c=f.capabilities(s),l=c.readable?await f.screen(a,s,48):[],u=s.stage!==`running`;u&&(o+=1);let d=u&&o>=4,p={lines:l,capabilities:c,...d?{ended:!0}:{}};if(await e.writeSSE({event:n,data:JSON.stringify(p)}),d)break;await e.sleep(500)}f.forget(new Set)})}),p.post(`/runs/:workspace/:runKey/control`,async e=>{let t=e.req.param(`workspace`),n=e.req.param(`runKey`),i=await m(t,n);if(i===void 0)return e.json(v(t,n),404);let a=_(t,n),o=await h(e),s=f.capabilities(i);if(o.release===!0)return typeof o.token==`string`&&f.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:r();if(!f.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)}),p.post(`/runs/:workspace/:runKey/keys`,async e=>{let t=e.req.param(`workspace`),n=e.req.param(`runKey`),r=await m(t,n);if(r===void 0)return e.json(v(t,n),404);let i=await h(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 f.write(_(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)}),p.post(`/runs/:workspace/:runKey/resize`,async e=>{let t=e.req.param(`workspace`),n=e.req.param(`runKey`),r=await m(t,n);if(r===void 0)return e.json(v(t,n),404);let{token:i,columns:a,rows:o}=await h(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 f.resize(_(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)}}),p.delete(`/runs/:workspace/:runKey`,async e=>{let t=e.req.param(`workspace`),n=e.req.param(`runKey`),r=await m(t,n);if(r===void 0)return e.json(v(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.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})}),p.get(`/runs/:workspace/:runKey/artifacts`,async e=>{let t=e.req.param(`workspace`),n=e.req.param(`runKey`),r=await m(t,n);if(r===void 0)return e.json(v(t,n),404);let i=a.runDirectoryFor(r),o={runDir:i,description:r.runDirectory?.description??``,artifacts:C(i,r)};return e.json(o)}),p.get(`/runs/:workspace/:runKey/artifacts/:name{.+}`,async e=>{let t=e.req.param(`workspace`),n=e.req.param(`runKey`),r=await m(t,n);if(r===void 0)return e.json(v(t,n),404);let i=a.runDirectoryFor(r),o=e.req.param(`name`),s=y(i,o);if(s===void 0)return e.json({error:`'${o}' is not inside this run.`},400);if(e.req.query(`raw`)===`1`)return D(s,o,e.req.header(`range`),e.req.query(`download`)===`1`)??e.json({error:`'${o}' has not been written.`},404);let c=E(s,o);return c===void 0?e.json({error:`'${o}' has not been written.`},404):e.json(c)}),p}function y(e,t){let n=a.resolve(e,t),r=a.resolve(e);if(!(n!==r&&!n.startsWith(`${r}${a.sep}`)))return n}function b(e){try{return i.statSync(e)}catch{return}}function x(e){return e===void 0?`pending`:e.isDirectory()?`written`:e.size===0?`empty`:`written`}function S(e,t){let n=b(a.join(e,t.path));return{...t,state:x(n),...n===void 0?{}:{size:n.size,modifiedAt:n.mtime.toISOString()}}}function C(e,t){let n=(t.runDirectory?.entries??[]).map(t=>S(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.readdirSync(e,{withFileTypes:!0})}catch{return n}let o=a.filter(e=>!r.has(e.name)).map(t=>S(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 w(e){return m[a.extname(e).toLowerCase()]??`application/octet-stream`}function T(e){return e.startsWith(`text/`)||e===`application/json`||e===`application/xml`}function E(e,t){let n=b(e);if(n===void 0||n.isDirectory())return;let r=w(t),a;if(T(r))try{let t=i.openSync(e,`r`);try{let e=Buffer.alloc(Math.min(n.size,f));i.readSync(t,e,0,e.length,0),a=e.toString(`utf8`)}finally{i.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>f}}function D(e,t,n,r){let s=b(e);if(s===void 0||s.isDirectory())return;let c=w(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.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.toWeb(i.createReadStream(e,{start:l,end:u}));return new Response(h,{status:d?206:200,headers:m})}const O={basePath:t,start(e){let t=v();return{fetch:e=>t.fetch(e),close:()=>void 0}}};export{O as api,v as createWorkflowHubApi};
2
+ //# sourceMappingURL=workflowHubApi.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowHubApi.mjs","names":["WorkflowTerminalFacade"],"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,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../services/workflowRuns.cjs");let n=require("node:fs");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);let i=require("node:os");i=e.__toESM(i,1);function a(e,a={}){let o=a.homeDir??t.resolveWorkflowHome({envValue:process.env.WORKFLOW_MCP_HOME,homeDir:i.default.homedir()}),s=r.default.join(o,t.WORKSPACES_DIR_NAME),c,l,u=`[]`,d=!1,f=new Map,p=new Map;function m(e,t,r,i){let a;try{a=n.default.statSync(t)}catch{return e.delete(t),i}let o=e.get(t);if(o&&o.size===a.size&&o.mtimeMs===a.mtimeMs)return o.value;let s;try{s=n.default.readFileSync(t,`utf8`)}catch{return e.delete(t),i}let c=r(s);return e.set(t,{size:a.size,mtimeMs:a.mtimeMs,value:c}),c}let h=e=>{try{return n.default.readdirSync(e).sort()}catch{return[]}},g=()=>{let e=[],n=new Set;for(let i of h(s))for(let a of t.WORKFLOW_STAGES){let o=r.default.join(s,i,a);for(let i of h(o)){let a=r.default.join(o,i),s=r.default.join(a,t.RUN_RECORD_FILE_NAME),c=r.default.join(a,t.PROGRESS_FILE_NAME);n.add(s),n.add(c);let l=m(f,s,t.parseWorkflowRunRecord,void 0);if(l===void 0)continue;let u=m(p,c,e=>t.foldWorkflowProgress(t.parseWorkflowProgress(e)),[]);e.push({...l,view:t.completeWorkflowRunView(l.view,u)})}}for(let e of f.keys())n.has(e)||f.delete(e);for(let e of p.keys())n.has(e)||p.delete(e);return e},_=()=>{if(d)return;let t=g(),n=JSON.stringify(t);n!==u&&(u=n,e(t))},v=()=>{if(!(c||d))try{c=n.default.watch(s,{recursive:!0},()=>{l&&clearTimeout(l),l=setTimeout(_,80)}),c.on(`error`,()=>{c?.close(),c=void 0})}catch{}};v(),_();let y=setInterval(()=>{v(),_()},2e3);return{close(){d=!0,l&&clearTimeout(l),clearInterval(y),c?.close()}}}exports.watchWorkflowRuns=a;
2
+ //# sourceMappingURL=workflowWatcher.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowWatcher.cjs","names":["resolveWorkflowHome","os","path","WORKSPACES_DIR_NAME","fs","WORKFLOW_STAGES","RUN_RECORD_FILE_NAME","PROGRESS_FILE_NAME","parseWorkflowRunRecord","foldWorkflowProgress","parseWorkflowProgress","completeWorkflowRunView"],"sources":["../../src/adapters/workflowWatcher.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport {\n completeWorkflowRunView,\n foldWorkflowProgress,\n type ParsedWorkflowRun,\n parseWorkflowProgress,\n parseWorkflowRunRecord,\n PROGRESS_FILE_NAME,\n resolveWorkflowHome,\n RUN_RECORD_FILE_NAME,\n WORKFLOW_HOME_ENV,\n WORKFLOW_STAGES,\n WORKSPACES_DIR_NAME,\n} from '../services/workflowRuns.ts';\nimport type { WorkflowJobView } from '../types/webWorkflows.ts';\n\nconst POLL_MS = 2000;\nconst DEBOUNCE_MS = 80;\n\nexport interface WorkflowRunsSource {\n close(): void;\n}\n\nexport interface WatchWorkflowRunsOptions {\n /** Registry home override, the test seam; defaults to the engine's own resolution. */\n homeDir?: string;\n}\n\ninterface CachedParse<T> {\n size: number;\n mtimeMs: number;\n value: T;\n}\n\n/**\n * Watches workflow-mcp's registry, one watcher for the whole hub: the\n * registry is global, per-session scoping happens where the summaries live.\n *\n * Same reliability posture as the other watchers: the poll is the source of\n * truth, fs.watch is only an accelerator, and a missing registry home (no\n * workflow ever ran) is a normal state. Files are re-parsed only when their\n * size or mtime moved, because a scan touches every run the registry holds.\n * Emits the parsed run set whenever it changes, including the transition to\n * empty.\n */\nexport function watchWorkflowRuns(\n onRuns: (runs: ParsedWorkflowRun[]) => void,\n options: WatchWorkflowRunsOptions = {},\n): WorkflowRunsSource {\n const homeDir =\n options.homeDir ?? resolveWorkflowHome({ envValue: process.env[WORKFLOW_HOME_ENV], homeDir: os.homedir() });\n const workspacesDir = path.join(homeDir, WORKSPACES_DIR_NAME);\n\n let watcher: fs.FSWatcher | undefined;\n let debounce: NodeJS.Timeout | undefined;\n // Seeded with the empty fingerprint: a machine with no runs should not\n // announce an empty registry, only a registry that changed.\n let lastEmitted: string | undefined = '[]';\n let closed = false;\n const recordCache = new Map<string, CachedParse<ParsedWorkflowRun | undefined>>();\n const progressCache = new Map<string, CachedParse<WorkflowJobView[]>>();\n\n function cachedParse<T>(\n cache: Map<string, CachedParse<T>>,\n filePath: string,\n parse: (raw: string) => T,\n empty: T,\n ): T {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(filePath);\n } catch {\n cache.delete(filePath);\n return empty; // Absent is a normal state: a run may have no progress yet.\n }\n const cached = cache.get(filePath);\n if (cached && cached.size === stat.size && cached.mtimeMs === stat.mtimeMs) return cached.value;\n let raw: string;\n try {\n raw = fs.readFileSync(filePath, 'utf8');\n } catch {\n cache.delete(filePath);\n return empty; // Mid-move or mid-write; the next pass settles it.\n }\n const value = parse(raw);\n cache.set(filePath, { size: stat.size, mtimeMs: stat.mtimeMs, value });\n return value;\n }\n\n const listDir = (dir: string): string[] => {\n try {\n return fs.readdirSync(dir).sort();\n } catch {\n return []; // The directory appears with the first run.\n }\n };\n\n const scan = (): ParsedWorkflowRun[] => {\n const runs: ParsedWorkflowRun[] = [];\n const seenPaths = new Set<string>();\n for (const workspace of listDir(workspacesDir)) {\n for (const stage of WORKFLOW_STAGES) {\n const stageDir = path.join(workspacesDir, workspace, stage);\n for (const runKey of listDir(stageDir)) {\n const runDir = path.join(stageDir, runKey);\n const recordPath = path.join(runDir, RUN_RECORD_FILE_NAME);\n const progressPath = path.join(runDir, PROGRESS_FILE_NAME);\n seenPaths.add(recordPath);\n seenPaths.add(progressPath);\n const parsed = cachedParse(recordCache, recordPath, parseWorkflowRunRecord, undefined);\n if (parsed === undefined) continue;\n const jobs = cachedParse(\n progressCache,\n progressPath,\n (raw) => foldWorkflowProgress(parseWorkflowProgress(raw)),\n [],\n );\n runs.push({ ...parsed, view: completeWorkflowRunView(parsed.view, jobs) });\n }\n }\n }\n for (const key of recordCache.keys()) if (!seenPaths.has(key)) recordCache.delete(key);\n for (const key of progressCache.keys()) if (!seenPaths.has(key)) progressCache.delete(key);\n return runs;\n };\n\n const emit = (): void => {\n if (closed) return;\n const runs = scan();\n const fingerprint = JSON.stringify(runs);\n if (fingerprint === lastEmitted) return;\n lastEmitted = fingerprint;\n onRuns(runs);\n };\n\n const ensureWatcher = (): void => {\n if (watcher || closed) return;\n try {\n watcher = fs.watch(workspacesDir, { recursive: true }, () => {\n if (debounce) clearTimeout(debounce);\n debounce = setTimeout(emit, DEBOUNCE_MS);\n });\n watcher.on('error', () => {\n watcher?.close();\n watcher = undefined; // The poll re-establishes it once the dir is back.\n });\n } catch {\n // The registry does not exist yet; the poll keeps trying.\n }\n };\n\n ensureWatcher();\n emit();\n const poll = setInterval(() => {\n ensureWatcher();\n emit();\n }, POLL_MS);\n\n return {\n close() {\n closed = true;\n if (debounce) clearTimeout(debounce);\n clearInterval(poll);\n watcher?.close();\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { ParsedWorkflowRun } from "../services/workflowRuns.cjs";
2
+ //#region src/adapters/workflowWatcher.d.ts
3
+ interface WorkflowRunsSource {
4
+ close(): void;
5
+ }
6
+ interface WatchWorkflowRunsOptions {
7
+ /** Registry home override, the test seam; defaults to the engine's own resolution. */
8
+ homeDir?: string;
9
+ }
10
+ /**
11
+ * Watches workflow-mcp's registry, one watcher for the whole hub: the
12
+ * registry is global, per-session scoping happens where the summaries live.
13
+ *
14
+ * Same reliability posture as the other watchers: the poll is the source of
15
+ * truth, fs.watch is only an accelerator, and a missing registry home (no
16
+ * workflow ever ran) is a normal state. Files are re-parsed only when their
17
+ * size or mtime moved, because a scan touches every run the registry holds.
18
+ * Emits the parsed run set whenever it changes, including the transition to
19
+ * empty.
20
+ */
21
+ declare function watchWorkflowRuns(onRuns: (runs: ParsedWorkflowRun[]) => void, options?: WatchWorkflowRunsOptions): WorkflowRunsSource;
22
+ //#endregion
23
+ export { WatchWorkflowRunsOptions, WorkflowRunsSource, watchWorkflowRuns };
24
+ //# sourceMappingURL=workflowWatcher.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowWatcher.d.cts","names":[],"sources":["../../src/adapters/workflowWatcher.ts"],"mappings":";;UAqBiB;EACf;;UAGe;;EAEf;;;;;;;;;;;;;iBAoBc,kBACd,SAAS,MAAM,8BACf,UAAS,2BACR"}
@@ -0,0 +1,24 @@
1
+ import { ParsedWorkflowRun } from "../services/workflowRuns.mjs";
2
+ //#region src/adapters/workflowWatcher.d.ts
3
+ interface WorkflowRunsSource {
4
+ close(): void;
5
+ }
6
+ interface WatchWorkflowRunsOptions {
7
+ /** Registry home override, the test seam; defaults to the engine's own resolution. */
8
+ homeDir?: string;
9
+ }
10
+ /**
11
+ * Watches workflow-mcp's registry, one watcher for the whole hub: the
12
+ * registry is global, per-session scoping happens where the summaries live.
13
+ *
14
+ * Same reliability posture as the other watchers: the poll is the source of
15
+ * truth, fs.watch is only an accelerator, and a missing registry home (no
16
+ * workflow ever ran) is a normal state. Files are re-parsed only when their
17
+ * size or mtime moved, because a scan touches every run the registry holds.
18
+ * Emits the parsed run set whenever it changes, including the transition to
19
+ * empty.
20
+ */
21
+ declare function watchWorkflowRuns(onRuns: (runs: ParsedWorkflowRun[]) => void, options?: WatchWorkflowRunsOptions): WorkflowRunsSource;
22
+ //#endregion
23
+ export { WatchWorkflowRunsOptions, WorkflowRunsSource, watchWorkflowRuns };
24
+ //# sourceMappingURL=workflowWatcher.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowWatcher.d.mts","names":[],"sources":["../../src/adapters/workflowWatcher.ts"],"mappings":";;UAqBiB;EACf;;UAGe;;EAEf;;;;;;;;;;;;;iBAoBc,kBACd,SAAS,MAAM,8BACf,UAAS,2BACR"}
@@ -0,0 +1,2 @@
1
+ import{PROGRESS_FILE_NAME as e,RUN_RECORD_FILE_NAME as t,WORKFLOW_STAGES as n,WORKSPACES_DIR_NAME as r,completeWorkflowRunView as i,foldWorkflowProgress as a,parseWorkflowProgress as o,parseWorkflowRunRecord as s,resolveWorkflowHome as c}from"../services/workflowRuns.mjs";import l from"node:fs";import u from"node:path";import d from"node:os";function f(f,p={}){let m=p.homeDir??c({envValue:process.env.WORKFLOW_MCP_HOME,homeDir:d.homedir()}),h=u.join(m,r),g,_,v=`[]`,y=!1,b=new Map,x=new Map;function S(e,t,n,r){let i;try{i=l.statSync(t)}catch{return e.delete(t),r}let a=e.get(t);if(a&&a.size===i.size&&a.mtimeMs===i.mtimeMs)return a.value;let o;try{o=l.readFileSync(t,`utf8`)}catch{return e.delete(t),r}let s=n(o);return e.set(t,{size:i.size,mtimeMs:i.mtimeMs,value:s}),s}let C=e=>{try{return l.readdirSync(e).sort()}catch{return[]}},w=()=>{let r=[],c=new Set;for(let l of C(h))for(let d of n){let n=u.join(h,l,d);for(let l of C(n)){let d=u.join(n,l),f=u.join(d,t),p=u.join(d,e);c.add(f),c.add(p);let m=S(b,f,s,void 0);if(m===void 0)continue;let h=S(x,p,e=>a(o(e)),[]);r.push({...m,view:i(m.view,h)})}}for(let e of b.keys())c.has(e)||b.delete(e);for(let e of x.keys())c.has(e)||x.delete(e);return r},T=()=>{if(y)return;let e=w(),t=JSON.stringify(e);t!==v&&(v=t,f(e))},E=()=>{if(!(g||y))try{g=l.watch(h,{recursive:!0},()=>{_&&clearTimeout(_),_=setTimeout(T,80)}),g.on(`error`,()=>{g?.close(),g=void 0})}catch{}};E(),T();let D=setInterval(()=>{E(),T()},2e3);return{close(){y=!0,_&&clearTimeout(_),clearInterval(D),g?.close()}}}export{f as watchWorkflowRuns};
2
+ //# sourceMappingURL=workflowWatcher.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowWatcher.mjs","names":[],"sources":["../../src/adapters/workflowWatcher.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport {\n completeWorkflowRunView,\n foldWorkflowProgress,\n type ParsedWorkflowRun,\n parseWorkflowProgress,\n parseWorkflowRunRecord,\n PROGRESS_FILE_NAME,\n resolveWorkflowHome,\n RUN_RECORD_FILE_NAME,\n WORKFLOW_HOME_ENV,\n WORKFLOW_STAGES,\n WORKSPACES_DIR_NAME,\n} from '../services/workflowRuns.ts';\nimport type { WorkflowJobView } from '../types/webWorkflows.ts';\n\nconst POLL_MS = 2000;\nconst DEBOUNCE_MS = 80;\n\nexport interface WorkflowRunsSource {\n close(): void;\n}\n\nexport interface WatchWorkflowRunsOptions {\n /** Registry home override, the test seam; defaults to the engine's own resolution. */\n homeDir?: string;\n}\n\ninterface CachedParse<T> {\n size: number;\n mtimeMs: number;\n value: T;\n}\n\n/**\n * Watches workflow-mcp's registry, one watcher for the whole hub: the\n * registry is global, per-session scoping happens where the summaries live.\n *\n * Same reliability posture as the other watchers: the poll is the source of\n * truth, fs.watch is only an accelerator, and a missing registry home (no\n * workflow ever ran) is a normal state. Files are re-parsed only when their\n * size or mtime moved, because a scan touches every run the registry holds.\n * Emits the parsed run set whenever it changes, including the transition to\n * empty.\n */\nexport function watchWorkflowRuns(\n onRuns: (runs: ParsedWorkflowRun[]) => void,\n options: WatchWorkflowRunsOptions = {},\n): WorkflowRunsSource {\n const homeDir =\n options.homeDir ?? resolveWorkflowHome({ envValue: process.env[WORKFLOW_HOME_ENV], homeDir: os.homedir() });\n const workspacesDir = path.join(homeDir, WORKSPACES_DIR_NAME);\n\n let watcher: fs.FSWatcher | undefined;\n let debounce: NodeJS.Timeout | undefined;\n // Seeded with the empty fingerprint: a machine with no runs should not\n // announce an empty registry, only a registry that changed.\n let lastEmitted: string | undefined = '[]';\n let closed = false;\n const recordCache = new Map<string, CachedParse<ParsedWorkflowRun | undefined>>();\n const progressCache = new Map<string, CachedParse<WorkflowJobView[]>>();\n\n function cachedParse<T>(\n cache: Map<string, CachedParse<T>>,\n filePath: string,\n parse: (raw: string) => T,\n empty: T,\n ): T {\n let stat: fs.Stats;\n try {\n stat = fs.statSync(filePath);\n } catch {\n cache.delete(filePath);\n return empty; // Absent is a normal state: a run may have no progress yet.\n }\n const cached = cache.get(filePath);\n if (cached && cached.size === stat.size && cached.mtimeMs === stat.mtimeMs) return cached.value;\n let raw: string;\n try {\n raw = fs.readFileSync(filePath, 'utf8');\n } catch {\n cache.delete(filePath);\n return empty; // Mid-move or mid-write; the next pass settles it.\n }\n const value = parse(raw);\n cache.set(filePath, { size: stat.size, mtimeMs: stat.mtimeMs, value });\n return value;\n }\n\n const listDir = (dir: string): string[] => {\n try {\n return fs.readdirSync(dir).sort();\n } catch {\n return []; // The directory appears with the first run.\n }\n };\n\n const scan = (): ParsedWorkflowRun[] => {\n const runs: ParsedWorkflowRun[] = [];\n const seenPaths = new Set<string>();\n for (const workspace of listDir(workspacesDir)) {\n for (const stage of WORKFLOW_STAGES) {\n const stageDir = path.join(workspacesDir, workspace, stage);\n for (const runKey of listDir(stageDir)) {\n const runDir = path.join(stageDir, runKey);\n const recordPath = path.join(runDir, RUN_RECORD_FILE_NAME);\n const progressPath = path.join(runDir, PROGRESS_FILE_NAME);\n seenPaths.add(recordPath);\n seenPaths.add(progressPath);\n const parsed = cachedParse(recordCache, recordPath, parseWorkflowRunRecord, undefined);\n if (parsed === undefined) continue;\n const jobs = cachedParse(\n progressCache,\n progressPath,\n (raw) => foldWorkflowProgress(parseWorkflowProgress(raw)),\n [],\n );\n runs.push({ ...parsed, view: completeWorkflowRunView(parsed.view, jobs) });\n }\n }\n }\n for (const key of recordCache.keys()) if (!seenPaths.has(key)) recordCache.delete(key);\n for (const key of progressCache.keys()) if (!seenPaths.has(key)) progressCache.delete(key);\n return runs;\n };\n\n const emit = (): void => {\n if (closed) return;\n const runs = scan();\n const fingerprint = JSON.stringify(runs);\n if (fingerprint === lastEmitted) return;\n lastEmitted = fingerprint;\n onRuns(runs);\n };\n\n const ensureWatcher = (): void => {\n if (watcher || closed) return;\n try {\n watcher = fs.watch(workspacesDir, { recursive: true }, () => {\n if (debounce) clearTimeout(debounce);\n debounce = setTimeout(emit, DEBOUNCE_MS);\n });\n watcher.on('error', () => {\n watcher?.close();\n watcher = undefined; // The poll re-establishes it once the dir is back.\n });\n } catch {\n // The registry does not exist yet; the poll keeps trying.\n }\n };\n\n ensureWatcher();\n emit();\n const poll = setInterval(() => {\n ensureWatcher();\n emit();\n }, POLL_MS);\n\n return {\n close() {\n closed = true;\n if (debounce) clearTimeout(debounce);\n clearInterval(poll);\n watcher?.close();\n },\n };\n}\n"],"mappings":"wVA+CA,SAAgB,EACd,EACA,EAAoC,CAAC,EACjB,CACpB,IAAM,EACJ,EAAQ,SAAW,EAAoB,CAAE,SAAU,QAAQ,IAAA,kBAAwB,QAAS,EAAG,QAAQ,CAAE,CAAC,EACtG,EAAgB,EAAK,KAAK,EAAS,CAAmB,EAExD,EACA,EAGA,EAAkC,KAClC,EAAS,GACP,EAAc,IAAI,IAClB,EAAgB,IAAI,IAE1B,SAAS,EACP,EACA,EACA,EACA,EACG,CACH,IAAI,EACJ,GAAI,CACF,EAAO,EAAG,SAAS,CAAQ,CAC7B,MAAQ,CAEN,OADA,EAAM,OAAO,CAAQ,EACd,CACT,CACA,IAAM,EAAS,EAAM,IAAI,CAAQ,EACjC,GAAI,GAAU,EAAO,OAAS,EAAK,MAAQ,EAAO,UAAY,EAAK,QAAS,OAAO,EAAO,MAC1F,IAAI,EACJ,GAAI,CACF,EAAM,EAAG,aAAa,EAAU,MAAM,CACxC,MAAQ,CAEN,OADA,EAAM,OAAO,CAAQ,EACd,CACT,CACA,IAAM,EAAQ,EAAM,CAAG,EAEvB,OADA,EAAM,IAAI,EAAU,CAAE,KAAM,EAAK,KAAM,QAAS,EAAK,QAAS,OAAM,CAAC,EAC9D,CACT,CAEA,IAAM,EAAW,GAA0B,CACzC,GAAI,CACF,OAAO,EAAG,YAAY,CAAG,CAAC,CAAC,KAAK,CAClC,MAAQ,CACN,MAAO,CAAC,CACV,CACF,EAEM,MAAkC,CACtC,IAAM,EAA4B,CAAC,EAC7B,EAAY,IAAI,IACtB,IAAK,IAAM,KAAa,EAAQ,CAAa,EAC3C,IAAK,IAAM,KAAS,EAAiB,CACnC,IAAM,EAAW,EAAK,KAAK,EAAe,EAAW,CAAK,EAC1D,IAAK,IAAM,KAAU,EAAQ,CAAQ,EAAG,CACtC,IAAM,EAAS,EAAK,KAAK,EAAU,CAAM,EACnC,EAAa,EAAK,KAAK,EAAQ,CAAoB,EACnD,EAAe,EAAK,KAAK,EAAQ,CAAkB,EACzD,EAAU,IAAI,CAAU,EACxB,EAAU,IAAI,CAAY,EAC1B,IAAM,EAAS,EAAY,EAAa,EAAY,EAAwB,IAAA,EAAS,EACrF,GAAI,IAAW,IAAA,GAAW,SAC1B,IAAM,EAAO,EACX,EACA,EACC,GAAQ,EAAqB,EAAsB,CAAG,CAAC,EACxD,CAAC,CACH,EACA,EAAK,KAAK,CAAE,GAAG,EAAQ,KAAM,EAAwB,EAAO,KAAM,CAAI,CAAE,CAAC,CAC3E,CACF,CAEF,IAAK,IAAM,KAAO,EAAY,KAAK,EAAQ,EAAU,IAAI,CAAG,GAAG,EAAY,OAAO,CAAG,EACrF,IAAK,IAAM,KAAO,EAAc,KAAK,EAAQ,EAAU,IAAI,CAAG,GAAG,EAAc,OAAO,CAAG,EACzF,OAAO,CACT,EAEM,MAAmB,CACvB,GAAI,EAAQ,OACZ,IAAM,EAAO,EAAK,EACZ,EAAc,KAAK,UAAU,CAAI,EACnC,IAAgB,IACpB,EAAc,EACd,EAAO,CAAI,EACb,EAEM,MAA4B,CAC5B,QAAW,GACf,GAAI,CACF,EAAU,EAAG,MAAM,EAAe,CAAE,UAAW,EAAK,MAAS,CACvD,GAAU,aAAa,CAAQ,EACnC,EAAW,WAAW,EAAM,EAAW,CACzC,CAAC,EACD,EAAQ,GAAG,YAAe,CACxB,GAAS,MAAM,EACf,EAAU,IAAA,EACZ,CAAC,CACH,MAAQ,CAER,CACF,EAEA,EAAc,EACd,EAAK,EACL,IAAM,EAAO,gBAAkB,CAC7B,EAAc,EACd,EAAK,CACP,EAAG,GAAO,EAEV,MAAO,CACL,OAAQ,CACN,EAAS,GACL,GAAU,aAAa,CAAQ,EACnC,cAAc,CAAI,EAClB,GAAS,MAAM,CACjB,CACF,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=require("../types/webWorkflows.cjs"),t=require("./workflowCatalogChannel.cjs"),n=require("../services/workflowRuns.cjs"),r=require("./workflowWatcher.cjs");function i(t={}){return{frameType:e.WORKFLOW_RUNS_TYPE,start(e){let i=[],a=new Map,o=e=>n.presentWorkflowRuns(i.filter(t=>n.runBelongsToSession(t,{sessionId:e.sessionId,cwd:e.cwd})).map(e=>e.view),Date.now()),s=(t.watch??r.watchWorkflowRuns)(t=>{i=t;for(let t of e.sessions()){let n=o(t),r=JSON.stringify(n);r!==a.get(t.sessionId)&&(a.set(t.sessionId,r),e.publish(t.sessionId,{runs:n}))}});return{payloadFor(e){return{runs:o(e)}},sessionRemoved(e){a.delete(e)},close(){s.close()}}}}}const a=[i(),t.createWorkflowCatalogChannel()];exports.createWorkflowsChannel=i,exports.webHubChannels=a;
2
+ //# sourceMappingURL=workflowsHubChannel.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowsHubChannel.cjs","names":["WORKFLOW_RUNS_TYPE","presentWorkflowRuns","runBelongsToSession","watchWorkflowRuns","createWorkflowCatalogChannel"],"sources":["../../src/adapters/workflowsHubChannel.ts"],"sourcesContent":["import type { HubChannelSource, HubSessionScope, WebHubChannel } from '@agimon-ai/doompi-web-contracts';\nimport { type ParsedWorkflowRun, presentWorkflowRuns, runBelongsToSession } from '../services/workflowRuns.ts';\nimport { WORKFLOW_RUNS_TYPE, type WorkflowRunView } from '../types/webWorkflows.ts';\nimport { createWorkflowCatalogChannel } from './workflowCatalogChannel.ts';\nimport { watchWorkflowRuns } from './workflowWatcher.ts';\n\nexport interface WorkflowsChannelOptions {\n /** Injectable for tests; defaults to watching workflow-mcp's registry. */\n watch?: typeof watchWorkflowRuns;\n}\n\n/**\n * The workflows data channel: one hub-wide registry watcher, filtered and\n * presented per session on demand, published as { runs } payloads under the\n * 'workflow_runs' frame type. Live announcements are deduped per session by\n * JSON fingerprint; the subscribe-time snapshot is recomputed so retention\n * keeps moving between registry changes.\n */\nexport function createWorkflowsChannel(options: WorkflowsChannelOptions = {}): WebHubChannel {\n return {\n frameType: WORKFLOW_RUNS_TYPE,\n start(host) {\n let parsed: ParsedWorkflowRun[] = [];\n const lastPublished = new Map<string, string>();\n const runsFor = (scope: HubSessionScope): WorkflowRunView[] =>\n presentWorkflowRuns(\n parsed\n .filter((run) => runBelongsToSession(run, { sessionId: scope.sessionId, cwd: scope.cwd }))\n .map((run) => run.view),\n Date.now(),\n );\n const watcher = (options.watch ?? watchWorkflowRuns)((runs) => {\n parsed = runs;\n for (const scope of host.sessions()) {\n const view = runsFor(scope);\n const json = JSON.stringify(view);\n if (json === lastPublished.get(scope.sessionId)) continue;\n lastPublished.set(scope.sessionId, json);\n host.publish(scope.sessionId, { runs: view });\n }\n });\n const channelSource: HubChannelSource = {\n payloadFor(scope) {\n return { runs: runsFor(scope) };\n },\n sessionRemoved(sessionId) {\n lastPublished.delete(sessionId);\n },\n close() {\n watcher.close();\n },\n };\n return channelSource;\n },\n };\n}\n\n/** The named export the generated hub registry imports. */\nexport const webHubChannels: readonly WebHubChannel[] = [createWorkflowsChannel(), createWorkflowCatalogChannel()];\n"],"mappings":"oKAkBA,SAAgB,EAAuB,EAAmC,CAAC,EAAkB,CAC3F,MAAO,CACL,UAAWA,EAAAA,mBACX,MAAM,EAAM,CACV,IAAI,EAA8B,CAAC,EAC7B,EAAgB,IAAI,IACpB,EAAW,GACfC,EAAAA,oBACE,EACG,OAAQ,GAAQC,EAAAA,oBAAoB,EAAK,CAAE,UAAW,EAAM,UAAW,IAAK,EAAM,GAAI,CAAC,CAAC,CAAC,CACzF,IAAK,GAAQ,EAAI,IAAI,EACxB,KAAK,IAAI,CACX,EACI,GAAW,EAAQ,OAASC,EAAAA,kBAAAA,CAAoB,GAAS,CAC7D,EAAS,EACT,IAAK,IAAM,KAAS,EAAK,SAAS,EAAG,CACnC,IAAM,EAAO,EAAQ,CAAK,EACpB,EAAO,KAAK,UAAU,CAAI,EAC5B,IAAS,EAAc,IAAI,EAAM,SAAS,IAC9C,EAAc,IAAI,EAAM,UAAW,CAAI,EACvC,EAAK,QAAQ,EAAM,UAAW,CAAE,KAAM,CAAK,CAAC,EAC9C,CACF,CAAC,EAYD,MAAO,CAVL,WAAW,EAAO,CAChB,MAAO,CAAE,KAAM,EAAQ,CAAK,CAAE,CAChC,EACA,eAAe,EAAW,CACxB,EAAc,OAAO,CAAS,CAChC,EACA,OAAQ,CACN,EAAQ,MAAM,CAChB,CAEiB,CACrB,CACF,CACF,CAGA,MAAa,EAA2C,CAAC,EAAuB,EAAGC,EAAAA,6BAA6B,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { watchWorkflowRuns } from "./workflowWatcher.cjs";
2
+ import { WebHubChannel } from "@agimon-ai/doompi-web-contracts";
3
+ //#region src/adapters/workflowsHubChannel.d.ts
4
+ interface WorkflowsChannelOptions {
5
+ /** Injectable for tests; defaults to watching workflow-mcp's registry. */
6
+ watch?: typeof watchWorkflowRuns;
7
+ }
8
+ /**
9
+ * The workflows data channel: one hub-wide registry watcher, filtered and
10
+ * presented per session on demand, published as { runs } payloads under the
11
+ * 'workflow_runs' frame type. Live announcements are deduped per session by
12
+ * JSON fingerprint; the subscribe-time snapshot is recomputed so retention
13
+ * keeps moving between registry changes.
14
+ */
15
+ declare function createWorkflowsChannel(options?: WorkflowsChannelOptions): WebHubChannel;
16
+ /** The named export the generated hub registry imports. */
17
+ declare const webHubChannels: readonly WebHubChannel[];
18
+ //#endregion
19
+ export { WorkflowsChannelOptions, createWorkflowsChannel, webHubChannels };
20
+ //# sourceMappingURL=workflowsHubChannel.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowsHubChannel.d.cts","names":[],"sources":["../../src/adapters/workflowsHubChannel.ts"],"mappings":";;;UAMiB;;EAEf,eAAe;;;;;;;;;iBAUD,uBAAuB,UAAS,0BAA+B;;cAwClE,yBAAyB"}
@@ -0,0 +1,20 @@
1
+ import { watchWorkflowRuns } from "./workflowWatcher.mjs";
2
+ import { WebHubChannel } from "@agimon-ai/doompi-web-contracts";
3
+ //#region src/adapters/workflowsHubChannel.d.ts
4
+ interface WorkflowsChannelOptions {
5
+ /** Injectable for tests; defaults to watching workflow-mcp's registry. */
6
+ watch?: typeof watchWorkflowRuns;
7
+ }
8
+ /**
9
+ * The workflows data channel: one hub-wide registry watcher, filtered and
10
+ * presented per session on demand, published as { runs } payloads under the
11
+ * 'workflow_runs' frame type. Live announcements are deduped per session by
12
+ * JSON fingerprint; the subscribe-time snapshot is recomputed so retention
13
+ * keeps moving between registry changes.
14
+ */
15
+ declare function createWorkflowsChannel(options?: WorkflowsChannelOptions): WebHubChannel;
16
+ /** The named export the generated hub registry imports. */
17
+ declare const webHubChannels: readonly WebHubChannel[];
18
+ //#endregion
19
+ export { WorkflowsChannelOptions, createWorkflowsChannel, webHubChannels };
20
+ //# sourceMappingURL=workflowsHubChannel.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowsHubChannel.d.mts","names":[],"sources":["../../src/adapters/workflowsHubChannel.ts"],"mappings":";;;UAMiB;;EAEf,eAAe;;;;;;;;;iBAUD,uBAAuB,UAAS,0BAA+B;;cAwClE,yBAAyB"}
@@ -0,0 +1,2 @@
1
+ import{WORKFLOW_RUNS_TYPE as e}from"../types/webWorkflows.mjs";import{createWorkflowCatalogChannel as t}from"./workflowCatalogChannel.mjs";import{presentWorkflowRuns as n,runBelongsToSession as r}from"../services/workflowRuns.mjs";import{watchWorkflowRuns as i}from"./workflowWatcher.mjs";function a(t={}){return{frameType:e,start(e){let a=[],o=new Map,s=e=>n(a.filter(t=>r(t,{sessionId:e.sessionId,cwd:e.cwd})).map(e=>e.view),Date.now()),c=(t.watch??i)(t=>{a=t;for(let t of e.sessions()){let n=s(t),r=JSON.stringify(n);r!==o.get(t.sessionId)&&(o.set(t.sessionId,r),e.publish(t.sessionId,{runs:n}))}});return{payloadFor(e){return{runs:s(e)}},sessionRemoved(e){o.delete(e)},close(){c.close()}}}}}const o=[a(),t()];export{a as createWorkflowsChannel,o as webHubChannels};
2
+ //# sourceMappingURL=workflowsHubChannel.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflowsHubChannel.mjs","names":[],"sources":["../../src/adapters/workflowsHubChannel.ts"],"sourcesContent":["import type { HubChannelSource, HubSessionScope, WebHubChannel } from '@agimon-ai/doompi-web-contracts';\nimport { type ParsedWorkflowRun, presentWorkflowRuns, runBelongsToSession } from '../services/workflowRuns.ts';\nimport { WORKFLOW_RUNS_TYPE, type WorkflowRunView } from '../types/webWorkflows.ts';\nimport { createWorkflowCatalogChannel } from './workflowCatalogChannel.ts';\nimport { watchWorkflowRuns } from './workflowWatcher.ts';\n\nexport interface WorkflowsChannelOptions {\n /** Injectable for tests; defaults to watching workflow-mcp's registry. */\n watch?: typeof watchWorkflowRuns;\n}\n\n/**\n * The workflows data channel: one hub-wide registry watcher, filtered and\n * presented per session on demand, published as { runs } payloads under the\n * 'workflow_runs' frame type. Live announcements are deduped per session by\n * JSON fingerprint; the subscribe-time snapshot is recomputed so retention\n * keeps moving between registry changes.\n */\nexport function createWorkflowsChannel(options: WorkflowsChannelOptions = {}): WebHubChannel {\n return {\n frameType: WORKFLOW_RUNS_TYPE,\n start(host) {\n let parsed: ParsedWorkflowRun[] = [];\n const lastPublished = new Map<string, string>();\n const runsFor = (scope: HubSessionScope): WorkflowRunView[] =>\n presentWorkflowRuns(\n parsed\n .filter((run) => runBelongsToSession(run, { sessionId: scope.sessionId, cwd: scope.cwd }))\n .map((run) => run.view),\n Date.now(),\n );\n const watcher = (options.watch ?? watchWorkflowRuns)((runs) => {\n parsed = runs;\n for (const scope of host.sessions()) {\n const view = runsFor(scope);\n const json = JSON.stringify(view);\n if (json === lastPublished.get(scope.sessionId)) continue;\n lastPublished.set(scope.sessionId, json);\n host.publish(scope.sessionId, { runs: view });\n }\n });\n const channelSource: HubChannelSource = {\n payloadFor(scope) {\n return { runs: runsFor(scope) };\n },\n sessionRemoved(sessionId) {\n lastPublished.delete(sessionId);\n },\n close() {\n watcher.close();\n },\n };\n return channelSource;\n },\n };\n}\n\n/** The named export the generated hub registry imports. */\nexport const webHubChannels: readonly WebHubChannel[] = [createWorkflowsChannel(), createWorkflowCatalogChannel()];\n"],"mappings":"iSAkBA,SAAgB,EAAuB,EAAmC,CAAC,EAAkB,CAC3F,MAAO,CACL,UAAW,EACX,MAAM,EAAM,CACV,IAAI,EAA8B,CAAC,EAC7B,EAAgB,IAAI,IACpB,EAAW,GACf,EACE,EACG,OAAQ,GAAQ,EAAoB,EAAK,CAAE,UAAW,EAAM,UAAW,IAAK,EAAM,GAAI,CAAC,CAAC,CAAC,CACzF,IAAK,GAAQ,EAAI,IAAI,EACxB,KAAK,IAAI,CACX,EACI,GAAW,EAAQ,OAAS,EAAA,CAAoB,GAAS,CAC7D,EAAS,EACT,IAAK,IAAM,KAAS,EAAK,SAAS,EAAG,CACnC,IAAM,EAAO,EAAQ,CAAK,EACpB,EAAO,KAAK,UAAU,CAAI,EAC5B,IAAS,EAAc,IAAI,EAAM,SAAS,IAC9C,EAAc,IAAI,EAAM,UAAW,CAAI,EACvC,EAAK,QAAQ,EAAM,UAAW,CAAE,KAAM,CAAK,CAAC,EAC9C,CACF,CAAC,EAYD,MAAO,CAVL,WAAW,EAAO,CAChB,MAAO,CAAE,KAAM,EAAQ,CAAK,CAAE,CAChC,EACA,eAAe,EAAW,CACxB,EAAc,OAAO,CAAS,CAChC,EACA,OAAQ,CACN,EAAQ,MAAM,CAChB,CAEiB,CACrB,CACF,CACF,CAGA,MAAa,EAA2C,CAAC,EAAuB,EAAG,EAA6B,CAAC"}
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./adapters/workflowHubApi.cjs");exports.api=e.api,exports.createWorkflowHubApi=e.createWorkflowHubApi;
@@ -0,0 +1,2 @@
1
+ import { WorkflowHubApiOptions, api, createWorkflowHubApi } from "./adapters/workflowHubApi.cjs";
2
+ export { type WorkflowHubApiOptions, api, createWorkflowHubApi };
@@ -0,0 +1,2 @@
1
+ import { WorkflowHubApiOptions, api, createWorkflowHubApi } from "./adapters/workflowHubApi.mjs";
2
+ export { type WorkflowHubApiOptions, api, createWorkflowHubApi };
@@ -0,0 +1 @@
1
+ import{api as e,createWorkflowHubApi as t}from"./adapters/workflowHubApi.mjs";export{e as api,t as createWorkflowHubApi};
@@ -1,2 +1,2 @@
1
- let e=require("zod");const t={runKey:e.z.string().min(1).describe(`Run key of the workflow run.`),workspace:e.z.string().optional().describe(`Workspace the run belongs to.`)},n=e.z.discriminatedUnion(`action`,[e.z.object({action:e.z.literal(`status`),...t}).strict(),e.z.object({action:e.z.literal(`tail`),...t}).strict(),e.z.object({action:e.z.literal(`recovery-evidence`),...t}).strict(),e.z.object({action:e.z.literal(`follow`),...t}).strict(),e.z.object({action:e.z.literal(`open`),...t}).strict(),e.z.object({action:e.z.literal(`pause`),...t,reason:e.z.string().optional()}).strict(),e.z.object({action:e.z.literal(`resume`),...t,reason:e.z.string().optional()}).strict(),e.z.object({action:e.z.literal(`stop`),...t,reason:e.z.string().optional()}).strict(),e.z.object({action:e.z.literal(`recover`),...t,dryRun:e.z.boolean().optional(),job:e.z.string().optional(),runner:e.z.string().optional()}).strict()]),r=e.z.object({description:e.z.string().optional().describe(`Description shown for the workflow input.`),required:e.z.boolean().optional().describe(`Whether the workflow input must be provided.`),default:e.z.string().optional().describe(`Default value for the workflow input.`),type:e.z.string().optional().describe(`Declared workflow input type.`),options:e.z.array(e.z.string()).optional().describe(`Allowed values for the workflow input.`)}).passthrough(),i=e.z.object({inputs:e.z.record(e.z.string(),r).optional().describe(`Workflow dispatch input definitions.`)}).passthrough(),a=e.z.object({directory:e.z.string().min(1).describe(`Directory searched for workflow definitions.`),hasNextPage:e.z.boolean().describe(`Whether another catalog page exists.`),page:e.z.number().int().positive().describe(`One-based catalog page number.`),pageSize:e.z.number().int().positive().describe(`Catalog page size.`),workflows:e.z.array(e.z.object({path:e.z.string().min(1).describe(`Workflow path relative to the catalog directory.`),name:e.z.string().describe(`Workflow display name.`),description:e.z.string().describe(`Workflow description.`),tags:e.z.array(e.z.string()).describe(`Workflow tags.`)}).passthrough()).describe(`Workflow definitions on this page.`)}).passthrough(),o=e.z.object({id:e.z.number().int().nonnegative(),foreground:e.z.string().optional(),background:e.z.string().optional(),foreground_source:e.z.string().optional(),background_source:e.z.string().optional(),bold:e.z.boolean().optional(),faint:e.z.boolean().optional(),italic:e.z.boolean().optional(),underline:e.z.boolean().optional(),inverse:e.z.boolean().optional(),strikethrough:e.z.boolean().optional()}).loose(),s=e.z.object({row:e.z.number().int().nonnegative(),column:e.z.number().int().nonnegative(),text:e.z.string(),style_id:e.z.number().int().nonnegative().optional(),cell_width:e.z.number().int().nonnegative().optional()}).loose(),c=e.z.object({styles:e.z.array(o).optional(),row_spans:e.z.array(s).optional(),scrollback_spans:e.z.array(s).optional()}).loose(),l=e.z.object({render_grid:c}).loose();exports.dispatchInputSchema=r,exports.renderGridSchema=c,exports.renderGridSpanSchema=s,exports.renderGridStyleSchema=o,exports.terminalReplaySchema=l,exports.workflowCatalogPageSchema=a,exports.workflowDispatchInputsSchema=i,exports.workflowRunInputSchema=n;
1
+ let e=require("zod");const t={runKey:e.z.string().min(1).describe(`Run key of the workflow run.`),workspace:e.z.string().optional().describe(`Workspace the run belongs to.`)},n=e.z.discriminatedUnion(`action`,[e.z.object({action:e.z.literal(`status`),...t}).strict(),e.z.object({action:e.z.literal(`tail`),...t}).strict(),e.z.object({action:e.z.literal(`recovery-evidence`),...t}).strict(),e.z.object({action:e.z.literal(`follow`),...t}).strict(),e.z.object({action:e.z.literal(`open`),...t}).strict(),e.z.object({action:e.z.literal(`pause`),...t,reason:e.z.string().optional()}).strict(),e.z.object({action:e.z.literal(`resume`),...t,reason:e.z.string().optional()}).strict(),e.z.object({action:e.z.literal(`stop`),...t,reason:e.z.string().optional()}).strict(),e.z.object({action:e.z.literal(`recover`),...t,dryRun:e.z.boolean().optional(),job:e.z.string().optional(),runner:e.z.string().optional()}).strict()]),r=e.z.object({description:e.z.string().optional().describe(`Description shown for the workflow input.`),required:e.z.boolean().optional().describe(`Whether the workflow input must be provided.`),default:e.z.string().optional().describe(`Default value for the workflow input.`),type:e.z.string().optional().describe(`Declared workflow input type.`),options:e.z.array(e.z.string()).optional().describe(`Allowed values for the workflow input.`)}).passthrough(),i=e.z.object({inputs:e.z.record(e.z.string(),r).optional().describe(`Workflow dispatch input definitions.`)}).passthrough(),a=e.z.object({directory:e.z.string().min(1).describe(`Directory searched for workflow definitions.`),hasNextPage:e.z.boolean().describe(`Whether another catalog page exists.`),page:e.z.number().int().positive().describe(`One-based catalog page number.`),pageSize:e.z.number().int().positive().describe(`Catalog page size.`),workflows:e.z.array(e.z.object({path:e.z.string().min(1).describe(`Workflow path relative to the catalog directory.`),name:e.z.string().describe(`Workflow display name.`),description:e.z.string().describe(`Workflow description.`),tags:e.z.array(e.z.string()).describe(`Workflow tags.`)}).passthrough()).describe(`Workflow definitions on this page.`)}).passthrough();exports.dispatchInputSchema=r,exports.workflowCatalogPageSchema=a,exports.workflowDispatchInputsSchema=i,exports.workflowRunInputSchema=n;
2
2
  //# sourceMappingURL=workflowPi.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"workflowPi.cjs","names":["z"],"sources":["../../src/schemas/workflowPi.ts"],"sourcesContent":["import { z } from 'zod';\n\nconst RUN_SELECTOR_FIELDS = {\n runKey: z.string().min(1).describe('Run key of the workflow run.'),\n workspace: z.string().optional().describe('Workspace the run belongs to.'),\n} as const;\n\nexport const workflowRunInputSchema = z.discriminatedUnion('action', [\n z.object({ action: z.literal('status'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('tail'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('recovery-evidence'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('follow'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('open'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('pause'), ...RUN_SELECTOR_FIELDS, reason: z.string().optional() }).strict(),\n z.object({ action: z.literal('resume'), ...RUN_SELECTOR_FIELDS, reason: z.string().optional() }).strict(),\n z.object({ action: z.literal('stop'), ...RUN_SELECTOR_FIELDS, reason: z.string().optional() }).strict(),\n z\n .object({\n action: z.literal('recover'),\n ...RUN_SELECTOR_FIELDS,\n dryRun: z.boolean().optional(),\n job: z.string().optional(),\n runner: z.string().optional(),\n })\n .strict(),\n]);\n\nexport type WorkflowRunInput = z.infer<typeof workflowRunInputSchema>;\n\nexport const dispatchInputSchema = z\n .object({\n description: z.string().optional().describe('Description shown for the workflow input.'),\n required: z.boolean().optional().describe('Whether the workflow input must be provided.'),\n default: z.string().optional().describe('Default value for the workflow input.'),\n type: z.string().optional().describe('Declared workflow input type.'),\n options: z.array(z.string()).optional().describe('Allowed values for the workflow input.'),\n })\n .passthrough();\n\nexport type DispatchInput = z.infer<typeof dispatchInputSchema>;\n\nexport const workflowDispatchInputsSchema = z\n .object({\n inputs: z.record(z.string(), dispatchInputSchema).optional().describe('Workflow dispatch input definitions.'),\n })\n .passthrough();\n\nexport const workflowCatalogPageSchema = z\n .object({\n directory: z.string().min(1).describe('Directory searched for workflow definitions.'),\n hasNextPage: z.boolean().describe('Whether another catalog page exists.'),\n page: z.number().int().positive().describe('One-based catalog page number.'),\n pageSize: z.number().int().positive().describe('Catalog page size.'),\n workflows: z\n .array(\n z\n .object({\n path: z.string().min(1).describe('Workflow path relative to the catalog directory.'),\n name: z.string().describe('Workflow display name.'),\n description: z.string().describe('Workflow description.'),\n tags: z.array(z.string()).describe('Workflow tags.'),\n })\n .passthrough(),\n )\n .describe('Workflow definitions on this page.'),\n })\n .passthrough();\n\n/**\n * A cmux `terminal.replay` render grid, as `cmux.render-grid.v1`.\n *\n * Only the fields the run panel paints are named; the payload carries cursor,\n * modes, and theme data this package has no use for, so the object is loose\n * rather than strict. `read-screen` and `capture-pane` return plain text by\n * design, so this RPC is the only cmux surface that can answer in colour.\n */\nexport const renderGridStyleSchema = z\n .object({\n id: z.number().int().nonnegative(),\n foreground: z.string().optional(),\n background: z.string().optional(),\n /** `default` means the terminal's own colour, which the panel leaves to its theme. */\n foreground_source: z.string().optional(),\n background_source: z.string().optional(),\n bold: z.boolean().optional(),\n faint: z.boolean().optional(),\n italic: z.boolean().optional(),\n underline: z.boolean().optional(),\n inverse: z.boolean().optional(),\n strikethrough: z.boolean().optional(),\n })\n .loose();\n\nexport const renderGridSpanSchema = z\n .object({\n row: z.number().int().nonnegative(),\n column: z.number().int().nonnegative(),\n text: z.string(),\n style_id: z.number().int().nonnegative().optional(),\n /** Display columns the span occupies, which wide glyphs make larger than its length. */\n cell_width: z.number().int().nonnegative().optional(),\n })\n .loose();\n\nexport const renderGridSchema = z\n .object({\n styles: z.array(renderGridStyleSchema).optional(),\n row_spans: z.array(renderGridSpanSchema).optional(),\n scrollback_spans: z.array(renderGridSpanSchema).optional(),\n })\n .loose();\n\nexport const terminalReplaySchema = z.object({ render_grid: renderGridSchema }).loose();\n\nexport type RenderGridStyle = z.infer<typeof renderGridStyleSchema>;\nexport type RenderGridSpan = z.infer<typeof renderGridSpanSchema>;\nexport type RenderGrid = z.infer<typeof renderGridSchema>;\n"],"mappings":"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"}
1
+ {"version":3,"file":"workflowPi.cjs","names":["z"],"sources":["../../src/schemas/workflowPi.ts"],"sourcesContent":["import { z } from 'zod';\n\nconst RUN_SELECTOR_FIELDS = {\n runKey: z.string().min(1).describe('Run key of the workflow run.'),\n workspace: z.string().optional().describe('Workspace the run belongs to.'),\n} as const;\n\nexport const workflowRunInputSchema = z.discriminatedUnion('action', [\n z.object({ action: z.literal('status'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('tail'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('recovery-evidence'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('follow'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('open'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('pause'), ...RUN_SELECTOR_FIELDS, reason: z.string().optional() }).strict(),\n z.object({ action: z.literal('resume'), ...RUN_SELECTOR_FIELDS, reason: z.string().optional() }).strict(),\n z.object({ action: z.literal('stop'), ...RUN_SELECTOR_FIELDS, reason: z.string().optional() }).strict(),\n z\n .object({\n action: z.literal('recover'),\n ...RUN_SELECTOR_FIELDS,\n dryRun: z.boolean().optional(),\n job: z.string().optional(),\n runner: z.string().optional(),\n })\n .strict(),\n]);\n\nexport type WorkflowRunInput = z.infer<typeof workflowRunInputSchema>;\n\nexport const dispatchInputSchema = z\n .object({\n description: z.string().optional().describe('Description shown for the workflow input.'),\n required: z.boolean().optional().describe('Whether the workflow input must be provided.'),\n default: z.string().optional().describe('Default value for the workflow input.'),\n type: z.string().optional().describe('Declared workflow input type.'),\n options: z.array(z.string()).optional().describe('Allowed values for the workflow input.'),\n })\n .passthrough();\n\nexport type DispatchInput = z.infer<typeof dispatchInputSchema>;\n\nexport const workflowDispatchInputsSchema = z\n .object({\n inputs: z.record(z.string(), dispatchInputSchema).optional().describe('Workflow dispatch input definitions.'),\n })\n .passthrough();\n\nexport const workflowCatalogPageSchema = z\n .object({\n directory: z.string().min(1).describe('Directory searched for workflow definitions.'),\n hasNextPage: z.boolean().describe('Whether another catalog page exists.'),\n page: z.number().int().positive().describe('One-based catalog page number.'),\n pageSize: z.number().int().positive().describe('Catalog page size.'),\n workflows: z\n .array(\n z\n .object({\n path: z.string().min(1).describe('Workflow path relative to the catalog directory.'),\n name: z.string().describe('Workflow display name.'),\n description: z.string().describe('Workflow description.'),\n tags: z.array(z.string()).describe('Workflow tags.'),\n })\n .passthrough(),\n )\n .describe('Workflow definitions on this page.'),\n })\n .passthrough();\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{z as e}from"zod";const t={runKey:e.string().min(1).describe(`Run key of the workflow run.`),workspace:e.string().optional().describe(`Workspace the run belongs to.`)},n=e.discriminatedUnion(`action`,[e.object({action:e.literal(`status`),...t}).strict(),e.object({action:e.literal(`tail`),...t}).strict(),e.object({action:e.literal(`recovery-evidence`),...t}).strict(),e.object({action:e.literal(`follow`),...t}).strict(),e.object({action:e.literal(`open`),...t}).strict(),e.object({action:e.literal(`pause`),...t,reason:e.string().optional()}).strict(),e.object({action:e.literal(`resume`),...t,reason:e.string().optional()}).strict(),e.object({action:e.literal(`stop`),...t,reason:e.string().optional()}).strict(),e.object({action:e.literal(`recover`),...t,dryRun:e.boolean().optional(),job:e.string().optional(),runner:e.string().optional()}).strict()]),r=e.object({description:e.string().optional().describe(`Description shown for the workflow input.`),required:e.boolean().optional().describe(`Whether the workflow input must be provided.`),default:e.string().optional().describe(`Default value for the workflow input.`),type:e.string().optional().describe(`Declared workflow input type.`),options:e.array(e.string()).optional().describe(`Allowed values for the workflow input.`)}).passthrough(),i=e.object({inputs:e.record(e.string(),r).optional().describe(`Workflow dispatch input definitions.`)}).passthrough(),a=e.object({directory:e.string().min(1).describe(`Directory searched for workflow definitions.`),hasNextPage:e.boolean().describe(`Whether another catalog page exists.`),page:e.number().int().positive().describe(`One-based catalog page number.`),pageSize:e.number().int().positive().describe(`Catalog page size.`),workflows:e.array(e.object({path:e.string().min(1).describe(`Workflow path relative to the catalog directory.`),name:e.string().describe(`Workflow display name.`),description:e.string().describe(`Workflow description.`),tags:e.array(e.string()).describe(`Workflow tags.`)}).passthrough()).describe(`Workflow definitions on this page.`)}).passthrough(),o=e.object({id:e.number().int().nonnegative(),foreground:e.string().optional(),background:e.string().optional(),foreground_source:e.string().optional(),background_source:e.string().optional(),bold:e.boolean().optional(),faint:e.boolean().optional(),italic:e.boolean().optional(),underline:e.boolean().optional(),inverse:e.boolean().optional(),strikethrough:e.boolean().optional()}).loose(),s=e.object({row:e.number().int().nonnegative(),column:e.number().int().nonnegative(),text:e.string(),style_id:e.number().int().nonnegative().optional(),cell_width:e.number().int().nonnegative().optional()}).loose(),c=e.object({styles:e.array(o).optional(),row_spans:e.array(s).optional(),scrollback_spans:e.array(s).optional()}).loose(),l=e.object({render_grid:c}).loose();export{r as dispatchInputSchema,c as renderGridSchema,s as renderGridSpanSchema,o as renderGridStyleSchema,l as terminalReplaySchema,a as workflowCatalogPageSchema,i as workflowDispatchInputsSchema,n as workflowRunInputSchema};
1
+ import{z as e}from"zod";const t={runKey:e.string().min(1).describe(`Run key of the workflow run.`),workspace:e.string().optional().describe(`Workspace the run belongs to.`)},n=e.discriminatedUnion(`action`,[e.object({action:e.literal(`status`),...t}).strict(),e.object({action:e.literal(`tail`),...t}).strict(),e.object({action:e.literal(`recovery-evidence`),...t}).strict(),e.object({action:e.literal(`follow`),...t}).strict(),e.object({action:e.literal(`open`),...t}).strict(),e.object({action:e.literal(`pause`),...t,reason:e.string().optional()}).strict(),e.object({action:e.literal(`resume`),...t,reason:e.string().optional()}).strict(),e.object({action:e.literal(`stop`),...t,reason:e.string().optional()}).strict(),e.object({action:e.literal(`recover`),...t,dryRun:e.boolean().optional(),job:e.string().optional(),runner:e.string().optional()}).strict()]),r=e.object({description:e.string().optional().describe(`Description shown for the workflow input.`),required:e.boolean().optional().describe(`Whether the workflow input must be provided.`),default:e.string().optional().describe(`Default value for the workflow input.`),type:e.string().optional().describe(`Declared workflow input type.`),options:e.array(e.string()).optional().describe(`Allowed values for the workflow input.`)}).passthrough(),i=e.object({inputs:e.record(e.string(),r).optional().describe(`Workflow dispatch input definitions.`)}).passthrough(),a=e.object({directory:e.string().min(1).describe(`Directory searched for workflow definitions.`),hasNextPage:e.boolean().describe(`Whether another catalog page exists.`),page:e.number().int().positive().describe(`One-based catalog page number.`),pageSize:e.number().int().positive().describe(`Catalog page size.`),workflows:e.array(e.object({path:e.string().min(1).describe(`Workflow path relative to the catalog directory.`),name:e.string().describe(`Workflow display name.`),description:e.string().describe(`Workflow description.`),tags:e.array(e.string()).describe(`Workflow tags.`)}).passthrough()).describe(`Workflow definitions on this page.`)}).passthrough();export{r as dispatchInputSchema,a as workflowCatalogPageSchema,i as workflowDispatchInputsSchema,n as workflowRunInputSchema};
2
2
  //# sourceMappingURL=workflowPi.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"workflowPi.mjs","names":[],"sources":["../../src/schemas/workflowPi.ts"],"sourcesContent":["import { z } from 'zod';\n\nconst RUN_SELECTOR_FIELDS = {\n runKey: z.string().min(1).describe('Run key of the workflow run.'),\n workspace: z.string().optional().describe('Workspace the run belongs to.'),\n} as const;\n\nexport const workflowRunInputSchema = z.discriminatedUnion('action', [\n z.object({ action: z.literal('status'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('tail'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('recovery-evidence'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('follow'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('open'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('pause'), ...RUN_SELECTOR_FIELDS, reason: z.string().optional() }).strict(),\n z.object({ action: z.literal('resume'), ...RUN_SELECTOR_FIELDS, reason: z.string().optional() }).strict(),\n z.object({ action: z.literal('stop'), ...RUN_SELECTOR_FIELDS, reason: z.string().optional() }).strict(),\n z\n .object({\n action: z.literal('recover'),\n ...RUN_SELECTOR_FIELDS,\n dryRun: z.boolean().optional(),\n job: z.string().optional(),\n runner: z.string().optional(),\n })\n .strict(),\n]);\n\nexport type WorkflowRunInput = z.infer<typeof workflowRunInputSchema>;\n\nexport const dispatchInputSchema = z\n .object({\n description: z.string().optional().describe('Description shown for the workflow input.'),\n required: z.boolean().optional().describe('Whether the workflow input must be provided.'),\n default: z.string().optional().describe('Default value for the workflow input.'),\n type: z.string().optional().describe('Declared workflow input type.'),\n options: z.array(z.string()).optional().describe('Allowed values for the workflow input.'),\n })\n .passthrough();\n\nexport type DispatchInput = z.infer<typeof dispatchInputSchema>;\n\nexport const workflowDispatchInputsSchema = z\n .object({\n inputs: z.record(z.string(), dispatchInputSchema).optional().describe('Workflow dispatch input definitions.'),\n })\n .passthrough();\n\nexport const workflowCatalogPageSchema = z\n .object({\n directory: z.string().min(1).describe('Directory searched for workflow definitions.'),\n hasNextPage: z.boolean().describe('Whether another catalog page exists.'),\n page: z.number().int().positive().describe('One-based catalog page number.'),\n pageSize: z.number().int().positive().describe('Catalog page size.'),\n workflows: z\n .array(\n z\n .object({\n path: z.string().min(1).describe('Workflow path relative to the catalog directory.'),\n name: z.string().describe('Workflow display name.'),\n description: z.string().describe('Workflow description.'),\n tags: z.array(z.string()).describe('Workflow tags.'),\n })\n .passthrough(),\n )\n .describe('Workflow definitions on this page.'),\n })\n .passthrough();\n\n/**\n * A cmux `terminal.replay` render grid, as `cmux.render-grid.v1`.\n *\n * Only the fields the run panel paints are named; the payload carries cursor,\n * modes, and theme data this package has no use for, so the object is loose\n * rather than strict. `read-screen` and `capture-pane` return plain text by\n * design, so this RPC is the only cmux surface that can answer in colour.\n */\nexport const renderGridStyleSchema = z\n .object({\n id: z.number().int().nonnegative(),\n foreground: z.string().optional(),\n background: z.string().optional(),\n /** `default` means the terminal's own colour, which the panel leaves to its theme. */\n foreground_source: z.string().optional(),\n background_source: z.string().optional(),\n bold: z.boolean().optional(),\n faint: z.boolean().optional(),\n italic: z.boolean().optional(),\n underline: z.boolean().optional(),\n inverse: z.boolean().optional(),\n strikethrough: z.boolean().optional(),\n })\n .loose();\n\nexport const renderGridSpanSchema = z\n .object({\n row: z.number().int().nonnegative(),\n column: z.number().int().nonnegative(),\n text: z.string(),\n style_id: z.number().int().nonnegative().optional(),\n /** Display columns the span occupies, which wide glyphs make larger than its length. */\n cell_width: z.number().int().nonnegative().optional(),\n })\n .loose();\n\nexport const renderGridSchema = z\n .object({\n styles: z.array(renderGridStyleSchema).optional(),\n row_spans: z.array(renderGridSpanSchema).optional(),\n scrollback_spans: z.array(renderGridSpanSchema).optional(),\n })\n .loose();\n\nexport const terminalReplaySchema = z.object({ render_grid: renderGridSchema }).loose();\n\nexport type RenderGridStyle = z.infer<typeof renderGridStyleSchema>;\nexport type RenderGridSpan = z.infer<typeof renderGridSpanSchema>;\nexport type RenderGrid = z.infer<typeof renderGridSchema>;\n"],"mappings":"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"}
1
+ {"version":3,"file":"workflowPi.mjs","names":[],"sources":["../../src/schemas/workflowPi.ts"],"sourcesContent":["import { z } from 'zod';\n\nconst RUN_SELECTOR_FIELDS = {\n runKey: z.string().min(1).describe('Run key of the workflow run.'),\n workspace: z.string().optional().describe('Workspace the run belongs to.'),\n} as const;\n\nexport const workflowRunInputSchema = z.discriminatedUnion('action', [\n z.object({ action: z.literal('status'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('tail'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('recovery-evidence'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('follow'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('open'), ...RUN_SELECTOR_FIELDS }).strict(),\n z.object({ action: z.literal('pause'), ...RUN_SELECTOR_FIELDS, reason: z.string().optional() }).strict(),\n z.object({ action: z.literal('resume'), ...RUN_SELECTOR_FIELDS, reason: z.string().optional() }).strict(),\n z.object({ action: z.literal('stop'), ...RUN_SELECTOR_FIELDS, reason: z.string().optional() }).strict(),\n z\n .object({\n action: z.literal('recover'),\n ...RUN_SELECTOR_FIELDS,\n dryRun: z.boolean().optional(),\n job: z.string().optional(),\n runner: z.string().optional(),\n })\n .strict(),\n]);\n\nexport type WorkflowRunInput = z.infer<typeof workflowRunInputSchema>;\n\nexport const dispatchInputSchema = z\n .object({\n description: z.string().optional().describe('Description shown for the workflow input.'),\n required: z.boolean().optional().describe('Whether the workflow input must be provided.'),\n default: z.string().optional().describe('Default value for the workflow input.'),\n type: z.string().optional().describe('Declared workflow input type.'),\n options: z.array(z.string()).optional().describe('Allowed values for the workflow input.'),\n })\n .passthrough();\n\nexport type DispatchInput = z.infer<typeof dispatchInputSchema>;\n\nexport const workflowDispatchInputsSchema = z\n .object({\n inputs: z.record(z.string(), dispatchInputSchema).optional().describe('Workflow dispatch input definitions.'),\n })\n .passthrough();\n\nexport const workflowCatalogPageSchema = z\n .object({\n directory: z.string().min(1).describe('Directory searched for workflow definitions.'),\n hasNextPage: z.boolean().describe('Whether another catalog page exists.'),\n page: z.number().int().positive().describe('One-based catalog page number.'),\n pageSize: z.number().int().positive().describe('Catalog page size.'),\n workflows: z\n .array(\n z\n .object({\n path: z.string().min(1).describe('Workflow path relative to the catalog directory.'),\n name: z.string().describe('Workflow display name.'),\n description: z.string().describe('Workflow description.'),\n tags: z.array(z.string()).describe('Workflow tags.'),\n })\n .passthrough(),\n )\n .describe('Workflow definitions on this page.'),\n })\n .passthrough();\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ function e(e,t){return e!==void 0&&t!==void 0&&e.size===t.size&&e.modifiedAt===t.modifiedAt}function t(t){let n=new Map,r=r=>{let i=t.stamp(r),a=n.get(r);if(a&&e(a.stamp,i))return a.detail;let o=t.summarize(r);return i!==void 0&&n.set(r,{stamp:i,detail:o}),o};return{async read(e){return(await t.list(e)).map(e=>{let t=r(e.path);return{path:e.path,relativePath:e.relativePath,name:e.name===``?e.relativePath:e.name,description:e.description,tags:e.tags,triggers:t.triggers,inputs:t.inputs,jobs:t.jobs,artifacts:t.artifacts,...t.runners===void 0?{}:{runners:t.runners},...t.error===void 0?{}:{error:t.error}}})},forget(e){for(let t of n.keys())e.has(t)||n.delete(t)}}}function n(e){return[...e].sort((e,t)=>e.error===void 0==(t.error===void 0)?e.name.localeCompare(t.name):e.error===void 0?-1:1)}exports.createWorkflowCatalogReader=t,exports.presentWorkflowCatalog=n;
2
+ //# sourceMappingURL=webWorkflowCatalog.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webWorkflowCatalog.cjs","names":[],"sources":["../../src/services/webWorkflowCatalog.ts"],"sourcesContent":["import type {\n WorkflowCatalogArtifactView,\n WorkflowCatalogEntryView,\n WorkflowCatalogInputView,\n WorkflowCatalogJobView,\n} from '../types/webWorkflows.ts';\n\n/**\n * The cockpit's view of the workflows a session can launch.\n *\n * The engine already reads a repository's workflow files and parses one of\n * them into triggers, inputs, jobs, artifacts and compatible runners, so this\n * module only shapes that answer for the wire and remembers what it read.\n *\n * WHY A CACHE:\n * A session's catalog is recomputed on a slow tick and again on every\n * subscribe, and parsing every workflow file each time is wasted work on files\n * that change a few times a week. Entries are kept per path and reused while\n * the file's size and modification time are unchanged, which is the same test\n * a build tool makes and costs one stat per file.\n */\n\n/** What the engine reports for one workflow file before it is parsed. */\nexport interface CatalogListEntry {\n path: string;\n relativePath: string;\n name: string;\n description: string;\n tags: string[];\n}\n\n/** One file's parsed detail, or the reason it could not be read. */\nexport interface CatalogDetail {\n triggers: string[];\n inputs: WorkflowCatalogInputView[];\n jobs: WorkflowCatalogJobView[];\n artifacts: WorkflowCatalogArtifactView[];\n runners?: string[];\n error?: string;\n}\n\n/** What a file looked like when it was last parsed. */\nexport interface CatalogFileStamp {\n size: number;\n modifiedAt: number;\n}\n\nexport interface WorkflowCatalogReaderDeps {\n /** Every workflow file under a directory. */\n list(directory: string): Promise<CatalogListEntry[]>;\n /** One file's parsed detail; never throws, so a bad file cannot empty a board. */\n summarize(path: string): CatalogDetail;\n /** Size and modification time, or undefined when the file is gone. */\n stamp(path: string): CatalogFileStamp | undefined;\n}\n\ninterface CachedEntry {\n stamp: CatalogFileStamp;\n detail: CatalogDetail;\n}\n\n/** Reads one directory's catalog, reusing what it parsed last time. */\nexport interface WorkflowCatalogReader {\n read(directory: string): Promise<WorkflowCatalogEntryView[]>;\n /** Drops what was parsed for files no longer listed, so a cache cannot grow forever. */\n forget(keep: ReadonlySet<string>): void;\n}\n\nfunction unchanged(left: CatalogFileStamp | undefined, right: CatalogFileStamp | undefined): boolean {\n return left !== undefined && right !== undefined && left.size === right.size && left.modifiedAt === right.modifiedAt;\n}\n\nexport function createWorkflowCatalogReader(deps: WorkflowCatalogReaderDeps): WorkflowCatalogReader {\n const cache = new Map<string, CachedEntry>();\n\n const detailFor = (path: string): CatalogDetail => {\n const stamp = deps.stamp(path);\n const cached = cache.get(path);\n if (cached && unchanged(cached.stamp, stamp)) return cached.detail;\n const detail = deps.summarize(path);\n // A file that vanished between listing and stat is parsed anyway, which\n // answers with its own error; it just cannot be cached against a stamp.\n if (stamp !== undefined) cache.set(path, { stamp, detail });\n return detail;\n };\n\n return {\n async read(directory) {\n const listed = await deps.list(directory);\n return listed.map((entry) => {\n const detail = detailFor(entry.path);\n return {\n path: entry.path,\n relativePath: entry.relativePath,\n name: entry.name === '' ? entry.relativePath : entry.name,\n description: entry.description,\n tags: entry.tags,\n triggers: detail.triggers,\n inputs: detail.inputs,\n jobs: detail.jobs,\n artifacts: detail.artifacts,\n ...(detail.runners === undefined ? {} : { runners: detail.runners }),\n ...(detail.error === undefined ? {} : { error: detail.error }),\n };\n });\n },\n forget(keep) {\n for (const path of cache.keys()) {\n if (!keep.has(path)) cache.delete(path);\n }\n },\n };\n}\n\n/** The launchable rows first, then the ones that would not parse, name ordered. */\nexport function presentWorkflowCatalog(entries: readonly WorkflowCatalogEntryView[]): WorkflowCatalogEntryView[] {\n return [...entries].sort((left, right) => {\n if ((left.error === undefined) !== (right.error === undefined)) return left.error === undefined ? -1 : 1;\n return left.name.localeCompare(right.name);\n });\n}\n"],"mappings":"AAoEA,SAAS,EAAU,EAAoC,EAA8C,CACnG,OAAO,IAAS,IAAA,IAAa,IAAU,IAAA,IAAa,EAAK,OAAS,EAAM,MAAQ,EAAK,aAAe,EAAM,UAC5G,CAEA,SAAgB,EAA4B,EAAwD,CAClG,IAAM,EAAQ,IAAI,IAEZ,EAAa,GAAgC,CACjD,IAAM,EAAQ,EAAK,MAAM,CAAI,EACvB,EAAS,EAAM,IAAI,CAAI,EAC7B,GAAI,GAAU,EAAU,EAAO,MAAO,CAAK,EAAG,OAAO,EAAO,OAC5D,IAAM,EAAS,EAAK,UAAU,CAAI,EAIlC,OADI,IAAU,IAAA,IAAW,EAAM,IAAI,EAAM,CAAE,QAAO,QAAO,CAAC,EACnD,CACT,EAEA,MAAO,CACL,MAAM,KAAK,EAAW,CAEpB,OAAO,MADc,EAAK,KAAK,CAAS,EAAA,CAC1B,IAAK,GAAU,CAC3B,IAAM,EAAS,EAAU,EAAM,IAAI,EACnC,MAAO,CACL,KAAM,EAAM,KACZ,aAAc,EAAM,aACpB,KAAM,EAAM,OAAS,GAAK,EAAM,aAAe,EAAM,KACrD,YAAa,EAAM,YACnB,KAAM,EAAM,KACZ,SAAU,EAAO,SACjB,OAAQ,EAAO,OACf,KAAM,EAAO,KACb,UAAW,EAAO,UAClB,GAAI,EAAO,UAAY,IAAA,GAAY,CAAC,EAAI,CAAE,QAAS,EAAO,OAAQ,EAClE,GAAI,EAAO,QAAU,IAAA,GAAY,CAAC,EAAI,CAAE,MAAO,EAAO,KAAM,CAC9D,CACF,CAAC,CACH,EACA,OAAO,EAAM,CACX,IAAK,IAAM,KAAQ,EAAM,KAAK,EACvB,EAAK,IAAI,CAAI,GAAG,EAAM,OAAO,CAAI,CAE1C,CACF,CACF,CAGA,SAAgB,EAAuB,EAA0E,CAC/G,MAAO,CAAC,GAAG,CAAO,CAAC,CAAC,MAAM,EAAM,IACzB,EAAK,QAAU,IAAA,KAAgB,EAAM,QAAU,IAAA,IAC7C,EAAK,KAAK,cAAc,EAAM,IAAI,EAD8B,EAAK,QAAU,IAAA,GAAY,GAAK,CAExG,CACH"}
@@ -0,0 +1,49 @@
1
+ import { WorkflowCatalogArtifactView, WorkflowCatalogInputView, WorkflowCatalogJobView } from "../types/webWorkflows.cjs";
2
+ //#region src/services/webWorkflowCatalog.d.ts
3
+ /**
4
+ * The cockpit's view of the workflows a session can launch.
5
+ *
6
+ * The engine already reads a repository's workflow files and parses one of
7
+ * them into triggers, inputs, jobs, artifacts and compatible runners, so this
8
+ * module only shapes that answer for the wire and remembers what it read.
9
+ *
10
+ * WHY A CACHE:
11
+ * A session's catalog is recomputed on a slow tick and again on every
12
+ * subscribe, and parsing every workflow file each time is wasted work on files
13
+ * that change a few times a week. Entries are kept per path and reused while
14
+ * the file's size and modification time are unchanged, which is the same test
15
+ * a build tool makes and costs one stat per file.
16
+ */
17
+ /** What the engine reports for one workflow file before it is parsed. */
18
+ interface CatalogListEntry {
19
+ path: string;
20
+ relativePath: string;
21
+ name: string;
22
+ description: string;
23
+ tags: string[];
24
+ }
25
+ /** One file's parsed detail, or the reason it could not be read. */
26
+ interface CatalogDetail {
27
+ triggers: string[];
28
+ inputs: WorkflowCatalogInputView[];
29
+ jobs: WorkflowCatalogJobView[];
30
+ artifacts: WorkflowCatalogArtifactView[];
31
+ runners?: string[];
32
+ error?: string;
33
+ }
34
+ /** What a file looked like when it was last parsed. */
35
+ interface CatalogFileStamp {
36
+ size: number;
37
+ modifiedAt: number;
38
+ }
39
+ interface WorkflowCatalogReaderDeps {
40
+ /** Every workflow file under a directory. */
41
+ list(directory: string): Promise<CatalogListEntry[]>;
42
+ /** One file's parsed detail; never throws, so a bad file cannot empty a board. */
43
+ summarize(path: string): CatalogDetail;
44
+ /** Size and modification time, or undefined when the file is gone. */
45
+ stamp(path: string): CatalogFileStamp | undefined;
46
+ }
47
+ //#endregion
48
+ export { CatalogDetail, CatalogFileStamp, CatalogListEntry, WorkflowCatalogReaderDeps };
49
+ //# sourceMappingURL=webWorkflowCatalog.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webWorkflowCatalog.d.cts","names":[],"sources":["../../src/services/webWorkflowCatalog.ts"],"mappings":";;;;;;;;;;;;;;;;;UAuBiB;EACf;EACA;EACA;EACA;EACA;;;UAIe;EACf;EACA,QAAQ;EACR,MAAM;EACN,WAAW;EACX;EACA;;;UAIe;EACf;EACA;;UAGe;;EAEf,KAAK,oBAAoB,QAAQ;;EAEjC,UAAU,eAAe;;EAEzB,MAAM,eAAe"}
@@ -0,0 +1,49 @@
1
+ import { WorkflowCatalogArtifactView, WorkflowCatalogInputView, WorkflowCatalogJobView } from "../types/webWorkflows.mjs";
2
+ //#region src/services/webWorkflowCatalog.d.ts
3
+ /**
4
+ * The cockpit's view of the workflows a session can launch.
5
+ *
6
+ * The engine already reads a repository's workflow files and parses one of
7
+ * them into triggers, inputs, jobs, artifacts and compatible runners, so this
8
+ * module only shapes that answer for the wire and remembers what it read.
9
+ *
10
+ * WHY A CACHE:
11
+ * A session's catalog is recomputed on a slow tick and again on every
12
+ * subscribe, and parsing every workflow file each time is wasted work on files
13
+ * that change a few times a week. Entries are kept per path and reused while
14
+ * the file's size and modification time are unchanged, which is the same test
15
+ * a build tool makes and costs one stat per file.
16
+ */
17
+ /** What the engine reports for one workflow file before it is parsed. */
18
+ interface CatalogListEntry {
19
+ path: string;
20
+ relativePath: string;
21
+ name: string;
22
+ description: string;
23
+ tags: string[];
24
+ }
25
+ /** One file's parsed detail, or the reason it could not be read. */
26
+ interface CatalogDetail {
27
+ triggers: string[];
28
+ inputs: WorkflowCatalogInputView[];
29
+ jobs: WorkflowCatalogJobView[];
30
+ artifacts: WorkflowCatalogArtifactView[];
31
+ runners?: string[];
32
+ error?: string;
33
+ }
34
+ /** What a file looked like when it was last parsed. */
35
+ interface CatalogFileStamp {
36
+ size: number;
37
+ modifiedAt: number;
38
+ }
39
+ interface WorkflowCatalogReaderDeps {
40
+ /** Every workflow file under a directory. */
41
+ list(directory: string): Promise<CatalogListEntry[]>;
42
+ /** One file's parsed detail; never throws, so a bad file cannot empty a board. */
43
+ summarize(path: string): CatalogDetail;
44
+ /** Size and modification time, or undefined when the file is gone. */
45
+ stamp(path: string): CatalogFileStamp | undefined;
46
+ }
47
+ //#endregion
48
+ export { CatalogDetail, CatalogFileStamp, CatalogListEntry, WorkflowCatalogReaderDeps };
49
+ //# sourceMappingURL=webWorkflowCatalog.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webWorkflowCatalog.d.mts","names":[],"sources":["../../src/services/webWorkflowCatalog.ts"],"mappings":";;;;;;;;;;;;;;;;;UAuBiB;EACf;EACA;EACA;EACA;EACA;;;UAIe;EACf;EACA,QAAQ;EACR,MAAM;EACN,WAAW;EACX;EACA;;;UAIe;EACf;EACA;;UAGe;;EAEf,KAAK,oBAAoB,QAAQ;;EAEjC,UAAU,eAAe;;EAEzB,MAAM,eAAe"}
@@ -0,0 +1,2 @@
1
+ function e(e,t){return e!==void 0&&t!==void 0&&e.size===t.size&&e.modifiedAt===t.modifiedAt}function t(t){let n=new Map,r=r=>{let i=t.stamp(r),a=n.get(r);if(a&&e(a.stamp,i))return a.detail;let o=t.summarize(r);return i!==void 0&&n.set(r,{stamp:i,detail:o}),o};return{async read(e){return(await t.list(e)).map(e=>{let t=r(e.path);return{path:e.path,relativePath:e.relativePath,name:e.name===``?e.relativePath:e.name,description:e.description,tags:e.tags,triggers:t.triggers,inputs:t.inputs,jobs:t.jobs,artifacts:t.artifacts,...t.runners===void 0?{}:{runners:t.runners},...t.error===void 0?{}:{error:t.error}}})},forget(e){for(let t of n.keys())e.has(t)||n.delete(t)}}}function n(e){return[...e].sort((e,t)=>e.error===void 0==(t.error===void 0)?e.name.localeCompare(t.name):e.error===void 0?-1:1)}export{t as createWorkflowCatalogReader,n as presentWorkflowCatalog};
2
+ //# sourceMappingURL=webWorkflowCatalog.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webWorkflowCatalog.mjs","names":[],"sources":["../../src/services/webWorkflowCatalog.ts"],"sourcesContent":["import type {\n WorkflowCatalogArtifactView,\n WorkflowCatalogEntryView,\n WorkflowCatalogInputView,\n WorkflowCatalogJobView,\n} from '../types/webWorkflows.ts';\n\n/**\n * The cockpit's view of the workflows a session can launch.\n *\n * The engine already reads a repository's workflow files and parses one of\n * them into triggers, inputs, jobs, artifacts and compatible runners, so this\n * module only shapes that answer for the wire and remembers what it read.\n *\n * WHY A CACHE:\n * A session's catalog is recomputed on a slow tick and again on every\n * subscribe, and parsing every workflow file each time is wasted work on files\n * that change a few times a week. Entries are kept per path and reused while\n * the file's size and modification time are unchanged, which is the same test\n * a build tool makes and costs one stat per file.\n */\n\n/** What the engine reports for one workflow file before it is parsed. */\nexport interface CatalogListEntry {\n path: string;\n relativePath: string;\n name: string;\n description: string;\n tags: string[];\n}\n\n/** One file's parsed detail, or the reason it could not be read. */\nexport interface CatalogDetail {\n triggers: string[];\n inputs: WorkflowCatalogInputView[];\n jobs: WorkflowCatalogJobView[];\n artifacts: WorkflowCatalogArtifactView[];\n runners?: string[];\n error?: string;\n}\n\n/** What a file looked like when it was last parsed. */\nexport interface CatalogFileStamp {\n size: number;\n modifiedAt: number;\n}\n\nexport interface WorkflowCatalogReaderDeps {\n /** Every workflow file under a directory. */\n list(directory: string): Promise<CatalogListEntry[]>;\n /** One file's parsed detail; never throws, so a bad file cannot empty a board. */\n summarize(path: string): CatalogDetail;\n /** Size and modification time, or undefined when the file is gone. */\n stamp(path: string): CatalogFileStamp | undefined;\n}\n\ninterface CachedEntry {\n stamp: CatalogFileStamp;\n detail: CatalogDetail;\n}\n\n/** Reads one directory's catalog, reusing what it parsed last time. */\nexport interface WorkflowCatalogReader {\n read(directory: string): Promise<WorkflowCatalogEntryView[]>;\n /** Drops what was parsed for files no longer listed, so a cache cannot grow forever. */\n forget(keep: ReadonlySet<string>): void;\n}\n\nfunction unchanged(left: CatalogFileStamp | undefined, right: CatalogFileStamp | undefined): boolean {\n return left !== undefined && right !== undefined && left.size === right.size && left.modifiedAt === right.modifiedAt;\n}\n\nexport function createWorkflowCatalogReader(deps: WorkflowCatalogReaderDeps): WorkflowCatalogReader {\n const cache = new Map<string, CachedEntry>();\n\n const detailFor = (path: string): CatalogDetail => {\n const stamp = deps.stamp(path);\n const cached = cache.get(path);\n if (cached && unchanged(cached.stamp, stamp)) return cached.detail;\n const detail = deps.summarize(path);\n // A file that vanished between listing and stat is parsed anyway, which\n // answers with its own error; it just cannot be cached against a stamp.\n if (stamp !== undefined) cache.set(path, { stamp, detail });\n return detail;\n };\n\n return {\n async read(directory) {\n const listed = await deps.list(directory);\n return listed.map((entry) => {\n const detail = detailFor(entry.path);\n return {\n path: entry.path,\n relativePath: entry.relativePath,\n name: entry.name === '' ? entry.relativePath : entry.name,\n description: entry.description,\n tags: entry.tags,\n triggers: detail.triggers,\n inputs: detail.inputs,\n jobs: detail.jobs,\n artifacts: detail.artifacts,\n ...(detail.runners === undefined ? {} : { runners: detail.runners }),\n ...(detail.error === undefined ? {} : { error: detail.error }),\n };\n });\n },\n forget(keep) {\n for (const path of cache.keys()) {\n if (!keep.has(path)) cache.delete(path);\n }\n },\n };\n}\n\n/** The launchable rows first, then the ones that would not parse, name ordered. */\nexport function presentWorkflowCatalog(entries: readonly WorkflowCatalogEntryView[]): WorkflowCatalogEntryView[] {\n return [...entries].sort((left, right) => {\n if ((left.error === undefined) !== (right.error === undefined)) return left.error === undefined ? -1 : 1;\n return left.name.localeCompare(right.name);\n });\n}\n"],"mappings":"AAoEA,SAAS,EAAU,EAAoC,EAA8C,CACnG,OAAO,IAAS,IAAA,IAAa,IAAU,IAAA,IAAa,EAAK,OAAS,EAAM,MAAQ,EAAK,aAAe,EAAM,UAC5G,CAEA,SAAgB,EAA4B,EAAwD,CAClG,IAAM,EAAQ,IAAI,IAEZ,EAAa,GAAgC,CACjD,IAAM,EAAQ,EAAK,MAAM,CAAI,EACvB,EAAS,EAAM,IAAI,CAAI,EAC7B,GAAI,GAAU,EAAU,EAAO,MAAO,CAAK,EAAG,OAAO,EAAO,OAC5D,IAAM,EAAS,EAAK,UAAU,CAAI,EAIlC,OADI,IAAU,IAAA,IAAW,EAAM,IAAI,EAAM,CAAE,QAAO,QAAO,CAAC,EACnD,CACT,EAEA,MAAO,CACL,MAAM,KAAK,EAAW,CAEpB,OAAO,MADc,EAAK,KAAK,CAAS,EAAA,CAC1B,IAAK,GAAU,CAC3B,IAAM,EAAS,EAAU,EAAM,IAAI,EACnC,MAAO,CACL,KAAM,EAAM,KACZ,aAAc,EAAM,aACpB,KAAM,EAAM,OAAS,GAAK,EAAM,aAAe,EAAM,KACrD,YAAa,EAAM,YACnB,KAAM,EAAM,KACZ,SAAU,EAAO,SACjB,OAAQ,EAAO,OACf,KAAM,EAAO,KACb,UAAW,EAAO,UAClB,GAAI,EAAO,UAAY,IAAA,GAAY,CAAC,EAAI,CAAE,QAAS,EAAO,OAAQ,EAClE,GAAI,EAAO,QAAU,IAAA,GAAY,CAAC,EAAI,CAAE,MAAO,EAAO,KAAM,CAC9D,CACF,CAAC,CACH,EACA,OAAO,EAAM,CACX,IAAK,IAAM,KAAQ,EAAM,KAAK,EACvB,EAAK,IAAI,CAAI,GAAG,EAAM,OAAO,CAAI,CAE1C,CACF,CACF,CAGA,SAAgB,EAAuB,EAA0E,CAC/G,MAAO,CAAC,GAAG,CAAO,CAAC,CAAC,MAAM,EAAM,IACzB,EAAK,QAAU,IAAA,KAAgB,EAAM,QAAU,IAAA,IAC7C,EAAK,KAAK,cAAc,EAAM,IAAI,EAD8B,EAAK,QAAU,IAAA,GAAY,GAAK,CAExG,CACH"}
@@ -0,0 +1,2 @@
1
+ const e=/^([A-Za-z_][\w.-]*)=(.*)$/s;function t(e){return`error`in e}function n(e){return e.length>1&&e.startsWith(`"`)&&e.endsWith(`"`)?e.slice(1,-1):e}function r(e){let t=[],n=0;for(;n<e.length;){for(;n<e.length&&/\s/.test(e[n]);)n+=1;if(n>=e.length)break;let r=n,i=!1;for(;n<e.length&&(i||!/\s/.test(e[n]));)e[n]===`"`&&(i=!i),n+=1;t.push({text:e.slice(r,n),start:r})}return t}function i(t){let i=r(t.trim()),a=i[0];if(a===void 0)return{error:`Usage: /workflow-launch <workflow> [key=value …] [prompt]`};let o={},s,c=1;for(;c<i.length;c+=1){let t=i[c],r=e.exec(t.text);if(!r)break;let a=r[1],l=n(r[2]);a===`runner`?s=l:o[a]=l}let l=i[c],u=l===void 0?void 0:t.trim().slice(l.start).trim();return{workflow:n(a.text),...s===void 0?{}:{runner:s},inputs:o,...u===void 0||u===``?{}:{prompt:u}}}function a(e,t){let n=t.trim(),r=n.toLocaleLowerCase();return e.find(e=>e.name.toLocaleLowerCase()===r)??e.find(e=>e.relativePath===n||e.path===n)??e.find(e=>e.relativePath.endsWith(`/${n}`)||e.path.endsWith(`/${n}`))}function o(e,t){let n=[];e.triggers.includes(`user_prompt`)&&(t.prompt??``)===``&&n.push(`This workflow is triggered by a prompt, so it needs one.`);let r=e.inputs.filter(e=>e.required===!0&&(t.inputs[e.name]??``)===``).map(e=>e.name);r.length>0&&n.push(`Missing required input${r.length>1?`s`:``}: ${r.join(`, `)}.`);for(let r of e.inputs){let e=t.inputs[r.name];e===void 0||r.options===void 0||r.options.includes(e)||n.push(`Input ${r.name} must be one of: ${r.options.join(`, `)}.`)}let i=e.runners;return t.runner!==void 0&&i!==void 0&&!i.includes(t.runner)&&n.push(i.length===0?`This workflow declares no runner its steps agree on.`:`Runner ${t.runner} is not one this workflow declares: ${i.join(`, `)}.`),n}exports.isLaunchParseFailure=t,exports.parseWorkflowLaunchCommand=i,exports.resolveWorkflowEntry=a,exports.validateWorkflowLaunch=o;
2
+ //# sourceMappingURL=workflowLaunchCommand.cjs.map