@wiro-ai/wiro-mcp 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/README.md +173 -16
  2. package/dist/client.d.ts +10 -0
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +39 -20
  5. package/dist/client.js.map +1 -1
  6. package/dist/index.js +4 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/instructions.d.ts +2 -0
  9. package/dist/instructions.d.ts.map +1 -0
  10. package/dist/instructions.js +25 -0
  11. package/dist/instructions.js.map +1 -0
  12. package/dist/playbooks.d.ts +8 -0
  13. package/dist/playbooks.d.ts.map +1 -0
  14. package/dist/playbooks.js +665 -0
  15. package/dist/playbooks.js.map +1 -0
  16. package/dist/prompts.d.ts +4 -0
  17. package/dist/prompts.d.ts.map +1 -0
  18. package/dist/prompts.js +133 -0
  19. package/dist/prompts.js.map +1 -0
  20. package/dist/server.d.ts +9 -2
  21. package/dist/server.d.ts.map +1 -1
  22. package/dist/server.js +9 -3
  23. package/dist/server.js.map +1 -1
  24. package/dist/tools/apply-agent-skills.d.ts +4 -0
  25. package/dist/tools/apply-agent-skills.d.ts.map +1 -0
  26. package/dist/tools/apply-agent-skills.js +545 -0
  27. package/dist/tools/apply-agent-skills.js.map +1 -0
  28. package/dist/tools/cancel-agent-message.d.ts +4 -0
  29. package/dist/tools/cancel-agent-message.d.ts.map +1 -0
  30. package/dist/tools/cancel-agent-message.js +126 -0
  31. package/dist/tools/cancel-agent-message.js.map +1 -0
  32. package/dist/tools/cancel-task.js +3 -3
  33. package/dist/tools/cancel-task.js.map +1 -1
  34. package/dist/tools/clone-workflow.d.ts +4 -0
  35. package/dist/tools/clone-workflow.d.ts.map +1 -0
  36. package/dist/tools/clone-workflow.js +83 -0
  37. package/dist/tools/clone-workflow.js.map +1 -0
  38. package/dist/tools/connect-agent-oauth.d.ts +4 -0
  39. package/dist/tools/connect-agent-oauth.d.ts.map +1 -0
  40. package/dist/tools/connect-agent-oauth.js +508 -0
  41. package/dist/tools/connect-agent-oauth.js.map +1 -0
  42. package/dist/tools/control-agent.d.ts +4 -0
  43. package/dist/tools/control-agent.d.ts.map +1 -0
  44. package/dist/tools/control-agent.js +274 -0
  45. package/dist/tools/control-agent.js.map +1 -0
  46. package/dist/tools/create-workflow.d.ts +100 -0
  47. package/dist/tools/create-workflow.d.ts.map +1 -0
  48. package/dist/tools/create-workflow.js +103 -0
  49. package/dist/tools/create-workflow.js.map +1 -0
  50. package/dist/tools/delete-agent-session.d.ts +6 -0
  51. package/dist/tools/delete-agent-session.d.ts.map +1 -0
  52. package/dist/tools/delete-agent-session.js +134 -0
  53. package/dist/tools/delete-agent-session.js.map +1 -0
  54. package/dist/tools/delete-agent.d.ts +4 -0
  55. package/dist/tools/delete-agent.d.ts.map +1 -0
  56. package/dist/tools/delete-agent.js +144 -0
  57. package/dist/tools/delete-agent.js.map +1 -0
  58. package/dist/tools/delete-workflow.d.ts +4 -0
  59. package/dist/tools/delete-workflow.d.ts.map +1 -0
  60. package/dist/tools/delete-workflow.js +77 -0
  61. package/dist/tools/delete-workflow.js.map +1 -0
  62. package/dist/tools/deploy-agent.d.ts +4 -0
  63. package/dist/tools/deploy-agent.d.ts.map +1 -0
  64. package/dist/tools/deploy-agent.js +455 -0
  65. package/dist/tools/deploy-agent.js.map +1 -0
  66. package/dist/tools/disconnect-agent-oauth.d.ts +4 -0
  67. package/dist/tools/disconnect-agent-oauth.d.ts.map +1 -0
  68. package/dist/tools/disconnect-agent-oauth.js +118 -0
  69. package/dist/tools/disconnect-agent-oauth.js.map +1 -0
  70. package/dist/tools/get-account.d.ts +4 -0
  71. package/dist/tools/get-account.d.ts.map +1 -0
  72. package/dist/tools/get-account.js +110 -0
  73. package/dist/tools/get-account.js.map +1 -0
  74. package/dist/tools/get-agent-activity.d.ts +4 -0
  75. package/dist/tools/get-agent-activity.d.ts.map +1 -0
  76. package/dist/tools/get-agent-activity.js +383 -0
  77. package/dist/tools/get-agent-activity.js.map +1 -0
  78. package/dist/tools/get-agent-conversation.d.ts +4 -0
  79. package/dist/tools/get-agent-conversation.d.ts.map +1 -0
  80. package/dist/tools/get-agent-conversation.js +224 -0
  81. package/dist/tools/get-agent-conversation.js.map +1 -0
  82. package/dist/tools/get-agent-message.d.ts +4 -0
  83. package/dist/tools/get-agent-message.d.ts.map +1 -0
  84. package/dist/tools/get-agent-message.js +68 -0
  85. package/dist/tools/get-agent-message.js.map +1 -0
  86. package/dist/tools/get-agent-template.d.ts +4 -0
  87. package/dist/tools/get-agent-template.d.ts.map +1 -0
  88. package/dist/tools/get-agent-template.js +107 -0
  89. package/dist/tools/get-agent-template.js.map +1 -0
  90. package/dist/tools/get-model-schema.d.ts +5 -1
  91. package/dist/tools/get-model-schema.d.ts.map +1 -1
  92. package/dist/tools/get-model-schema.js +136 -8
  93. package/dist/tools/get-model-schema.js.map +1 -1
  94. package/dist/tools/get-my-agent.d.ts +4 -0
  95. package/dist/tools/get-my-agent.d.ts.map +1 -0
  96. package/dist/tools/get-my-agent.js +78 -0
  97. package/dist/tools/get-my-agent.js.map +1 -0
  98. package/dist/tools/get-playbook.d.ts +3 -0
  99. package/dist/tools/get-playbook.d.ts.map +1 -0
  100. package/dist/tools/get-playbook.js +50 -0
  101. package/dist/tools/get-playbook.js.map +1 -0
  102. package/dist/tools/get-task-price.d.ts.map +1 -1
  103. package/dist/tools/get-task-price.js +42 -15
  104. package/dist/tools/get-task-price.js.map +1 -1
  105. package/dist/tools/get-task.d.ts.map +1 -1
  106. package/dist/tools/get-task.js +4 -3
  107. package/dist/tools/get-task.js.map +1 -1
  108. package/dist/tools/get-workflow.d.ts +9 -0
  109. package/dist/tools/get-workflow.d.ts.map +1 -0
  110. package/dist/tools/get-workflow.js +99 -0
  111. package/dist/tools/get-workflow.js.map +1 -0
  112. package/dist/tools/index.d.ts +62 -2
  113. package/dist/tools/index.d.ts.map +1 -1
  114. package/dist/tools/index.js +127 -14
  115. package/dist/tools/index.js.map +1 -1
  116. package/dist/tools/kill-task.d.ts.map +1 -1
  117. package/dist/tools/kill-task.js +10 -6
  118. package/dist/tools/kill-task.js.map +1 -1
  119. package/dist/tools/list-agent-skills.d.ts +4 -0
  120. package/dist/tools/list-agent-skills.d.ts.map +1 -0
  121. package/dist/tools/list-agent-skills.js +242 -0
  122. package/dist/tools/list-agent-skills.js.map +1 -0
  123. package/dist/tools/list-agent-templates.d.ts +4 -0
  124. package/dist/tools/list-agent-templates.d.ts.map +1 -0
  125. package/dist/tools/list-agent-templates.js +110 -0
  126. package/dist/tools/list-agent-templates.js.map +1 -0
  127. package/dist/tools/list-my-agents.d.ts +4 -0
  128. package/dist/tools/list-my-agents.d.ts.map +1 -0
  129. package/dist/tools/list-my-agents.js +90 -0
  130. package/dist/tools/list-my-agents.js.map +1 -0
  131. package/dist/tools/list-studio-results.d.ts +4 -0
  132. package/dist/tools/list-studio-results.d.ts.map +1 -0
  133. package/dist/tools/list-studio-results.js +95 -0
  134. package/dist/tools/list-studio-results.js.map +1 -0
  135. package/dist/tools/list-tasks.d.ts.map +1 -1
  136. package/dist/tools/list-tasks.js +7 -5
  137. package/dist/tools/list-tasks.js.map +1 -1
  138. package/dist/tools/list-workflow-runs.d.ts +4 -0
  139. package/dist/tools/list-workflow-runs.d.ts.map +1 -0
  140. package/dist/tools/list-workflow-runs.js +110 -0
  141. package/dist/tools/list-workflow-runs.js.map +1 -0
  142. package/dist/tools/list-workflows.d.ts +4 -0
  143. package/dist/tools/list-workflows.d.ts.map +1 -0
  144. package/dist/tools/list-workflows.js +143 -0
  145. package/dist/tools/list-workflows.js.map +1 -0
  146. package/dist/tools/manage-agent-billing.d.ts +4 -0
  147. package/dist/tools/manage-agent-billing.d.ts.map +1 -0
  148. package/dist/tools/manage-agent-billing.js +764 -0
  149. package/dist/tools/manage-agent-billing.js.map +1 -0
  150. package/dist/tools/manage-agent-custom-skill.d.ts +4 -0
  151. package/dist/tools/manage-agent-custom-skill.d.ts.map +1 -0
  152. package/dist/tools/manage-agent-custom-skill.js +537 -0
  153. package/dist/tools/manage-agent-custom-skill.js.map +1 -0
  154. package/dist/tools/recommend-model.d.ts.map +1 -1
  155. package/dist/tools/recommend-model.js +6 -5
  156. package/dist/tools/recommend-model.js.map +1 -1
  157. package/dist/tools/resume-workflow-run.d.ts +4 -0
  158. package/dist/tools/resume-workflow-run.d.ts.map +1 -0
  159. package/dist/tools/resume-workflow-run.js +69 -0
  160. package/dist/tools/resume-workflow-run.js.map +1 -0
  161. package/dist/tools/run-model.d.ts.map +1 -1
  162. package/dist/tools/run-model.js +39 -3
  163. package/dist/tools/run-model.js.map +1 -1
  164. package/dist/tools/run-workflow.d.ts +6 -0
  165. package/dist/tools/run-workflow.d.ts.map +1 -0
  166. package/dist/tools/run-workflow.js +132 -0
  167. package/dist/tools/run-workflow.js.map +1 -0
  168. package/dist/tools/save-workflow-from-agent.d.ts +4 -0
  169. package/dist/tools/save-workflow-from-agent.d.ts.map +1 -0
  170. package/dist/tools/save-workflow-from-agent.js +283 -0
  171. package/dist/tools/save-workflow-from-agent.js.map +1 -0
  172. package/dist/tools/schemas.d.ts +62 -12
  173. package/dist/tools/schemas.d.ts.map +1 -1
  174. package/dist/tools/schemas.js +17 -3
  175. package/dist/tools/schemas.js.map +1 -1
  176. package/dist/tools/search-models.js +6 -6
  177. package/dist/tools/search-models.js.map +1 -1
  178. package/dist/tools/send-agent-message.d.ts +4 -0
  179. package/dist/tools/send-agent-message.d.ts.map +1 -0
  180. package/dist/tools/send-agent-message.js +156 -0
  181. package/dist/tools/send-agent-message.js.map +1 -0
  182. package/dist/tools/set-agent-credentials.d.ts +4 -0
  183. package/dist/tools/set-agent-credentials.d.ts.map +1 -0
  184. package/dist/tools/set-agent-credentials.js +529 -0
  185. package/dist/tools/set-agent-credentials.js.map +1 -0
  186. package/dist/tools/update-agent.d.ts +4 -0
  187. package/dist/tools/update-agent.d.ts.map +1 -0
  188. package/dist/tools/update-agent.js +178 -0
  189. package/dist/tools/update-agent.js.map +1 -0
  190. package/dist/tools/update-workflow.d.ts +201 -0
  191. package/dist/tools/update-workflow.d.ts.map +1 -0
  192. package/dist/tools/update-workflow.js +168 -0
  193. package/dist/tools/update-workflow.js.map +1 -0
  194. package/dist/tools/upload-file.d.ts.map +1 -1
  195. package/dist/tools/upload-file.js +4 -3
  196. package/dist/tools/upload-file.js.map +1 -1
  197. package/dist/tools/wait-for-workflow-run.d.ts +259 -0
  198. package/dist/tools/wait-for-workflow-run.d.ts.map +1 -0
  199. package/dist/tools/wait-for-workflow-run.js +190 -0
  200. package/dist/tools/wait-for-workflow-run.js.map +1 -0
  201. package/dist/types.d.ts +22 -4
  202. package/dist/types.d.ts.map +1 -1
  203. package/dist/types.js.map +1 -1
  204. package/dist/utils/agent-catalog.d.ts +1256 -0
  205. package/dist/utils/agent-catalog.d.ts.map +1 -0
  206. package/dist/utils/agent-catalog.js +388 -0
  207. package/dist/utils/agent-catalog.js.map +1 -0
  208. package/dist/utils/agent-config.d.ts +441 -0
  209. package/dist/utils/agent-config.d.ts.map +1 -0
  210. package/dist/utils/agent-config.js +1667 -0
  211. package/dist/utils/agent-config.js.map +1 -0
  212. package/dist/utils/agent-message.d.ts +115 -0
  213. package/dist/utils/agent-message.d.ts.map +1 -0
  214. package/dist/utils/agent-message.js +246 -0
  215. package/dist/utils/agent-message.js.map +1 -0
  216. package/dist/utils/agent-targets.d.ts +76 -0
  217. package/dist/utils/agent-targets.d.ts.map +1 -0
  218. package/dist/utils/agent-targets.js +408 -0
  219. package/dist/utils/agent-targets.js.map +1 -0
  220. package/dist/utils/agent-turns.d.ts +354 -0
  221. package/dist/utils/agent-turns.d.ts.map +1 -0
  222. package/dist/utils/agent-turns.js +623 -0
  223. package/dist/utils/agent-turns.js.map +1 -0
  224. package/dist/utils/api.d.ts +40 -0
  225. package/dist/utils/api.d.ts.map +1 -0
  226. package/dist/utils/api.js +111 -0
  227. package/dist/utils/api.js.map +1 -0
  228. package/dist/utils/format.d.ts +42 -1
  229. package/dist/utils/format.d.ts.map +1 -1
  230. package/dist/utils/format.js +173 -14
  231. package/dist/utils/format.js.map +1 -1
  232. package/dist/utils/media.d.ts +28 -0
  233. package/dist/utils/media.d.ts.map +1 -0
  234. package/dist/utils/media.js +65 -0
  235. package/dist/utils/media.js.map +1 -0
  236. package/dist/utils/poll.d.ts +39 -0
  237. package/dist/utils/poll.d.ts.map +1 -0
  238. package/dist/utils/poll.js +111 -0
  239. package/dist/utils/poll.js.map +1 -0
  240. package/dist/utils/remote-file.js +2 -2
  241. package/dist/utils/remote-file.js.map +1 -1
  242. package/dist/utils/structured.d.ts +2 -0
  243. package/dist/utils/structured.d.ts.map +1 -1
  244. package/dist/utils/structured.js +10 -10
  245. package/dist/utils/structured.js.map +1 -1
  246. package/dist/utils/studio-runs.d.ts +87 -0
  247. package/dist/utils/studio-runs.d.ts.map +1 -0
  248. package/dist/utils/studio-runs.js +167 -0
  249. package/dist/utils/studio-runs.js.map +1 -0
  250. package/dist/utils/user-agent.d.ts +1140 -0
  251. package/dist/utils/user-agent.d.ts.map +1 -0
  252. package/dist/utils/user-agent.js +802 -0
  253. package/dist/utils/user-agent.js.map +1 -0
  254. package/dist/utils/workflow-agent.d.ts +151 -0
  255. package/dist/utils/workflow-agent.d.ts.map +1 -0
  256. package/dist/utils/workflow-agent.js +292 -0
  257. package/dist/utils/workflow-agent.js.map +1 -0
  258. package/dist/utils/workflow-artifacts.d.ts +160 -0
  259. package/dist/utils/workflow-artifacts.d.ts.map +1 -0
  260. package/dist/utils/workflow-artifacts.js +801 -0
  261. package/dist/utils/workflow-artifacts.js.map +1 -0
  262. package/dist/utils/workflow-save.d.ts +123 -0
  263. package/dist/utils/workflow-save.d.ts.map +1 -0
  264. package/dist/utils/workflow-save.js +734 -0
  265. package/dist/utils/workflow-save.js.map +1 -0
  266. package/dist/utils/workflow.d.ts +559 -0
  267. package/dist/utils/workflow.d.ts.map +1 -0
  268. package/dist/utils/workflow.js +613 -0
  269. package/dist/utils/workflow.js.map +1 -0
  270. package/package.json +1 -1
@@ -0,0 +1,613 @@
1
+ import { z } from 'zod';
2
+ import { asString, isPlainObject, toInteger, toNumber, truncate, unixToIso, } from './api.js';
3
+ // Workflow rows and runs as the Wiro API returns them, projected to what an
4
+ // assistant needs. Owner, team and moderator identities are dropped, and run
5
+ // outputs are resolved from the End node the same way the run engine does.
6
+ export const WORKFLOW_PAGE_URL = 'https://wiro.ai/workflows/';
7
+ export const RUN_TERMINAL_STATUSES = ['completed', 'failed', 'cancelled', 'stopped'];
8
+ export class WorkflowInputError extends Error {
9
+ }
10
+ // What the workflow tools hide, and where. A secret-looking name matches
11
+ // SENSITIVE_INPUT_KEY (the Workflow Agent's own rule); a key-looking value
12
+ // matches looksLikeSecretValue.
13
+ // - Input lists (get_workflow and the save results): a Start field's default and
14
+ // saved value show as "[hidden]" under a secret-looking name or when the value
15
+ // looks like a key, for every caller.
16
+ // - get_workflow contents, for every caller: a saved value (metadata.input.values)
17
+ // or Start field default under a secret-looking name or that looks like a key
18
+ // shows as "[hidden]". For a caller outside the workflow's workspace, values
19
+ // under a secret-looking key at any depth of a step's input also show as
20
+ // "[REDACTED]". "$" references are wiring, not values, and stay as they are.
21
+ // - wait_for_workflow_run debug_node_id: the inputs a step received, under a
22
+ // secret-looking key at any depth ("[hidden]").
23
+ // update_workflow restores "[hidden]" and "[REDACTED]" from the saved workflow.
24
+ export const SENSITIVE_INPUT_KEY = /(^|[-_])(password|secret|api[-_]?key|authorization|credentials?|private[-_]?key|access[-_]?token|refresh[-_]?token|client[-_]?secret|bearer[-_]?token|token)([-_]|$)/i;
25
+ // The shapes of common provider API keys, which a name check alone misses
26
+ // (an "openai_key" field, say).
27
+ const PROVIDER_KEY_SHAPES = [
28
+ /^sk-[A-Za-z0-9_-]{20,}$/, // OpenAI, Anthropic
29
+ /^AIza[0-9A-Za-z_-]{30,}$/, // Google API
30
+ /^(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}$/, // GitHub
31
+ /^github_pat_[A-Za-z0-9_]{20,}$/, // GitHub fine-grained
32
+ /^xox[abprs]-[A-Za-z0-9-]{10,}$/, // Slack
33
+ /^AKIA[0-9A-Z]{16}$/, // AWS access key id
34
+ /^hf_[A-Za-z0-9]{30,}$/, // Hugging Face
35
+ /^r8_[A-Za-z0-9]{30,}$/, // Replicate
36
+ /^(sk|rk)_(live|test)_[A-Za-z0-9]{16,}$/, // Stripe
37
+ ];
38
+ /** True for a string shaped like a known provider's API key, whatever field holds it. */
39
+ export function looksLikeSecretValue(value) {
40
+ if (typeof value !== 'string')
41
+ return false;
42
+ const text = value.trim();
43
+ return PROVIDER_KEY_SHAPES.some(shape => shape.test(text));
44
+ }
45
+ function isFilledValue(value) {
46
+ return value !== '' && value !== null && value !== undefined;
47
+ }
48
+ /** Whether a Start field's default or saved value is shown as "[hidden]". */
49
+ function hidesStartValue(key, value) {
50
+ return isFilledValue(value) && (looksLikeSecretValue(value) || SENSITIVE_INPUT_KEY.test(key));
51
+ }
52
+ /** Accepts '<slug>-<slugid>' or a wiro.ai workflow URL and returns the full slug. */
53
+ export function parseWorkflowSlug(input) {
54
+ let value = input.trim();
55
+ if (/^https?:\/\//i.test(value)) {
56
+ try {
57
+ const url = new URL(value);
58
+ value = url.pathname.split('/').filter(Boolean).pop() ?? '';
59
+ }
60
+ catch {
61
+ throw new WorkflowInputError(`"${input}" is not a valid workflow URL.`);
62
+ }
63
+ }
64
+ value = decodeURIComponent(value.split(/[?#]/)[0] ?? '');
65
+ const separator = value.lastIndexOf('-');
66
+ const slugid = separator > 0 ? value.slice(separator + 1) : '';
67
+ if (value.length < 7 || separator <= 0 || !/^[A-Za-z0-9]{5}$/.test(slugid)) {
68
+ throw new WorkflowInputError(`"${input}" is not a full workflow slug. Use the "fullSlug" from list_workflows `
69
+ + 'or get_workflow (it ends with a dash and a 5-character id), or the workflow page URL.');
70
+ }
71
+ return value;
72
+ }
73
+ export function fullSlugOf(row) {
74
+ const slug = asString(row.slug);
75
+ const slugid = asString(row.slugid);
76
+ return slug && slugid ? `${slug}-${slugid}` : undefined;
77
+ }
78
+ export function workflowUrl(fullSlug) {
79
+ return `${WORKFLOW_PAGE_URL}${fullSlug}`;
80
+ }
81
+ export function parseJsonValue(value) {
82
+ if (typeof value !== 'string')
83
+ return value;
84
+ try {
85
+ return JSON.parse(value);
86
+ }
87
+ catch {
88
+ return undefined;
89
+ }
90
+ }
91
+ export function workflowTags(row) {
92
+ const tags = parseJsonValue(row.tags);
93
+ return Array.isArray(tags) ? tags.filter((tag) => typeof tag === 'string') : [];
94
+ }
95
+ export function workflowState(row) {
96
+ if (toInteger(row.ispublic) !== 1)
97
+ return 'private';
98
+ const status = toInteger(row.status, 1);
99
+ if (status === 2)
100
+ return 'published';
101
+ if (status === 3)
102
+ return 'rejected';
103
+ return 'pending_review';
104
+ }
105
+ export const workflowInputSchema = z.object({
106
+ key: z.string(),
107
+ label: z.string(),
108
+ type: z.string(),
109
+ field: z.string().optional(),
110
+ options: z.array(z.object({ value: z.string(), text: z.string() })).optional(),
111
+ default: z.unknown().optional(),
112
+ savedValue: z.unknown().optional(),
113
+ });
114
+ /** The Start fields a run takes, keyed exactly as /Workflow/Run expects. */
115
+ export function workflowInputs(contents) {
116
+ if (!isPlainObject(contents))
117
+ return [];
118
+ const schema = isPlainObject(contents.schema) ? contents.schema : {};
119
+ const fields = isPlainObject(schema.input) ? schema.input : {};
120
+ const metadata = isPlainObject(contents.metadata) ? contents.metadata : {};
121
+ const inputMetadata = isPlainObject(metadata.input) ? metadata.input : {};
122
+ const values = isPlainObject(inputMetadata.values) ? inputMetadata.values : {};
123
+ return Object.entries(fields).map(([key, rawField]) => {
124
+ const field = isPlainObject(rawField) ? rawField : {};
125
+ const ui = isPlainObject(field.ui) ? field.ui : {};
126
+ const options = Array.isArray(ui.options)
127
+ ? ui.options
128
+ .filter(isPlainObject)
129
+ .map(option => ({
130
+ value: String(option.value ?? ''),
131
+ text: String(option.text ?? option.value ?? ''),
132
+ }))
133
+ : undefined;
134
+ // A summary is for showing the inputs, so a secret-looking field's value and
135
+ // a value that looks like a key are masked here, for every caller.
136
+ const shown = (value) => (hidesStartValue(key, value) ? '[hidden]' : value);
137
+ return {
138
+ key,
139
+ label: asString(field.label) || asString(field.name) || key,
140
+ type: asString(field.type) || 'string',
141
+ ...(asString(ui.field) ? { field: asString(ui.field) } : {}),
142
+ ...(options && options.length > 0 ? { options } : {}),
143
+ ...(Object.prototype.hasOwnProperty.call(field, 'defaultValue') ? { default: shown(field.defaultValue) } : {}),
144
+ ...(Object.prototype.hasOwnProperty.call(values, key) ? { savedValue: shown(values[key]) } : {}),
145
+ };
146
+ });
147
+ }
148
+ /** End node fields: output label to reference. */
149
+ export function workflowOutputs(contents) {
150
+ if (!isPlainObject(contents) || !isPlainObject(contents.nodes))
151
+ return {};
152
+ const end = contents.nodes.output;
153
+ return isPlainObject(end) && isPlainObject(end.fields) ? { ...end.fields } : {};
154
+ }
155
+ export function workflowStepCount(contents) {
156
+ if (!isPlainObject(contents) || !isPlainObject(contents.nodes))
157
+ return 0;
158
+ return Object.keys(contents.nodes).filter(key => key !== 'output').length;
159
+ }
160
+ export const workflowSummarySchema = z.object({
161
+ fullSlug: z.string(),
162
+ url: z.string(),
163
+ title: z.string(),
164
+ description: z.string().optional(),
165
+ tags: z.array(z.string()),
166
+ state: z.enum(['private', 'pending_review', 'published', 'rejected']).optional(),
167
+ rejectReason: z.string().optional(),
168
+ isProtected: z.boolean(),
169
+ isClone: z.boolean().optional(),
170
+ stepCount: z.number().optional(),
171
+ toolCount: z.number().optional(),
172
+ models: z.array(z.string()).optional(),
173
+ creator: z.string().optional(),
174
+ team: z.string().optional(),
175
+ runCount: z.number().optional()
176
+ .describe('Runs while the workflow was in the public gallery, by anyone. Left out when there are none; the owner\'s runs of a private workflow are in list_workflow_runs.'),
177
+ likeCount: z.number().optional(),
178
+ cloneCount: z.number().optional(),
179
+ ratingAverage: z.number().optional(),
180
+ ratingCount: z.number().optional(),
181
+ commentCount: z.number().optional(),
182
+ cover: z.string().optional(),
183
+ createdAt: z.string().optional(),
184
+ updatedAt: z.string().optional(),
185
+ updatetime: z.string().optional(),
186
+ });
187
+ function optionalNumber(value) {
188
+ const parsed = toNumber(value);
189
+ return parsed === null ? undefined : parsed;
190
+ }
191
+ export function toWorkflowSummary(row, options = {}) {
192
+ const fullSlug = fullSlugOf(row) ?? '';
193
+ const user = isPlainObject(row.user) ? row.user : undefined;
194
+ const models = Array.isArray(row.tools)
195
+ ? row.tools.filter((tool) => typeof tool === 'string')
196
+ : undefined;
197
+ const description = asString(row.description);
198
+ const rejectReason = asString(row.rejectreason);
199
+ return {
200
+ fullSlug,
201
+ url: workflowUrl(fullSlug),
202
+ title: asString(row.title) ?? fullSlug,
203
+ ...(description ? { description: truncate(description, 600) } : {}),
204
+ tags: workflowTags(row),
205
+ ...(options.includeState ? { state: workflowState(row) } : {}),
206
+ ...(options.includeState && rejectReason ? { rejectReason } : {}),
207
+ isProtected: toInteger(row.isprotected) === 1,
208
+ ...(row.clonedfromid !== undefined ? { isClone: row.clonedfromid !== null && row.clonedfromid !== '' } : {}),
209
+ ...(optionalNumber(row.stepcount) !== undefined ? { stepCount: optionalNumber(row.stepcount) } : {}),
210
+ ...(optionalNumber(row.toolcount) !== undefined ? { toolCount: optionalNumber(row.toolcount) } : {}),
211
+ ...(models ? { models } : {}),
212
+ ...(user && asString(user.username) ? { creator: asString(user.username) } : {}),
213
+ ...(asString(row.teamname) ? { team: asString(row.teamname) } : {}),
214
+ // Wiro counts runs only while a workflow is in the public gallery, so the
215
+ // owner's runs of a private workflow or a copy are not in it.
216
+ ...((optionalNumber(row.runcount) ?? 0) > 0 ? { runCount: optionalNumber(row.runcount) } : {}),
217
+ ...(optionalNumber(row.likecount) !== undefined ? { likeCount: optionalNumber(row.likecount) } : {}),
218
+ ...(optionalNumber(row.clonecount) !== undefined ? { cloneCount: optionalNumber(row.clonecount) } : {}),
219
+ ...(optionalNumber(row.ratingaverage) !== undefined ? { ratingAverage: optionalNumber(row.ratingaverage) } : {}),
220
+ ...(optionalNumber(row.ratingcount) !== undefined ? { ratingCount: optionalNumber(row.ratingcount) } : {}),
221
+ ...(optionalNumber(row.commentcount) !== undefined ? { commentCount: optionalNumber(row.commentcount) } : {}),
222
+ ...(asString(row.cover) ? { cover: asString(row.cover) } : {}),
223
+ ...(unixToIso(row.createtime) ? { createdAt: unixToIso(row.createtime) } : {}),
224
+ ...(unixToIso(row.updatetime) ? { updatedAt: unixToIso(row.updatetime) } : {}),
225
+ ...(asString(row.updatetime) ? { updatetime: asString(row.updatetime) } : {}),
226
+ };
227
+ }
228
+ /** Masks, in place, the Start field defaults and saved values under a secret-looking name or that look like a key. */
229
+ function maskStartValues(contents) {
230
+ const schema = isPlainObject(contents.schema) ? contents.schema : {};
231
+ if (isPlainObject(schema.input)) {
232
+ for (const [key, field] of Object.entries(schema.input)) {
233
+ if (isPlainObject(field) && hidesStartValue(key, field.defaultValue))
234
+ field.defaultValue = '[hidden]';
235
+ }
236
+ }
237
+ const metadata = isPlainObject(contents.metadata) ? contents.metadata : {};
238
+ const inputMetadata = isPlainObject(metadata.input) ? metadata.input : {};
239
+ if (isPlainObject(inputMetadata.values)) {
240
+ const values = inputMetadata.values;
241
+ for (const [key, value] of Object.entries(values)) {
242
+ if (hidesStartValue(key, value))
243
+ values[key] = '[hidden]';
244
+ }
245
+ }
246
+ }
247
+ /** A step input with every value under a secret-looking key blanked, at any depth. */
248
+ function redactSecretKeys(value, secret = false) {
249
+ if (typeof value === 'string')
250
+ return secret && !value.startsWith('$') ? '[REDACTED]' : value;
251
+ if (Array.isArray(value))
252
+ return value.map(entry => redactSecretKeys(entry, secret));
253
+ if (isPlainObject(value)) {
254
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
255
+ key,
256
+ redactSecretKeys(entry, secret || SENSITIVE_INPUT_KEY.test(key)),
257
+ ]));
258
+ }
259
+ return secret && value !== null && value !== undefined ? '[REDACTED]' : value;
260
+ }
261
+ /** The graph as a caller outside the workflow's workspace sees it. */
262
+ function redactNodeInputs(contents) {
263
+ const copy = JSON.parse(JSON.stringify(contents));
264
+ if (isPlainObject(copy.nodes)) {
265
+ for (const node of Object.values(copy.nodes)) {
266
+ if (isPlainObject(node) && node.input !== undefined)
267
+ node.input = redactSecretKeys(node.input);
268
+ }
269
+ }
270
+ maskStartValues(copy);
271
+ return copy;
272
+ }
273
+ /** The graph as the workflow's own workspace sees it: secret Start values are hidden, step inputs are not. */
274
+ function hideStartValues(contents) {
275
+ const copy = JSON.parse(JSON.stringify(contents));
276
+ maskStartValues(copy);
277
+ return copy;
278
+ }
279
+ export const workflowDetailSchema = workflowSummarySchema.extend({
280
+ insider: z.boolean(),
281
+ canEditGraph: z.boolean(),
282
+ graphHidden: z.boolean(),
283
+ ownerInactive: z.boolean().optional(),
284
+ inputs: z.array(workflowInputSchema),
285
+ outputs: z.record(z.string(), z.unknown()),
286
+ estimatedCost: z.object({
287
+ type: z.string().optional(),
288
+ valueUsd: z.number().nullable().optional(),
289
+ }).optional(),
290
+ contents: z.record(z.string(), z.unknown()).optional(),
291
+ });
292
+ export function toWorkflowDetail(row, options = {}) {
293
+ const contents = parseJsonValue(row.contents);
294
+ const contentsObject = isPlainObject(contents) ? contents : {};
295
+ // Moderation fields are only sent to the owner and the owner's team.
296
+ const insider = Object.prototype.hasOwnProperty.call(row, 'rejectreason');
297
+ const isProtected = toInteger(row.isprotected) === 1;
298
+ const isClone = row.clonedfromid !== undefined && row.clonedfromid !== null && row.clonedfromid !== '';
299
+ const graphHidden = row.estimatedcost !== undefined
300
+ || (isProtected && workflowStepCount(contentsObject) === 0);
301
+ const estimated = isPlainObject(row.estimatedcost) ? row.estimatedcost : undefined;
302
+ const description = asString(row.description);
303
+ return {
304
+ ...toWorkflowSummary(row, { includeState: insider }),
305
+ // The full text: an edit starts from this value, so it must not be shortened.
306
+ ...(description ? { description } : {}),
307
+ // A hidden graph arrives emptied, so its own count would read 0; the row's
308
+ // stepcount, when sent, is kept instead.
309
+ ...(graphHidden ? {} : { stepCount: workflowStepCount(contentsObject) }),
310
+ insider,
311
+ canEditGraph: insider && !graphHidden && !(isClone && isProtected),
312
+ graphHidden,
313
+ ...(row.ownerinactive === true ? { ownerInactive: true } : {}),
314
+ inputs: workflowInputs(contentsObject),
315
+ outputs: workflowOutputs(contentsObject),
316
+ ...(estimated
317
+ ? {
318
+ estimatedCost: {
319
+ ...(asString(estimated.type) ? { type: asString(estimated.type) } : {}),
320
+ valueUsd: toNumber(estimated.value),
321
+ },
322
+ }
323
+ : {}),
324
+ ...(options.includeGraph !== false && !graphHidden && isPlainObject(contents)
325
+ ? { contents: insider ? hideStartValues(contentsObject) : redactNodeInputs(contentsObject) }
326
+ : {}),
327
+ };
328
+ }
329
+ /** A value for a one-line summary: long text is cut inside its quotes. */
330
+ function summaryValue(value) {
331
+ if (typeof value === 'string')
332
+ return JSON.stringify(truncate(value, 80));
333
+ return truncate(JSON.stringify(value) ?? '', 80);
334
+ }
335
+ export function formatWorkflowInputs(inputs) {
336
+ if (inputs.length === 0)
337
+ return ['No inputs. Run it with no `input`.'];
338
+ return inputs.map(input => {
339
+ const kind = input.field ? `${input.type}, ${input.field}` : input.type;
340
+ const fallback = input.savedValue !== undefined
341
+ ? ` (saved value: ${summaryValue(input.savedValue)})`
342
+ : input.default !== undefined && input.default !== ''
343
+ ? ` (default: ${summaryValue(input.default)})`
344
+ : '';
345
+ const options = input.options ? ` options: ${input.options.map(option => option.value).join(', ')}` : '';
346
+ return `- \`${input.key}\` (${kind}): ${input.label}${fallback}${options}`;
347
+ });
348
+ }
349
+ // ---------------------------------------------------------------------------
350
+ // Runs
351
+ export const runOutputItemSchema = z.object({
352
+ url: z.string().optional(),
353
+ name: z.string().optional(),
354
+ mimeType: z.string().optional(),
355
+ sizeBytes: z.number().nullable().optional(),
356
+ text: z.string().optional(),
357
+ });
358
+ export const runStepSchema = z.object({
359
+ nodeId: z.string(),
360
+ tool: z.string().optional(),
361
+ kind: z.string().optional(),
362
+ status: z.string(),
363
+ cause: z.string().optional(),
364
+ error: z.string().optional(),
365
+ });
366
+ export const workflowRunSchema = z.object({
367
+ workflowtoken: z.string(),
368
+ workflowSlug: z.string().optional(),
369
+ status: z.string(),
370
+ outcome: z.string().optional(),
371
+ canResume: z.boolean(),
372
+ graphMasked: z.boolean().optional(),
373
+ breakpointNodeId: z.string().nullable().optional(),
374
+ startedAt: z.string().optional(),
375
+ resumedAt: z.string().optional(),
376
+ endedAt: z.string().optional(),
377
+ elapsedSeconds: z.number().optional(),
378
+ listPriceUsd: z.number().nullable().optional(),
379
+ chargedUsd: z.number().nullable().optional(),
380
+ discountUsd: z.number().nullable().optional(),
381
+ outputs: z.record(z.string(), z.array(runOutputItemSchema)),
382
+ unresolvedOutputs: z.array(z.object({ field: z.string(), reason: z.string() })).optional(),
383
+ steps: z.array(runStepSchema),
384
+ counts: z.object({
385
+ failed: z.number(),
386
+ aborted: z.number(),
387
+ cancelled: z.number(),
388
+ pending: z.number(),
389
+ running: z.number(),
390
+ completed: z.number(),
391
+ }),
392
+ });
393
+ export function isRunTerminal(status) {
394
+ return RUN_TERMINAL_STATUSES.includes(String(status));
395
+ }
396
+ function msToIso(value) {
397
+ const ms = toNumber(value);
398
+ return ms !== null && ms > 0 ? new Date(ms).toISOString() : undefined;
399
+ }
400
+ export function isStrictlyEmpty(value) {
401
+ if (value === null || value === undefined || value === '')
402
+ return true;
403
+ if (Array.isArray(value))
404
+ return value.length === 0;
405
+ if (isPlainObject(value))
406
+ return Object.keys(value).length === 0;
407
+ return false;
408
+ }
409
+ function normalizeOutputValue(value, out, depth = 0) {
410
+ if (depth > 6 || value === null || value === undefined)
411
+ return;
412
+ if (Array.isArray(value)) {
413
+ for (const entry of value)
414
+ normalizeOutputValue(entry, out, depth + 1);
415
+ return;
416
+ }
417
+ if (typeof value === 'string') {
418
+ if (/^https?:\/\//i.test(value.trim()))
419
+ out.push({ url: value.trim() });
420
+ else if (value)
421
+ out.push({ text: truncate(value, 20_000) });
422
+ return;
423
+ }
424
+ if (typeof value === 'number' || typeof value === 'boolean') {
425
+ out.push({ text: String(value) });
426
+ return;
427
+ }
428
+ if (!isPlainObject(value))
429
+ return;
430
+ if (Array.isArray(value.children) && value.children.length > 0) {
431
+ normalizeOutputValue(value.children, out, depth + 1);
432
+ return;
433
+ }
434
+ if (value.contenttype === 'raw' && isPlainObject(value.content)) {
435
+ const answer = value.content.answer;
436
+ const text = Array.isArray(answer)
437
+ ? answer.map(part => (typeof part === 'string' ? part : isPlainObject(part) ? asString(part.text) ?? '' : '')).join('')
438
+ : asString(answer) ?? '';
439
+ if (text)
440
+ out.push({ text: truncate(text, 20_000) });
441
+ return;
442
+ }
443
+ if (typeof value.url === 'string' && value.url) {
444
+ out.push({
445
+ url: value.url,
446
+ ...(asString(value.name) ? { name: asString(value.name) } : {}),
447
+ ...(asString(value.contenttype) ? { mimeType: asString(value.contenttype) } : {}),
448
+ sizeBytes: toNumber(value.size),
449
+ });
450
+ }
451
+ }
452
+ /**
453
+ * Resolves the End node's fields against the run's step outputs, following the
454
+ * run engine: '$node-X.outputs' is a model step's output list, '$node-X.<port>'
455
+ * a utility port, '$input.<key>' the run's Start value, anything else a literal.
456
+ */
457
+ export function resolveRunOutputs(contents, nodes) {
458
+ const outputs = {};
459
+ const unresolved = [];
460
+ const fields = workflowOutputs(contents);
461
+ const byId = new Map(nodes.map(node => [asString(node.nodeId) ?? '', node]));
462
+ const contentsObject = isPlainObject(contents) ? contents : {};
463
+ const schema = isPlainObject(contentsObject.schema) ? contentsObject.schema : {};
464
+ const schemaInput = isPlainObject(schema.input) ? schema.input : {};
465
+ const metadata = isPlainObject(contentsObject.metadata) ? contentsObject.metadata : {};
466
+ const metadataInput = isPlainObject(metadata.input) ? metadata.input : {};
467
+ const values = isPlainObject(metadataInput.values) ? metadataInput.values : {};
468
+ for (const [field, reference] of Object.entries(fields)) {
469
+ const references = Array.isArray(reference) ? reference : [reference];
470
+ const items = [];
471
+ for (const ref of references) {
472
+ if (typeof ref !== 'string' || !ref.startsWith('$')) {
473
+ normalizeOutputValue(ref, items);
474
+ continue;
475
+ }
476
+ if (ref.startsWith('$input.')) {
477
+ const key = ref.slice('$input.'.length);
478
+ const stored = Object.prototype.hasOwnProperty.call(values, key) ? values[key] : undefined;
479
+ const fieldDef = isPlainObject(schemaInput[key]) ? schemaInput[key] : {};
480
+ normalizeOutputValue(isStrictlyEmpty(stored) ? fieldDef.defaultValue : stored, items);
481
+ continue;
482
+ }
483
+ const match = /^\$(node-[^.]+)\.(.+)$/.exec(ref);
484
+ if (!match) {
485
+ unresolved.push({ field, reason: `Malformed reference ${ref}` });
486
+ continue;
487
+ }
488
+ const node = byId.get(match[1]);
489
+ if (!node) {
490
+ unresolved.push({ field, reason: `Step ${match[1]} is not part of this run's results` });
491
+ continue;
492
+ }
493
+ if (node.status !== 'completed') {
494
+ unresolved.push({ field, reason: `Step ${match[1]} is ${String(node.status)}` });
495
+ continue;
496
+ }
497
+ const stepOutputs = node.outputs;
498
+ if (match[2] === 'outputs' && Array.isArray(stepOutputs)) {
499
+ normalizeOutputValue(stepOutputs, items);
500
+ }
501
+ else if (isPlainObject(stepOutputs) && Object.prototype.hasOwnProperty.call(stepOutputs, match[2])) {
502
+ normalizeOutputValue(stepOutputs[match[2]], items);
503
+ }
504
+ else {
505
+ unresolved.push({ field, reason: `Step ${match[1]} has no output "${match[2]}"` });
506
+ }
507
+ }
508
+ outputs[field] = items;
509
+ }
510
+ return { outputs, unresolved };
511
+ }
512
+ export function toWorkflowRun(run, nodes) {
513
+ const status = asString(run.status) ?? 'running';
514
+ const terminal = isRunTerminal(status);
515
+ const resolved = terminal ? resolveRunOutputs(run.contents, nodes) : { outputs: {}, unresolved: [] };
516
+ const counts = { failed: 0, aborted: 0, cancelled: 0, pending: 0, running: 0, completed: 0 };
517
+ const steps = [];
518
+ for (const node of nodes) {
519
+ const nodeStatus = asString(node.status) ?? 'pending';
520
+ if (nodeStatus in counts)
521
+ counts[nodeStatus] += 1;
522
+ if (nodeStatus === 'completed')
523
+ continue;
524
+ const error = asString(node.error);
525
+ steps.push({
526
+ nodeId: asString(node.nodeId) ?? '',
527
+ ...(asString(node.tool) ? { tool: asString(node.tool) } : {}),
528
+ ...(asString(node.kind) ? { kind: asString(node.kind) } : {}),
529
+ status: nodeStatus,
530
+ ...(asString(node.cause) ? { cause: asString(node.cause) } : {}),
531
+ ...(error ? { error: truncate(error, 500) } : {}),
532
+ });
533
+ }
534
+ return {
535
+ workflowtoken: asString(run.workflowtoken) ?? '',
536
+ ...(asString(run.workflowslug) ? { workflowSlug: asString(run.workflowslug) } : {}),
537
+ status,
538
+ ...(asString(run.outcome) ? { outcome: asString(run.outcome) } : {}),
539
+ canResume: run.canresume === true,
540
+ ...(run.graphmasked === true ? { graphMasked: true } : {}),
541
+ ...(run.breakpointnodeid !== undefined ? { breakpointNodeId: asString(run.breakpointnodeid) ?? null } : {}),
542
+ ...(msToIso(run.startedAt) ? { startedAt: msToIso(run.startedAt) } : {}),
543
+ ...(msToIso(run.resumedAt) ? { resumedAt: msToIso(run.resumedAt) } : {}),
544
+ ...(msToIso(run.endedAt) ? { endedAt: msToIso(run.endedAt) } : {}),
545
+ ...(toNumber(run.elapsedseconds) !== null ? { elapsedSeconds: toNumber(run.elapsedseconds) } : {}),
546
+ ...(run.totalcost !== undefined ? { listPriceUsd: toNumber(run.totalcost) } : {}),
547
+ ...(run.chargedcost !== undefined ? { chargedUsd: toNumber(run.chargedcost) } : {}),
548
+ ...(run.discountamount !== undefined ? { discountUsd: toNumber(run.discountamount) } : {}),
549
+ outputs: resolved.outputs,
550
+ ...(resolved.unresolved.length > 0 ? { unresolvedOutputs: resolved.unresolved } : {}),
551
+ steps,
552
+ counts,
553
+ };
554
+ }
555
+ export function runMediaItems(run) {
556
+ const items = [];
557
+ for (const [field, values] of Object.entries(run.outputs)) {
558
+ for (const value of values) {
559
+ if (value.url)
560
+ items.push({ url: value.url, name: value.name || field, ...(value.mimeType ? { mimeType: value.mimeType } : {}) });
561
+ }
562
+ }
563
+ return items;
564
+ }
565
+ export function formatWorkflowRun(run) {
566
+ const lines = ['## Workflow Run', ''];
567
+ lines.push(`**Status:** ${run.status}`);
568
+ lines.push(`**Run token:** \`${run.workflowtoken}\``);
569
+ if (run.workflowSlug)
570
+ lines.push(`**Workflow:** ${workflowUrl(run.workflowSlug)}`);
571
+ if (run.elapsedSeconds !== undefined)
572
+ lines.push(`**Duration:** ${run.elapsedSeconds}s`);
573
+ if (run.chargedUsd !== undefined && run.chargedUsd !== null) {
574
+ lines.push(`**Charged:** $${run.chargedUsd}`);
575
+ }
576
+ else if (run.listPriceUsd !== undefined && run.listPriceUsd !== null) {
577
+ lines.push(`**Cost so far (list price):** $${run.listPriceUsd}`);
578
+ }
579
+ const fields = Object.entries(run.outputs);
580
+ if (fields.length > 0) {
581
+ lines.push('', '### Outputs', '');
582
+ for (const [field, values] of fields) {
583
+ if (values.length === 0) {
584
+ lines.push(`- **${field}:** (empty)`);
585
+ continue;
586
+ }
587
+ for (const value of values) {
588
+ if (value.url)
589
+ lines.push(`- **${field}:** ${value.url}`);
590
+ else if (value.text)
591
+ lines.push(`- **${field}:** ${truncate(value.text, 2_000)}`);
592
+ }
593
+ }
594
+ }
595
+ for (const missing of run.unresolvedOutputs ?? []) {
596
+ lines.push(`- **${missing.field}:** not available (${missing.reason})`);
597
+ }
598
+ const problems = run.steps.filter(step => step.status === 'failed' || step.status === 'aborted' || step.status === 'cancelled');
599
+ if (problems.length > 0) {
600
+ lines.push('', '### Steps that did not finish', '');
601
+ for (const step of problems) {
602
+ const reason = step.status === 'aborted'
603
+ ? 'skipped because an earlier step failed'
604
+ : step.error || step.cause || step.status;
605
+ lines.push(`- \`${step.nodeId}\`${step.tool ? ` (${step.tool})` : ''}: ${step.status}, ${reason}`);
606
+ }
607
+ }
608
+ if (run.canResume) {
609
+ lines.push('', 'This run can be resumed with `resume_workflow_run`. Completed steps are kept and not charged again.');
610
+ }
611
+ return lines.join('\n');
612
+ }
613
+ //# sourceMappingURL=workflow.js.map