@elizaos/plugin-workflow 2.0.0-beta.1

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 (294) hide show
  1. package/README.md +71 -0
  2. package/auto-enable.ts +18 -0
  3. package/dist/actions/index.d.ts +2 -0
  4. package/dist/actions/index.d.ts.map +1 -0
  5. package/dist/actions/index.js +2 -0
  6. package/dist/actions/index.js.map +1 -0
  7. package/dist/actions/workflow.d.ts +23 -0
  8. package/dist/actions/workflow.d.ts.map +1 -0
  9. package/dist/actions/workflow.js +425 -0
  10. package/dist/actions/workflow.js.map +1 -0
  11. package/dist/data/defaultNodes.json +9887 -0
  12. package/dist/data/schemaIndex.json +1 -0
  13. package/dist/data/triggerSchemaIndex.json +1 -0
  14. package/dist/db/index.d.ts +2 -0
  15. package/dist/db/index.d.ts.map +1 -0
  16. package/dist/db/index.js +2 -0
  17. package/dist/db/index.js.map +1 -0
  18. package/dist/db/schema.d.ts +588 -0
  19. package/dist/db/schema.d.ts.map +1 -0
  20. package/dist/db/schema.js +59 -0
  21. package/dist/db/schema.js.map +1 -0
  22. package/dist/index.d.ts +34 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +126 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/lib/automations-builder.d.ts +21 -0
  27. package/dist/lib/automations-builder.d.ts.map +1 -0
  28. package/dist/lib/automations-builder.js +557 -0
  29. package/dist/lib/automations-builder.js.map +1 -0
  30. package/dist/lib/automations-types.d.ts +153 -0
  31. package/dist/lib/automations-types.d.ts.map +1 -0
  32. package/dist/lib/automations-types.js +191 -0
  33. package/dist/lib/automations-types.js.map +1 -0
  34. package/dist/lib/index.d.ts +3 -0
  35. package/dist/lib/index.d.ts.map +1 -0
  36. package/dist/lib/index.js +3 -0
  37. package/dist/lib/index.js.map +1 -0
  38. package/dist/lib/legacy-task-migration.d.ts +20 -0
  39. package/dist/lib/legacy-task-migration.d.ts.map +1 -0
  40. package/dist/lib/legacy-task-migration.js +110 -0
  41. package/dist/lib/legacy-task-migration.js.map +1 -0
  42. package/dist/lib/legacy-text-trigger-migration.d.ts +18 -0
  43. package/dist/lib/legacy-text-trigger-migration.d.ts.map +1 -0
  44. package/dist/lib/legacy-text-trigger-migration.js +131 -0
  45. package/dist/lib/legacy-text-trigger-migration.js.map +1 -0
  46. package/dist/lib/workflow-clarification.d.ts +113 -0
  47. package/dist/lib/workflow-clarification.d.ts.map +1 -0
  48. package/dist/lib/workflow-clarification.js +425 -0
  49. package/dist/lib/workflow-clarification.js.map +1 -0
  50. package/dist/plugin-routes.d.ts +9 -0
  51. package/dist/plugin-routes.d.ts.map +1 -0
  52. package/dist/plugin-routes.js +147 -0
  53. package/dist/plugin-routes.js.map +1 -0
  54. package/dist/providers/activeWorkflows.d.ts +11 -0
  55. package/dist/providers/activeWorkflows.d.ts.map +1 -0
  56. package/dist/providers/activeWorkflows.js +72 -0
  57. package/dist/providers/activeWorkflows.js.map +1 -0
  58. package/dist/providers/index.d.ts +4 -0
  59. package/dist/providers/index.d.ts.map +1 -0
  60. package/dist/providers/index.js +4 -0
  61. package/dist/providers/index.js.map +1 -0
  62. package/dist/providers/pendingDraft.d.ts +9 -0
  63. package/dist/providers/pendingDraft.d.ts.map +1 -0
  64. package/dist/providers/pendingDraft.js +48 -0
  65. package/dist/providers/pendingDraft.js.map +1 -0
  66. package/dist/providers/workflowStatus.d.ts +3 -0
  67. package/dist/providers/workflowStatus.d.ts.map +1 -0
  68. package/dist/providers/workflowStatus.js +69 -0
  69. package/dist/providers/workflowStatus.js.map +1 -0
  70. package/dist/register-routes.d.ts +2 -0
  71. package/dist/register-routes.d.ts.map +1 -0
  72. package/dist/register-routes.js +6 -0
  73. package/dist/register-routes.js.map +1 -0
  74. package/dist/routes/_helpers.d.ts +11 -0
  75. package/dist/routes/_helpers.d.ts.map +1 -0
  76. package/dist/routes/_helpers.js +22 -0
  77. package/dist/routes/_helpers.js.map +1 -0
  78. package/dist/routes/automations.d.ts +19 -0
  79. package/dist/routes/automations.d.ts.map +1 -0
  80. package/dist/routes/automations.js +32 -0
  81. package/dist/routes/automations.js.map +1 -0
  82. package/dist/routes/embedded-webhooks.d.ts +3 -0
  83. package/dist/routes/embedded-webhooks.d.ts.map +1 -0
  84. package/dist/routes/embedded-webhooks.js +47 -0
  85. package/dist/routes/embedded-webhooks.js.map +1 -0
  86. package/dist/routes/executions.d.ts +3 -0
  87. package/dist/routes/executions.d.ts.map +1 -0
  88. package/dist/routes/executions.js +58 -0
  89. package/dist/routes/executions.js.map +1 -0
  90. package/dist/routes/index.d.ts +4 -0
  91. package/dist/routes/index.d.ts.map +1 -0
  92. package/dist/routes/index.js +14 -0
  93. package/dist/routes/index.js.map +1 -0
  94. package/dist/routes/nodes.d.ts +3 -0
  95. package/dist/routes/nodes.d.ts.map +1 -0
  96. package/dist/routes/nodes.js +168 -0
  97. package/dist/routes/nodes.js.map +1 -0
  98. package/dist/routes/validation.d.ts +3 -0
  99. package/dist/routes/validation.d.ts.map +1 -0
  100. package/dist/routes/validation.js +41 -0
  101. package/dist/routes/validation.js.map +1 -0
  102. package/dist/routes/workflow-routes.d.ts +27 -0
  103. package/dist/routes/workflow-routes.d.ts.map +1 -0
  104. package/dist/routes/workflow-routes.js +326 -0
  105. package/dist/routes/workflow-routes.js.map +1 -0
  106. package/dist/routes/workflows.d.ts +3 -0
  107. package/dist/routes/workflows.d.ts.map +1 -0
  108. package/dist/routes/workflows.js +252 -0
  109. package/dist/routes/workflows.js.map +1 -0
  110. package/dist/schemas/draftIntent.d.ts +22 -0
  111. package/dist/schemas/draftIntent.d.ts.map +1 -0
  112. package/dist/schemas/draftIntent.js +22 -0
  113. package/dist/schemas/draftIntent.js.map +1 -0
  114. package/dist/schemas/feasibility.d.ts +13 -0
  115. package/dist/schemas/feasibility.d.ts.map +1 -0
  116. package/dist/schemas/feasibility.js +9 -0
  117. package/dist/schemas/feasibility.js.map +1 -0
  118. package/dist/schemas/index.d.ts +5 -0
  119. package/dist/schemas/index.d.ts.map +1 -0
  120. package/dist/schemas/index.js +5 -0
  121. package/dist/schemas/index.js.map +1 -0
  122. package/dist/schemas/keywordExtraction.d.ts +14 -0
  123. package/dist/schemas/keywordExtraction.d.ts.map +1 -0
  124. package/dist/schemas/keywordExtraction.js +12 -0
  125. package/dist/schemas/keywordExtraction.js.map +1 -0
  126. package/dist/schemas/workflowMatching.d.ts +36 -0
  127. package/dist/schemas/workflowMatching.d.ts.map +1 -0
  128. package/dist/schemas/workflowMatching.js +30 -0
  129. package/dist/schemas/workflowMatching.js.map +1 -0
  130. package/dist/services/embedded-workflow-service.d.ts +106 -0
  131. package/dist/services/embedded-workflow-service.d.ts.map +1 -0
  132. package/dist/services/embedded-workflow-service.js +1900 -0
  133. package/dist/services/embedded-workflow-service.js.map +1 -0
  134. package/dist/services/index.d.ts +5 -0
  135. package/dist/services/index.d.ts.map +1 -0
  136. package/dist/services/index.js +5 -0
  137. package/dist/services/index.js.map +1 -0
  138. package/dist/services/workflow-credential-store.d.ts +27 -0
  139. package/dist/services/workflow-credential-store.d.ts.map +1 -0
  140. package/dist/services/workflow-credential-store.js +92 -0
  141. package/dist/services/workflow-credential-store.js.map +1 -0
  142. package/dist/services/workflow-dispatch.d.ts +41 -0
  143. package/dist/services/workflow-dispatch.d.ts.map +1 -0
  144. package/dist/services/workflow-dispatch.js +86 -0
  145. package/dist/services/workflow-dispatch.js.map +1 -0
  146. package/dist/services/workflow-service.d.ts +63 -0
  147. package/dist/services/workflow-service.d.ts.map +1 -0
  148. package/dist/services/workflow-service.js +492 -0
  149. package/dist/services/workflow-service.js.map +1 -0
  150. package/dist/trigger-routes.d.ts +153 -0
  151. package/dist/trigger-routes.d.ts.map +1 -0
  152. package/dist/trigger-routes.js +424 -0
  153. package/dist/trigger-routes.js.map +1 -0
  154. package/dist/types/index.d.ts +457 -0
  155. package/dist/types/index.d.ts.map +1 -0
  156. package/dist/types/index.js +59 -0
  157. package/dist/types/index.js.map +1 -0
  158. package/dist/utils/catalog.d.ts +16 -0
  159. package/dist/utils/catalog.d.ts.map +1 -0
  160. package/dist/utils/catalog.js +211 -0
  161. package/dist/utils/catalog.js.map +1 -0
  162. package/dist/utils/clarification.d.ts +17 -0
  163. package/dist/utils/clarification.d.ts.map +1 -0
  164. package/dist/utils/clarification.js +46 -0
  165. package/dist/utils/clarification.js.map +1 -0
  166. package/dist/utils/context.d.ts +4 -0
  167. package/dist/utils/context.d.ts.map +1 -0
  168. package/dist/utils/context.js +18 -0
  169. package/dist/utils/context.js.map +1 -0
  170. package/dist/utils/credentialResolver.d.ts +22 -0
  171. package/dist/utils/credentialResolver.d.ts.map +1 -0
  172. package/dist/utils/credentialResolver.js +146 -0
  173. package/dist/utils/credentialResolver.js.map +1 -0
  174. package/dist/utils/generation.d.ts +36 -0
  175. package/dist/utils/generation.d.ts.map +1 -0
  176. package/dist/utils/generation.js +701 -0
  177. package/dist/utils/generation.js.map +1 -0
  178. package/dist/utils/host-capabilities.d.ts +27 -0
  179. package/dist/utils/host-capabilities.d.ts.map +1 -0
  180. package/dist/utils/host-capabilities.js +59 -0
  181. package/dist/utils/host-capabilities.js.map +1 -0
  182. package/dist/utils/inferSyntheticOutputSchema.d.ts +20 -0
  183. package/dist/utils/inferSyntheticOutputSchema.d.ts.map +1 -0
  184. package/dist/utils/inferSyntheticOutputSchema.js +151 -0
  185. package/dist/utils/inferSyntheticOutputSchema.js.map +1 -0
  186. package/dist/utils/outputSchema.d.ts +26 -0
  187. package/dist/utils/outputSchema.d.ts.map +1 -0
  188. package/dist/utils/outputSchema.js +297 -0
  189. package/dist/utils/outputSchema.js.map +1 -0
  190. package/dist/utils/validateAndRepair.d.ts +41 -0
  191. package/dist/utils/validateAndRepair.d.ts.map +1 -0
  192. package/dist/utils/validateAndRepair.js +483 -0
  193. package/dist/utils/validateAndRepair.js.map +1 -0
  194. package/dist/utils/workflow-prompts/actionResponse.d.ts +2 -0
  195. package/dist/utils/workflow-prompts/actionResponse.d.ts.map +1 -0
  196. package/dist/utils/workflow-prompts/actionResponse.js +17 -0
  197. package/dist/utils/workflow-prompts/actionResponse.js.map +1 -0
  198. package/dist/utils/workflow-prompts/draftIntent.d.ts +2 -0
  199. package/dist/utils/workflow-prompts/draftIntent.d.ts.map +1 -0
  200. package/dist/utils/workflow-prompts/draftIntent.js +23 -0
  201. package/dist/utils/workflow-prompts/draftIntent.js.map +1 -0
  202. package/dist/utils/workflow-prompts/feasibilityCheck.d.ts +2 -0
  203. package/dist/utils/workflow-prompts/feasibilityCheck.d.ts.map +1 -0
  204. package/dist/utils/workflow-prompts/feasibilityCheck.js +21 -0
  205. package/dist/utils/workflow-prompts/feasibilityCheck.js.map +1 -0
  206. package/dist/utils/workflow-prompts/fieldCorrection.d.ts +3 -0
  207. package/dist/utils/workflow-prompts/fieldCorrection.d.ts.map +1 -0
  208. package/dist/utils/workflow-prompts/fieldCorrection.js +20 -0
  209. package/dist/utils/workflow-prompts/fieldCorrection.js.map +1 -0
  210. package/dist/utils/workflow-prompts/index.d.ts +8 -0
  211. package/dist/utils/workflow-prompts/index.d.ts.map +1 -0
  212. package/dist/utils/workflow-prompts/index.js +8 -0
  213. package/dist/utils/workflow-prompts/index.js.map +1 -0
  214. package/dist/utils/workflow-prompts/keywordExtraction.d.ts +2 -0
  215. package/dist/utils/workflow-prompts/keywordExtraction.d.ts.map +1 -0
  216. package/dist/utils/workflow-prompts/keywordExtraction.js +21 -0
  217. package/dist/utils/workflow-prompts/keywordExtraction.js.map +1 -0
  218. package/dist/utils/workflow-prompts/parameterCorrection.d.ts +3 -0
  219. package/dist/utils/workflow-prompts/parameterCorrection.d.ts.map +1 -0
  220. package/dist/utils/workflow-prompts/parameterCorrection.js +29 -0
  221. package/dist/utils/workflow-prompts/parameterCorrection.js.map +1 -0
  222. package/dist/utils/workflow-prompts/workflowGeneration.d.ts +2 -0
  223. package/dist/utils/workflow-prompts/workflowGeneration.d.ts.map +1 -0
  224. package/dist/utils/workflow-prompts/workflowGeneration.js +529 -0
  225. package/dist/utils/workflow-prompts/workflowGeneration.js.map +1 -0
  226. package/dist/utils/workflow-prompts/workflowMatching.d.ts +2 -0
  227. package/dist/utils/workflow-prompts/workflowMatching.d.ts.map +1 -0
  228. package/dist/utils/workflow-prompts/workflowMatching.js +23 -0
  229. package/dist/utils/workflow-prompts/workflowMatching.js.map +1 -0
  230. package/dist/utils/workflow.d.ts +62 -0
  231. package/dist/utils/workflow.d.ts.map +1 -0
  232. package/dist/utils/workflow.js +712 -0
  233. package/dist/utils/workflow.js.map +1 -0
  234. package/package.json +87 -0
  235. package/src/actions/index.ts +1 -0
  236. package/src/actions/workflow.ts +494 -0
  237. package/src/data/defaultNodes.json +9887 -0
  238. package/src/data/schemaIndex.json +1 -0
  239. package/src/data/triggerSchemaIndex.json +1 -0
  240. package/src/db/index.ts +8 -0
  241. package/src/db/schema.ts +94 -0
  242. package/src/index.ts +179 -0
  243. package/src/lib/automations-builder.ts +679 -0
  244. package/src/lib/automations-types.ts +391 -0
  245. package/src/lib/index.ts +8 -0
  246. package/src/lib/legacy-task-migration.ts +143 -0
  247. package/src/lib/legacy-text-trigger-migration.ts +178 -0
  248. package/src/lib/workflow-clarification.ts +497 -0
  249. package/src/plugin-routes.ts +164 -0
  250. package/src/providers/activeWorkflows.ts +81 -0
  251. package/src/providers/index.ts +3 -0
  252. package/src/providers/pendingDraft.ts +55 -0
  253. package/src/providers/workflowStatus.ts +88 -0
  254. package/src/register-routes.ts +6 -0
  255. package/src/routes/_helpers.ts +27 -0
  256. package/src/routes/automations.ts +46 -0
  257. package/src/routes/embedded-webhooks.ts +64 -0
  258. package/src/routes/executions.ts +75 -0
  259. package/src/routes/index.ts +16 -0
  260. package/src/routes/nodes.ts +211 -0
  261. package/src/routes/validation.ts +51 -0
  262. package/src/routes/workflow-routes.ts +469 -0
  263. package/src/routes/workflows.ts +310 -0
  264. package/src/schemas/draftIntent.ts +21 -0
  265. package/src/schemas/feasibility.ts +8 -0
  266. package/src/schemas/index.ts +4 -0
  267. package/src/schemas/keywordExtraction.ts +11 -0
  268. package/src/schemas/workflowMatching.ts +29 -0
  269. package/src/services/embedded-workflow-service.ts +2224 -0
  270. package/src/services/index.ts +17 -0
  271. package/src/services/workflow-credential-store.ts +132 -0
  272. package/src/services/workflow-dispatch.ts +121 -0
  273. package/src/services/workflow-service.ts +839 -0
  274. package/src/trigger-routes.ts +714 -0
  275. package/src/types/index.ts +562 -0
  276. package/src/utils/catalog.ts +260 -0
  277. package/src/utils/clarification.ts +52 -0
  278. package/src/utils/context.ts +22 -0
  279. package/src/utils/credentialResolver.ts +234 -0
  280. package/src/utils/generation.ts +987 -0
  281. package/src/utils/host-capabilities.ts +81 -0
  282. package/src/utils/inferSyntheticOutputSchema.ts +163 -0
  283. package/src/utils/outputSchema.ts +372 -0
  284. package/src/utils/validateAndRepair.ts +610 -0
  285. package/src/utils/workflow-prompts/actionResponse.ts +16 -0
  286. package/src/utils/workflow-prompts/draftIntent.ts +22 -0
  287. package/src/utils/workflow-prompts/feasibilityCheck.ts +20 -0
  288. package/src/utils/workflow-prompts/fieldCorrection.ts +20 -0
  289. package/src/utils/workflow-prompts/index.ts +10 -0
  290. package/src/utils/workflow-prompts/keywordExtraction.ts +20 -0
  291. package/src/utils/workflow-prompts/parameterCorrection.ts +29 -0
  292. package/src/utils/workflow-prompts/workflowGeneration.ts +528 -0
  293. package/src/utils/workflow-prompts/workflowMatching.ts +22 -0
  294. package/src/utils/workflow.ts +895 -0
@@ -0,0 +1,839 @@
1
+ import { type IAgentRuntime, logger, Service } from '@elizaos/core';
2
+ import type {
3
+ NodeDefinition,
4
+ RuntimeContext,
5
+ TriggerContext,
6
+ WorkflowCreationResult,
7
+ WorkflowCredentialStoreApi,
8
+ WorkflowDefinition,
9
+ WorkflowDefinitionResponse,
10
+ WorkflowExecution,
11
+ } from '../types/index';
12
+ import {
13
+ isCredentialProvider,
14
+ isRuntimeContextProvider,
15
+ UnsupportedIntegrationError,
16
+ WORKFLOW_CREDENTIAL_PROVIDER_TYPE,
17
+ WORKFLOW_CREDENTIAL_STORE_TYPE,
18
+ WORKFLOW_RUNTIME_CONTEXT_PROVIDER_TYPE,
19
+ WorkflowApiError,
20
+ } from '../types/index';
21
+ import { filterNodesByIntegrationSupport, searchNodes } from '../utils/catalog';
22
+ import { CATALOG_CLARIFICATION_SUFFIX, isCatalogClarification } from '../utils/clarification';
23
+ import { getUserTagName } from '../utils/context';
24
+ import { resolveCredentials } from '../utils/credentialResolver';
25
+ import {
26
+ assessFeasibility,
27
+ collectExistingNodeDefinitions,
28
+ correctFieldReferences,
29
+ correctParameterNames,
30
+ extractKeywords,
31
+ fixWorkflowErrors,
32
+ generateWorkflow,
33
+ modifyWorkflow,
34
+ } from '../utils/generation';
35
+ import { validateAndRepair } from '../utils/validateAndRepair';
36
+ import {
37
+ correctOptionParameters,
38
+ detectUnknownParameters,
39
+ ensureExpressionPrefix,
40
+ injectMissingCredentialBlocks,
41
+ normalizeTriggerSimpleParam,
42
+ positionNodes,
43
+ validateNodeInputs,
44
+ validateNodeParameters,
45
+ validateOutputReferences,
46
+ validateWorkflow,
47
+ } from '../utils/workflow';
48
+ import {
49
+ EMBEDDED_WORKFLOW_SERVICE_TYPE,
50
+ EmbeddedWorkflowService,
51
+ } from './embedded-workflow-service';
52
+
53
+ export const WORKFLOW_SERVICE_TYPE = 'workflow';
54
+
55
+ export interface WorkflowServiceConfig {
56
+ apiKey: 'embedded';
57
+ host: 'in-process';
58
+ backend: 'embedded';
59
+ credentials?: Record<string, string>; // Pre-configured credential IDs
60
+ }
61
+
62
+ type WorkflowDefinitionClient = Pick<
63
+ EmbeddedWorkflowService,
64
+ | 'createWorkflow'
65
+ | 'listWorkflows'
66
+ | 'getWorkflow'
67
+ | 'updateWorkflow'
68
+ | 'deleteWorkflow'
69
+ | 'activateWorkflow'
70
+ | 'deactivateWorkflow'
71
+ | 'updateWorkflowTags'
72
+ | 'createCredential'
73
+ | 'listExecutions'
74
+ | 'getExecution'
75
+ | 'deleteExecution'
76
+ | 'listTags'
77
+ | 'createTag'
78
+ | 'getOrCreateTag'
79
+ > & {
80
+ getRuntimeNodeTypeVersions():
81
+ | Promise<Map<string, number[]> | null>
82
+ | Map<string, number[]>
83
+ | null;
84
+ getRegisteredNodeTypes?(): string[];
85
+ supportsWorkflow?(workflow: WorkflowDefinition): { supported: boolean; missing: string[] };
86
+ };
87
+
88
+ function isWorkflowCredentialStoreApi(service: unknown): service is WorkflowCredentialStoreApi {
89
+ return (
90
+ service !== null &&
91
+ typeof service === 'object' &&
92
+ typeof (service as { get?: unknown }).get === 'function' &&
93
+ typeof (service as { set?: unknown }).set === 'function'
94
+ );
95
+ }
96
+
97
+ /**
98
+ * Workflow Service - Orchestrates the RAG pipeline for workflow generation.
99
+ *
100
+ * generateWorkflowDraft(): keywords → node search → LLM generation → validation → positioning
101
+ * deployWorkflow(): credential resolution → in-process runtime → tagging
102
+ */
103
+ export class WorkflowService extends Service {
104
+ static override readonly serviceType = WORKFLOW_SERVICE_TYPE;
105
+
106
+ override capabilityDescription =
107
+ 'Generate and deploy workflows from natural language using RAG pipeline. ' +
108
+ 'Supports workflow CRUD, execution management, and credential resolution.';
109
+
110
+ private apiClient: WorkflowDefinitionClient | null = null;
111
+ private serviceConfig: WorkflowServiceConfig | null = null;
112
+
113
+ static async start(runtime: IAgentRuntime): Promise<WorkflowService> {
114
+ logger.info({ src: 'plugin:workflow:service:main' }, 'Starting Workflow Service...');
115
+
116
+ // Get optional pre-configured credentials from character.settings.workflows
117
+ // Note: runtime.getSetting() only returns primitives — nested objects must be read directly
118
+ const workflowSettings = runtime.character?.settings?.workflows as
119
+ | { credentials?: Record<string, string> }
120
+ | undefined;
121
+ const credentials = workflowSettings?.credentials;
122
+
123
+ const service = new WorkflowService(runtime);
124
+ const embedded =
125
+ (runtime.getService(EMBEDDED_WORKFLOW_SERVICE_TYPE) as EmbeddedWorkflowService | null) ??
126
+ (await EmbeddedWorkflowService.start(runtime));
127
+ service.serviceConfig = {
128
+ apiKey: 'embedded',
129
+ host: 'in-process',
130
+ backend: 'embedded',
131
+ credentials,
132
+ };
133
+ service.apiClient = embedded;
134
+
135
+ logger.info(
136
+ { src: 'plugin:workflow:service:main' },
137
+ `Workflow Service started - connected to ${service.serviceConfig.host}`
138
+ );
139
+ if (credentials) {
140
+ const configured = Object.entries(credentials)
141
+ .filter(([, v]) => v)
142
+ .map(([k]) => k);
143
+ if (configured.length > 0) {
144
+ logger.info(
145
+ { src: 'plugin:workflow:service:main' },
146
+ `Pre-configured credentials: ${configured.join(', ')}`
147
+ );
148
+ }
149
+ }
150
+
151
+ return service;
152
+ }
153
+
154
+ override async stop(): Promise<void> {
155
+ logger.info({ src: 'plugin:workflow:service:main' }, 'Stopping Workflow Service...');
156
+ this.apiClient = null;
157
+ this.serviceConfig = null;
158
+ logger.info({ src: 'plugin:workflow:service:main' }, 'Workflow Service stopped');
159
+ }
160
+
161
+ private filterForEmbeddedBackend<T extends { node: NodeDefinition }>(results: T[]): T[] {
162
+ if (this.serviceConfig?.backend !== 'embedded') {
163
+ return results;
164
+ }
165
+ const registered = this.apiClient?.getRegisteredNodeTypes?.();
166
+ if (!registered?.length) {
167
+ return results;
168
+ }
169
+ const registeredSet = new Set(registered);
170
+ return results.filter((result) => registeredSet.has(result.node.name));
171
+ }
172
+
173
+ private resolveDeployTarget(workflow: WorkflowDefinition): {
174
+ client: WorkflowDefinitionClient;
175
+ config: WorkflowServiceConfig;
176
+ routedToFallback: boolean;
177
+ } {
178
+ const client = this.getClient();
179
+ const config = this.getConfig();
180
+ if (config.backend !== 'embedded') {
181
+ return { client, config, routedToFallback: false };
182
+ }
183
+
184
+ const support = client.supportsWorkflow?.(workflow);
185
+ if (!support || support.supported) {
186
+ return { client, config, routedToFallback: false };
187
+ }
188
+
189
+ throw new WorkflowApiError(
190
+ `Embedded workflow runtime does not support node type(s): ${support.missing.join(', ')}`,
191
+ 400
192
+ );
193
+ }
194
+
195
+ private injectCatalogClarifications(workflow: WorkflowDefinition): void {
196
+ const paramWarnings = validateNodeParameters(workflow);
197
+ const inputWarnings = validateNodeInputs(workflow);
198
+ const catalogWarnings = [...paramWarnings, ...inputWarnings];
199
+
200
+ if (!workflow._meta) {
201
+ workflow._meta = {};
202
+ }
203
+
204
+ // Strip previous catalog-derived clarifications to avoid stale duplicates
205
+ // across regeneration cycles (generate → modify → modify). Mixed-shape
206
+ // arrays (legacy strings + structured ClarificationRequest) are both
207
+ // supported via isCatalogClarification.
208
+ const nonCatalog = (workflow._meta.requiresClarification || []).filter(
209
+ (c) => !isCatalogClarification(c)
210
+ );
211
+
212
+ if (catalogWarnings.length > 0) {
213
+ logger.warn(
214
+ { src: 'plugin:workflow:service:main' },
215
+ `Catalog validation: ${catalogWarnings.join(', ')}`
216
+ );
217
+ const clarifications = catalogWarnings.map((w) => `${w} ${CATALOG_CLARIFICATION_SUFFIX}`);
218
+ workflow._meta.requiresClarification = [...nonCatalog, ...clarifications];
219
+ } else {
220
+ workflow._meta.requiresClarification = nonCatalog.length > 0 ? nonCatalog : undefined;
221
+ }
222
+ }
223
+
224
+ private getClient(): WorkflowDefinitionClient {
225
+ if (!this.apiClient) {
226
+ throw new Error('Workflow Service not initialized');
227
+ }
228
+ return this.apiClient;
229
+ }
230
+
231
+ private getConfig(): WorkflowServiceConfig {
232
+ if (!this.serviceConfig) {
233
+ throw new Error('Workflow Service not initialized');
234
+ }
235
+ return this.serviceConfig;
236
+ }
237
+
238
+ /**
239
+ * Query the optional `workflow_runtime_context_provider` service for runtime
240
+ * facts to inject into the workflow-generation prompt. The host runtime
241
+ * uses this to surface real Discord guild/channel IDs, the user's Gmail
242
+ * email, and which credential types it can resolve. Returns `undefined`
243
+ * when no provider is registered or the call throws — generation proceeds
244
+ * with the baseline prompt.
245
+ */
246
+ private async fetchRuntimeContext(
247
+ nodeDefs: NodeDefinition[],
248
+ userId: string,
249
+ triggerContext?: TriggerContext
250
+ ): Promise<RuntimeContext | undefined> {
251
+ const raw = this.runtime.getService(WORKFLOW_RUNTIME_CONTEXT_PROVIDER_TYPE);
252
+ const provider = isRuntimeContextProvider(raw) ? raw : null;
253
+ if (!provider) {
254
+ return undefined;
255
+ }
256
+ const relevantCredTypes = [
257
+ ...new Set(nodeDefs.flatMap((n) => (n.credentials ?? []).map((c) => c.name))),
258
+ ];
259
+ try {
260
+ return await provider.getRuntimeContext({
261
+ userId,
262
+ relevantNodes: nodeDefs,
263
+ relevantCredTypes,
264
+ ...(triggerContext ? { triggerContext } : {}),
265
+ });
266
+ } catch (err) {
267
+ logger.warn(
268
+ {
269
+ src: 'plugin:workflow:service:main',
270
+ err: err instanceof Error ? err.message : String(err),
271
+ },
272
+ 'RuntimeContextProvider threw — generating without runtime facts'
273
+ );
274
+ return undefined;
275
+ }
276
+ }
277
+
278
+ async generateWorkflowDraft(
279
+ prompt: string,
280
+ opts?: { userId?: string; triggerContext?: TriggerContext }
281
+ ): Promise<WorkflowDefinition> {
282
+ logger.info({ src: 'plugin:workflow:service:main' }, 'Generating workflow draft from prompt');
283
+
284
+ // Fetch host-supplied bias hints early (before keyword extraction) so the
285
+ // LLM is told which providers the host already knows it can satisfy.
286
+ // We pass empty `relevantNodes` / `relevantCredTypes` here because we do
287
+ // not yet have searchNodes results — `preferredProviders` is derived from
288
+ // the host's connector config alone (independent of node search). The
289
+ // full runtime context (with credentials + facts) is fetched again later
290
+ // once we have the filtered node list.
291
+ const earlyContext = await this.fetchRuntimeContext([], opts?.userId ?? 'local');
292
+ const preferredProviders = earlyContext?.preferredProviders;
293
+
294
+ const keywords = await extractKeywords(this.runtime, prompt, preferredProviders);
295
+ logger.debug(
296
+ { src: 'plugin:workflow:service:main' },
297
+ `Extracted keywords: ${keywords.join(', ')}${preferredProviders?.length ? ` (with bias: ${preferredProviders.join(', ')})` : ''}`
298
+ );
299
+
300
+ let relevantNodes = this.filterForEmbeddedBackend(searchNodes(keywords, 15));
301
+ logger.debug(
302
+ { src: 'plugin:workflow:service:main' },
303
+ `Found ${relevantNodes.length} relevant nodes`
304
+ );
305
+
306
+ if (relevantNodes.length === 0) {
307
+ throw new Error(
308
+ 'No relevant workflows nodes found for the given prompt. Please be more specific about the integrations you want to use (e.g., Gmail, Slack, Stripe).'
309
+ );
310
+ }
311
+
312
+ // ── Integration availability check ──
313
+ const rawProvider = this.runtime.getService(WORKFLOW_CREDENTIAL_PROVIDER_TYPE);
314
+ const credProvider = isCredentialProvider(rawProvider) ? rawProvider : null;
315
+
316
+ if (credProvider?.checkCredentialTypes) {
317
+ const credTypes = new Set<string>();
318
+ for (const { node } of relevantNodes) {
319
+ for (const cred of node.credentials ?? []) {
320
+ credTypes.add(cred.name);
321
+ }
322
+ }
323
+
324
+ if (credTypes.size > 0) {
325
+ const checkResult = credProvider.checkCredentialTypes([...credTypes]);
326
+
327
+ if (checkResult.unsupported.length > 0) {
328
+ const supportedSet = new Set(checkResult.supported);
329
+ const { remaining, removed } = filterNodesByIntegrationSupport(
330
+ relevantNodes,
331
+ supportedSet
332
+ );
333
+
334
+ const remainingServiceNodes = remaining.filter((r) => r.node.credentials?.length);
335
+
336
+ if (remainingServiceNodes.length === 0) {
337
+ throw new UnsupportedIntegrationError(
338
+ [...new Set(removed.map((r) => r.node.displayName))],
339
+ []
340
+ );
341
+ }
342
+
343
+ const feasibility = await assessFeasibility(this.runtime, prompt, removed, remaining);
344
+
345
+ if (!feasibility.feasible) {
346
+ throw new UnsupportedIntegrationError(
347
+ [...new Set(removed.map((r) => r.node.displayName))],
348
+ [...new Set(remainingServiceNodes.map((r) => r.node.displayName))]
349
+ );
350
+ }
351
+
352
+ logger.debug(
353
+ { src: 'plugin:workflow:service:main' },
354
+ `Feasibility OK: ${feasibility.reason}. Proceeding with ${remaining.length} nodes.`
355
+ );
356
+ relevantNodes = remaining;
357
+ }
358
+ }
359
+ }
360
+ // ── End integration check ──
361
+
362
+ const finalNodeDefs = relevantNodes.map((r) => r.node);
363
+ const runtimeContext = await this.fetchRuntimeContext(
364
+ finalNodeDefs,
365
+ opts?.userId ?? 'local',
366
+ opts?.triggerContext
367
+ );
368
+
369
+ let workflow = await generateWorkflow(this.runtime, prompt, finalNodeDefs, runtimeContext);
370
+ logger.debug(
371
+ { src: 'plugin:workflow:service:main' },
372
+ `Generated workflow with ${workflow.nodes?.length || 0} nodes`
373
+ );
374
+
375
+ // Safety net: even with the MANDATORY INVARIANT prompt rule, the LLM
376
+ // sometimes omits the `credentials` block on credentialed nodes. Inject
377
+ // it deterministically based on the node's catalog definition + the
378
+ // host's supported cred types so resolveCredentials can mint the
379
+ // credential server-side instead of falling back to a manual UI step.
380
+ const injectedCreds = injectMissingCredentialBlocks(workflow, finalNodeDefs, runtimeContext);
381
+ if (injectedCreds > 0) {
382
+ logger.debug(
383
+ { src: 'plugin:workflow:service:main' },
384
+ `Injected ${injectedCreds} missing credentials block(s) (LLM omitted)`
385
+ );
386
+ }
387
+
388
+ // Layer 1+3 (Session 21): deterministic pre-deploy validation pass with
389
+ // bounded LLM-retry. Catches typeVersion hallucinations, missing
390
+ // parameters.authentication, output-field case mismatches (Subject vs
391
+ // subject), node-name collisions, and dangling connection edges. When
392
+ // an error can't be auto-fixed deterministically, fixWorkflowErrors
393
+ // sends a surgical fix prompt to the LLM. Cap at 3 retries to bound
394
+ // worst-case cost.
395
+ //
396
+ // Fetch the live workflow runtime's node-type registry once per deploy so
397
+ // typeVersion clamping intersects catalog ∩ runtime — necessary
398
+ // because the bundled `defaultNodes.json` can be ahead of the user's
399
+ // actually-installed workflows binary (e.g. catalog says Gmail v2.2 but
400
+ // runtime only ships up to v2.1).
401
+ const generateClient = this.getClient();
402
+ const runtimeVersions = (await generateClient.getRuntimeNodeTypeVersions()) ?? undefined;
403
+ for (let attempt = 0; attempt < 3; attempt++) {
404
+ const repairResult = validateAndRepair(
405
+ workflow,
406
+ finalNodeDefs,
407
+ runtimeContext,
408
+ runtimeVersions
409
+ );
410
+ workflow = repairResult.workflow;
411
+ if (repairResult.errors.length === 0) {
412
+ break;
413
+ }
414
+ if (attempt === 2) {
415
+ logger.warn(
416
+ {
417
+ src: 'plugin:workflow:service:main',
418
+ errors: repairResult.errors,
419
+ },
420
+ `validateAndRepair: ${repairResult.errors.length} unrecoverable error(s) after 3 retries — proceeding to deploy with _meta.errors`
421
+ );
422
+ workflow._meta = workflow._meta ?? {};
423
+ const errorLines = repairResult.errors.map(
424
+ (e) =>
425
+ `${e.node}: ${e.detail}${e.availableFields?.length ? ` (available: ${e.availableFields.join(', ')})` : ''}`
426
+ );
427
+ const existing = workflow._meta.requiresClarification ?? [];
428
+ workflow._meta.requiresClarification = [...existing, ...errorLines];
429
+ break;
430
+ }
431
+ try {
432
+ workflow = await fixWorkflowErrors(
433
+ this.runtime,
434
+ workflow,
435
+ repairResult.errors,
436
+ finalNodeDefs
437
+ );
438
+ } catch (err) {
439
+ logger.warn(
440
+ {
441
+ src: 'plugin:workflow:service:main',
442
+ err: err instanceof Error ? err.message : String(err),
443
+ },
444
+ 'fixWorkflowErrors threw — exiting retry loop'
445
+ );
446
+ break;
447
+ }
448
+ }
449
+
450
+ normalizeTriggerSimpleParam(workflow);
451
+
452
+ const optionFixes = correctOptionParameters(workflow);
453
+ if (optionFixes > 0) {
454
+ logger.debug(
455
+ { src: 'plugin:workflow:service:main' },
456
+ `Corrected ${optionFixes} invalid option parameter(s)`
457
+ );
458
+ }
459
+
460
+ const unknownParams = detectUnknownParameters(workflow);
461
+ if (unknownParams.length > 0) {
462
+ logger.debug(
463
+ { src: 'plugin:workflow:service:main' },
464
+ `Found ${unknownParams.length} node(s) with unknown parameters, auto-correcting...`
465
+ );
466
+ workflow = await correctParameterNames(this.runtime, workflow, unknownParams);
467
+ }
468
+
469
+ const invalidRefs = validateOutputReferences(workflow);
470
+ if (invalidRefs.length > 0) {
471
+ logger.debug(
472
+ { src: 'plugin:workflow:service:main' },
473
+ `Found ${invalidRefs.length} invalid field reference(s), auto-correcting...`
474
+ );
475
+ workflow = await correctFieldReferences(this.runtime, workflow, invalidRefs);
476
+ }
477
+
478
+ const exprPrefixed = ensureExpressionPrefix(workflow);
479
+ if (exprPrefixed > 0) {
480
+ logger.debug(
481
+ { src: 'plugin:workflow:service:main' },
482
+ `Prefixed ${exprPrefixed} expression value(s) with "="`
483
+ );
484
+ }
485
+
486
+ const validationResult = validateWorkflow(workflow);
487
+ if (!validationResult.valid) {
488
+ logger.error(
489
+ { src: 'plugin:workflow:service:main' },
490
+ `Validation errors: ${validationResult.errors.join(', ')}`
491
+ );
492
+ throw new Error(`Generated workflow is invalid: ${validationResult.errors[0]}`);
493
+ }
494
+ if (validationResult.warnings.length > 0) {
495
+ logger.warn(
496
+ { src: 'plugin:workflow:service:main' },
497
+ `Validation warnings: ${validationResult.warnings.join(', ')}`
498
+ );
499
+ }
500
+
501
+ this.injectCatalogClarifications(workflow);
502
+ return positionNodes(workflow);
503
+ }
504
+
505
+ async modifyWorkflowDraft(
506
+ existingWorkflow: WorkflowDefinition,
507
+ modificationRequest: string,
508
+ opts?: { userId?: string; triggerContext?: TriggerContext }
509
+ ): Promise<WorkflowDefinition> {
510
+ logger.info(
511
+ { src: 'plugin:workflow:service:main' },
512
+ `Modifying workflow draft: ${modificationRequest.slice(0, 100)}`
513
+ );
514
+
515
+ // Get definitions for nodes already in the workflow
516
+ const existingDefs = collectExistingNodeDefinitions(existingWorkflow);
517
+
518
+ // Search for new nodes the modification might need
519
+ const keywords = await extractKeywords(this.runtime, modificationRequest);
520
+ const searchResults = this.filterForEmbeddedBackend(searchNodes(keywords, 10));
521
+ const newDefs = searchResults.map((r) => r.node);
522
+
523
+ // Deduplicate: merge existing + new, preferring existing (already in workflow)
524
+ const seenNames = new Set(existingDefs.map((d) => d.name));
525
+ const combinedDefs = [...existingDefs];
526
+ for (const def of newDefs) {
527
+ if (!seenNames.has(def.name)) {
528
+ seenNames.add(def.name);
529
+ combinedDefs.push(def);
530
+ }
531
+ }
532
+
533
+ logger.debug(
534
+ { src: 'plugin:workflow:service:main' },
535
+ `Modify context: ${existingDefs.length} existing + ${newDefs.length} searched → ${combinedDefs.length} unique node defs`
536
+ );
537
+
538
+ const runtimeContext = await this.fetchRuntimeContext(
539
+ combinedDefs,
540
+ opts?.userId ?? 'local',
541
+ opts?.triggerContext
542
+ );
543
+
544
+ let workflow = await modifyWorkflow(
545
+ this.runtime,
546
+ existingWorkflow,
547
+ modificationRequest,
548
+ combinedDefs,
549
+ runtimeContext
550
+ );
551
+
552
+ // Safety net: same deterministic credential-block injection as
553
+ // generateWorkflowDraft. Modification regenerations are equally prone
554
+ // to dropping the credentials block.
555
+ const injectedCreds = injectMissingCredentialBlocks(workflow, combinedDefs, runtimeContext);
556
+ if (injectedCreds > 0) {
557
+ logger.debug(
558
+ { src: 'plugin:workflow:service:main' },
559
+ `Injected ${injectedCreds} missing credentials block(s) on modify (LLM omitted)`
560
+ );
561
+ }
562
+
563
+ // Layer 1+3 (Session 21): mirror the validate-and-repair retry loop on
564
+ // the modify path. Modifications can drift in the same ways generations
565
+ // do (typeVersion hallucination, missing authentication, etc.) so the
566
+ // gate must run here too. Same runtime-version intersect as the
567
+ // generate path — fetch once, reuse across all 3 retry attempts.
568
+ const modifyClient = this.getClient();
569
+ const runtimeVersionsForModify = (await modifyClient.getRuntimeNodeTypeVersions()) ?? undefined;
570
+ for (let attempt = 0; attempt < 3; attempt++) {
571
+ const repairResult = validateAndRepair(
572
+ workflow,
573
+ combinedDefs,
574
+ runtimeContext,
575
+ runtimeVersionsForModify
576
+ );
577
+ workflow = repairResult.workflow;
578
+ if (repairResult.errors.length === 0) {
579
+ break;
580
+ }
581
+ if (attempt === 2) {
582
+ logger.warn(
583
+ {
584
+ src: 'plugin:workflow:service:main',
585
+ errors: repairResult.errors,
586
+ },
587
+ `validateAndRepair (modify): ${repairResult.errors.length} unrecoverable error(s) after 3 retries`
588
+ );
589
+ workflow._meta = workflow._meta ?? {};
590
+ const errorLines = repairResult.errors.map(
591
+ (e) =>
592
+ `${e.node}: ${e.detail}${e.availableFields?.length ? ` (available: ${e.availableFields.join(', ')})` : ''}`
593
+ );
594
+ const existing = workflow._meta.requiresClarification ?? [];
595
+ workflow._meta.requiresClarification = [...existing, ...errorLines];
596
+ break;
597
+ }
598
+ try {
599
+ workflow = await fixWorkflowErrors(
600
+ this.runtime,
601
+ workflow,
602
+ repairResult.errors,
603
+ combinedDefs
604
+ );
605
+ } catch (err) {
606
+ logger.warn(
607
+ {
608
+ src: 'plugin:workflow:service:main',
609
+ err: err instanceof Error ? err.message : String(err),
610
+ },
611
+ 'fixWorkflowErrors (modify) threw — exiting retry loop'
612
+ );
613
+ break;
614
+ }
615
+ }
616
+
617
+ normalizeTriggerSimpleParam(workflow);
618
+
619
+ const optionFixes = correctOptionParameters(workflow);
620
+ if (optionFixes > 0) {
621
+ logger.debug(
622
+ { src: 'plugin:workflow:service:main' },
623
+ `Corrected ${optionFixes} invalid option parameter(s) in modified workflow`
624
+ );
625
+ }
626
+
627
+ const unknownParams = detectUnknownParameters(workflow);
628
+ if (unknownParams.length > 0) {
629
+ logger.debug(
630
+ { src: 'plugin:workflow:service:main' },
631
+ `Found ${unknownParams.length} node(s) with unknown parameters in modified workflow, auto-correcting...`
632
+ );
633
+ workflow = await correctParameterNames(this.runtime, workflow, unknownParams);
634
+ }
635
+
636
+ const invalidRefs = validateOutputReferences(workflow);
637
+ if (invalidRefs.length > 0) {
638
+ logger.debug(
639
+ { src: 'plugin:workflow:service:main' },
640
+ `Found ${invalidRefs.length} invalid field reference(s) in modified workflow, auto-correcting...`
641
+ );
642
+ workflow = await correctFieldReferences(this.runtime, workflow, invalidRefs);
643
+ }
644
+
645
+ const exprPrefixed = ensureExpressionPrefix(workflow);
646
+ if (exprPrefixed > 0) {
647
+ logger.debug(
648
+ { src: 'plugin:workflow:service:main' },
649
+ `Prefixed ${exprPrefixed} expression value(s) with "=" in modified workflow`
650
+ );
651
+ }
652
+
653
+ const validationResult = validateWorkflow(workflow);
654
+ if (!validationResult.valid) {
655
+ logger.error(
656
+ { src: 'plugin:workflow:service:main' },
657
+ `Modified workflow validation errors: ${validationResult.errors.join(', ')}`
658
+ );
659
+ throw new Error(`Modified workflow is invalid: ${validationResult.errors[0]}`);
660
+ }
661
+
662
+ this.injectCatalogClarifications(workflow);
663
+ return positionNodes(workflow);
664
+ }
665
+
666
+ async deployWorkflow(
667
+ workflow: WorkflowDefinition,
668
+ userId: string
669
+ ): Promise<WorkflowCreationResult> {
670
+ logger.info(
671
+ { src: 'plugin:workflow:service:main' },
672
+ `Deploying workflow "${workflow.name}" for user ${userId}`
673
+ );
674
+
675
+ const deployTarget = this.resolveDeployTarget(workflow);
676
+ const { config, client } = deployTarget;
677
+
678
+ const rawCredStore = this.runtime.getService(WORKFLOW_CREDENTIAL_STORE_TYPE);
679
+ const credStore = isWorkflowCredentialStoreApi(rawCredStore) ? rawCredStore : null;
680
+
681
+ const rawProvider = this.runtime.getService(WORKFLOW_CREDENTIAL_PROVIDER_TYPE);
682
+ const credProvider = isCredentialProvider(rawProvider) ? rawProvider : null;
683
+
684
+ // Compute tag name once - reused for credentials and workflow tagging
685
+ const tagName = await getUserTagName(this.runtime, userId);
686
+
687
+ const credentialResult = await resolveCredentials(
688
+ workflow,
689
+ userId,
690
+ config,
691
+ credStore ?? null,
692
+ credProvider,
693
+ client,
694
+ tagName
695
+ );
696
+
697
+ // Block deploy if any credential is unresolved
698
+ if (credentialResult.missingConnections.length > 0) {
699
+ return {
700
+ id: '',
701
+ name: workflow.name,
702
+ active: false,
703
+ nodeCount: workflow.nodes.length,
704
+ missingCredentials: credentialResult.missingConnections,
705
+ };
706
+ }
707
+
708
+ // Determine if this is an update (existing workflow) or create (new workflow).
709
+ // If update fails (workflow deleted on workflows), fallback to create.
710
+ let deployedWorkflow: WorkflowDefinitionResponse;
711
+ let wasUpdate = false;
712
+ if (workflow.id) {
713
+ try {
714
+ deployedWorkflow = await client.updateWorkflow(workflow.id, credentialResult.workflow);
715
+ wasUpdate = true;
716
+ } catch {
717
+ logger.warn(
718
+ { src: 'plugin:workflow:service:main' },
719
+ `Update failed for workflow ${workflow.id}, creating new workflow instead`
720
+ );
721
+ const { id: _, ...rest } = credentialResult.workflow;
722
+ deployedWorkflow = await client.createWorkflow(rest);
723
+ }
724
+ } else {
725
+ deployedWorkflow = await client.createWorkflow(credentialResult.workflow);
726
+ }
727
+
728
+ logger.info(
729
+ { src: 'plugin:workflow:service:main' },
730
+ `Workflow ${wasUpdate ? 'updated' : 'created'}: ${deployedWorkflow.id}`
731
+ );
732
+
733
+ // Activate (publish) the workflow immediately after creation/update
734
+ let active = false;
735
+ try {
736
+ await client.activateWorkflow(deployedWorkflow.id);
737
+ active = true;
738
+ logger.info(
739
+ { src: 'plugin:workflow:service:main' },
740
+ `Workflow ${deployedWorkflow.id} activated`
741
+ );
742
+ } catch (error) {
743
+ logger.warn(
744
+ { src: 'plugin:workflow:service:main' },
745
+ `Failed to activate workflow: ${error instanceof Error ? error.message : String(error)}`
746
+ );
747
+ }
748
+
749
+ // Only tag new workflows (existing ones should already have tags)
750
+ if (userId && !wasUpdate) {
751
+ try {
752
+ const userTag = await client.getOrCreateTag(tagName);
753
+ await client.updateWorkflowTags(deployedWorkflow.id, [userTag.id]);
754
+ logger.debug(
755
+ { src: 'plugin:workflow:service:main' },
756
+ `Tagged workflow ${deployedWorkflow.id} with "${tagName}"`
757
+ );
758
+ } catch (error) {
759
+ logger.warn(
760
+ { src: 'plugin:workflow:service:main' },
761
+ `Failed to tag workflow: ${error instanceof Error ? error.message : String(error)}`
762
+ );
763
+ }
764
+ }
765
+
766
+ return {
767
+ id: deployedWorkflow.id,
768
+ name: deployedWorkflow.name,
769
+ active,
770
+ nodeCount: deployedWorkflow.nodes?.length || 0,
771
+ missingCredentials: credentialResult.missingConnections,
772
+ };
773
+ }
774
+
775
+ async listWorkflows(userId?: string): Promise<WorkflowDefinitionResponse[]> {
776
+ const client = this.getClient();
777
+
778
+ if (userId) {
779
+ const tagName = await getUserTagName(this.runtime, userId);
780
+ const tagsResponse = await client.listTags();
781
+ const userTag = tagsResponse.data.find((t) => t.name === tagName);
782
+
783
+ if (!userTag) {
784
+ return []; // No workflows for this user
785
+ }
786
+
787
+ // Get all workflows and filter by tag
788
+ const workflowsResponse = await client.listWorkflows();
789
+ return workflowsResponse.data.filter((w) => w.tags?.some((t) => t.id === userTag.id));
790
+ }
791
+
792
+ const response = await client.listWorkflows();
793
+ return response.data;
794
+ }
795
+
796
+ async activateWorkflow(workflowId: string): Promise<void> {
797
+ const client = this.getClient();
798
+ await client.activateWorkflow(workflowId);
799
+ logger.info({ src: 'plugin:workflow:service:main' }, `Workflow ${workflowId} activated`);
800
+ }
801
+
802
+ async deactivateWorkflow(workflowId: string): Promise<void> {
803
+ const client = this.getClient();
804
+ await client.deactivateWorkflow(workflowId);
805
+ logger.info({ src: 'plugin:workflow:service:main' }, `Workflow ${workflowId} deactivated`);
806
+ }
807
+
808
+ async deleteWorkflow(workflowId: string): Promise<void> {
809
+ const client = this.getClient();
810
+ await client.deleteWorkflow(workflowId);
811
+ logger.info({ src: 'plugin:workflow:service:main' }, `Workflow ${workflowId} deleted`);
812
+ }
813
+
814
+ async getWorkflow(workflowId: string): Promise<WorkflowDefinitionResponse> {
815
+ const client = this.getClient();
816
+ return client.getWorkflow(workflowId);
817
+ }
818
+
819
+ async getWorkflowExecutions(workflowId: string, limit?: number): Promise<WorkflowExecution[]> {
820
+ const client = this.getClient();
821
+ const response = await client.listExecutions({ workflowId, limit });
822
+ return response.data;
823
+ }
824
+
825
+ async listExecutions(params?: {
826
+ workflowId?: string;
827
+ status?: 'canceled' | 'error' | 'running' | 'success' | 'waiting';
828
+ limit?: number;
829
+ cursor?: string;
830
+ }): Promise<{ data: WorkflowExecution[]; nextCursor?: string }> {
831
+ const client = this.getClient();
832
+ return client.listExecutions(params);
833
+ }
834
+
835
+ async getExecutionDetail(executionId: string): Promise<WorkflowExecution> {
836
+ const client = this.getClient();
837
+ return client.getExecution(executionId);
838
+ }
839
+ }