@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.
- package/README.md +71 -0
- package/auto-enable.ts +18 -0
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +2 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/actions/workflow.d.ts +23 -0
- package/dist/actions/workflow.d.ts.map +1 -0
- package/dist/actions/workflow.js +425 -0
- package/dist/actions/workflow.js.map +1 -0
- package/dist/data/defaultNodes.json +9887 -0
- package/dist/data/schemaIndex.json +1 -0
- package/dist/data/triggerSchemaIndex.json +1 -0
- package/dist/db/index.d.ts +2 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +2 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +588 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +59 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +126 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/automations-builder.d.ts +21 -0
- package/dist/lib/automations-builder.d.ts.map +1 -0
- package/dist/lib/automations-builder.js +557 -0
- package/dist/lib/automations-builder.js.map +1 -0
- package/dist/lib/automations-types.d.ts +153 -0
- package/dist/lib/automations-types.d.ts.map +1 -0
- package/dist/lib/automations-types.js +191 -0
- package/dist/lib/automations-types.js.map +1 -0
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +3 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/legacy-task-migration.d.ts +20 -0
- package/dist/lib/legacy-task-migration.d.ts.map +1 -0
- package/dist/lib/legacy-task-migration.js +110 -0
- package/dist/lib/legacy-task-migration.js.map +1 -0
- package/dist/lib/legacy-text-trigger-migration.d.ts +18 -0
- package/dist/lib/legacy-text-trigger-migration.d.ts.map +1 -0
- package/dist/lib/legacy-text-trigger-migration.js +131 -0
- package/dist/lib/legacy-text-trigger-migration.js.map +1 -0
- package/dist/lib/workflow-clarification.d.ts +113 -0
- package/dist/lib/workflow-clarification.d.ts.map +1 -0
- package/dist/lib/workflow-clarification.js +425 -0
- package/dist/lib/workflow-clarification.js.map +1 -0
- package/dist/plugin-routes.d.ts +9 -0
- package/dist/plugin-routes.d.ts.map +1 -0
- package/dist/plugin-routes.js +147 -0
- package/dist/plugin-routes.js.map +1 -0
- package/dist/providers/activeWorkflows.d.ts +11 -0
- package/dist/providers/activeWorkflows.d.ts.map +1 -0
- package/dist/providers/activeWorkflows.js +72 -0
- package/dist/providers/activeWorkflows.js.map +1 -0
- package/dist/providers/index.d.ts +4 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/pendingDraft.d.ts +9 -0
- package/dist/providers/pendingDraft.d.ts.map +1 -0
- package/dist/providers/pendingDraft.js +48 -0
- package/dist/providers/pendingDraft.js.map +1 -0
- package/dist/providers/workflowStatus.d.ts +3 -0
- package/dist/providers/workflowStatus.d.ts.map +1 -0
- package/dist/providers/workflowStatus.js +69 -0
- package/dist/providers/workflowStatus.js.map +1 -0
- package/dist/register-routes.d.ts +2 -0
- package/dist/register-routes.d.ts.map +1 -0
- package/dist/register-routes.js +6 -0
- package/dist/register-routes.js.map +1 -0
- package/dist/routes/_helpers.d.ts +11 -0
- package/dist/routes/_helpers.d.ts.map +1 -0
- package/dist/routes/_helpers.js +22 -0
- package/dist/routes/_helpers.js.map +1 -0
- package/dist/routes/automations.d.ts +19 -0
- package/dist/routes/automations.d.ts.map +1 -0
- package/dist/routes/automations.js +32 -0
- package/dist/routes/automations.js.map +1 -0
- package/dist/routes/embedded-webhooks.d.ts +3 -0
- package/dist/routes/embedded-webhooks.d.ts.map +1 -0
- package/dist/routes/embedded-webhooks.js +47 -0
- package/dist/routes/embedded-webhooks.js.map +1 -0
- package/dist/routes/executions.d.ts +3 -0
- package/dist/routes/executions.d.ts.map +1 -0
- package/dist/routes/executions.js +58 -0
- package/dist/routes/executions.js.map +1 -0
- package/dist/routes/index.d.ts +4 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +14 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/nodes.d.ts +3 -0
- package/dist/routes/nodes.d.ts.map +1 -0
- package/dist/routes/nodes.js +168 -0
- package/dist/routes/nodes.js.map +1 -0
- package/dist/routes/validation.d.ts +3 -0
- package/dist/routes/validation.d.ts.map +1 -0
- package/dist/routes/validation.js +41 -0
- package/dist/routes/validation.js.map +1 -0
- package/dist/routes/workflow-routes.d.ts +27 -0
- package/dist/routes/workflow-routes.d.ts.map +1 -0
- package/dist/routes/workflow-routes.js +326 -0
- package/dist/routes/workflow-routes.js.map +1 -0
- package/dist/routes/workflows.d.ts +3 -0
- package/dist/routes/workflows.d.ts.map +1 -0
- package/dist/routes/workflows.js +252 -0
- package/dist/routes/workflows.js.map +1 -0
- package/dist/schemas/draftIntent.d.ts +22 -0
- package/dist/schemas/draftIntent.d.ts.map +1 -0
- package/dist/schemas/draftIntent.js +22 -0
- package/dist/schemas/draftIntent.js.map +1 -0
- package/dist/schemas/feasibility.d.ts +13 -0
- package/dist/schemas/feasibility.d.ts.map +1 -0
- package/dist/schemas/feasibility.js +9 -0
- package/dist/schemas/feasibility.js.map +1 -0
- package/dist/schemas/index.d.ts +5 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +5 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/keywordExtraction.d.ts +14 -0
- package/dist/schemas/keywordExtraction.d.ts.map +1 -0
- package/dist/schemas/keywordExtraction.js +12 -0
- package/dist/schemas/keywordExtraction.js.map +1 -0
- package/dist/schemas/workflowMatching.d.ts +36 -0
- package/dist/schemas/workflowMatching.d.ts.map +1 -0
- package/dist/schemas/workflowMatching.js +30 -0
- package/dist/schemas/workflowMatching.js.map +1 -0
- package/dist/services/embedded-workflow-service.d.ts +106 -0
- package/dist/services/embedded-workflow-service.d.ts.map +1 -0
- package/dist/services/embedded-workflow-service.js +1900 -0
- package/dist/services/embedded-workflow-service.js.map +1 -0
- package/dist/services/index.d.ts +5 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +5 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/workflow-credential-store.d.ts +27 -0
- package/dist/services/workflow-credential-store.d.ts.map +1 -0
- package/dist/services/workflow-credential-store.js +92 -0
- package/dist/services/workflow-credential-store.js.map +1 -0
- package/dist/services/workflow-dispatch.d.ts +41 -0
- package/dist/services/workflow-dispatch.d.ts.map +1 -0
- package/dist/services/workflow-dispatch.js +86 -0
- package/dist/services/workflow-dispatch.js.map +1 -0
- package/dist/services/workflow-service.d.ts +63 -0
- package/dist/services/workflow-service.d.ts.map +1 -0
- package/dist/services/workflow-service.js +492 -0
- package/dist/services/workflow-service.js.map +1 -0
- package/dist/trigger-routes.d.ts +153 -0
- package/dist/trigger-routes.d.ts.map +1 -0
- package/dist/trigger-routes.js +424 -0
- package/dist/trigger-routes.js.map +1 -0
- package/dist/types/index.d.ts +457 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +59 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/catalog.d.ts +16 -0
- package/dist/utils/catalog.d.ts.map +1 -0
- package/dist/utils/catalog.js +211 -0
- package/dist/utils/catalog.js.map +1 -0
- package/dist/utils/clarification.d.ts +17 -0
- package/dist/utils/clarification.d.ts.map +1 -0
- package/dist/utils/clarification.js +46 -0
- package/dist/utils/clarification.js.map +1 -0
- package/dist/utils/context.d.ts +4 -0
- package/dist/utils/context.d.ts.map +1 -0
- package/dist/utils/context.js +18 -0
- package/dist/utils/context.js.map +1 -0
- package/dist/utils/credentialResolver.d.ts +22 -0
- package/dist/utils/credentialResolver.d.ts.map +1 -0
- package/dist/utils/credentialResolver.js +146 -0
- package/dist/utils/credentialResolver.js.map +1 -0
- package/dist/utils/generation.d.ts +36 -0
- package/dist/utils/generation.d.ts.map +1 -0
- package/dist/utils/generation.js +701 -0
- package/dist/utils/generation.js.map +1 -0
- package/dist/utils/host-capabilities.d.ts +27 -0
- package/dist/utils/host-capabilities.d.ts.map +1 -0
- package/dist/utils/host-capabilities.js +59 -0
- package/dist/utils/host-capabilities.js.map +1 -0
- package/dist/utils/inferSyntheticOutputSchema.d.ts +20 -0
- package/dist/utils/inferSyntheticOutputSchema.d.ts.map +1 -0
- package/dist/utils/inferSyntheticOutputSchema.js +151 -0
- package/dist/utils/inferSyntheticOutputSchema.js.map +1 -0
- package/dist/utils/outputSchema.d.ts +26 -0
- package/dist/utils/outputSchema.d.ts.map +1 -0
- package/dist/utils/outputSchema.js +297 -0
- package/dist/utils/outputSchema.js.map +1 -0
- package/dist/utils/validateAndRepair.d.ts +41 -0
- package/dist/utils/validateAndRepair.d.ts.map +1 -0
- package/dist/utils/validateAndRepair.js +483 -0
- package/dist/utils/validateAndRepair.js.map +1 -0
- package/dist/utils/workflow-prompts/actionResponse.d.ts +2 -0
- package/dist/utils/workflow-prompts/actionResponse.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/actionResponse.js +17 -0
- package/dist/utils/workflow-prompts/actionResponse.js.map +1 -0
- package/dist/utils/workflow-prompts/draftIntent.d.ts +2 -0
- package/dist/utils/workflow-prompts/draftIntent.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/draftIntent.js +23 -0
- package/dist/utils/workflow-prompts/draftIntent.js.map +1 -0
- package/dist/utils/workflow-prompts/feasibilityCheck.d.ts +2 -0
- package/dist/utils/workflow-prompts/feasibilityCheck.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/feasibilityCheck.js +21 -0
- package/dist/utils/workflow-prompts/feasibilityCheck.js.map +1 -0
- package/dist/utils/workflow-prompts/fieldCorrection.d.ts +3 -0
- package/dist/utils/workflow-prompts/fieldCorrection.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/fieldCorrection.js +20 -0
- package/dist/utils/workflow-prompts/fieldCorrection.js.map +1 -0
- package/dist/utils/workflow-prompts/index.d.ts +8 -0
- package/dist/utils/workflow-prompts/index.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/index.js +8 -0
- package/dist/utils/workflow-prompts/index.js.map +1 -0
- package/dist/utils/workflow-prompts/keywordExtraction.d.ts +2 -0
- package/dist/utils/workflow-prompts/keywordExtraction.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/keywordExtraction.js +21 -0
- package/dist/utils/workflow-prompts/keywordExtraction.js.map +1 -0
- package/dist/utils/workflow-prompts/parameterCorrection.d.ts +3 -0
- package/dist/utils/workflow-prompts/parameterCorrection.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/parameterCorrection.js +29 -0
- package/dist/utils/workflow-prompts/parameterCorrection.js.map +1 -0
- package/dist/utils/workflow-prompts/workflowGeneration.d.ts +2 -0
- package/dist/utils/workflow-prompts/workflowGeneration.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/workflowGeneration.js +529 -0
- package/dist/utils/workflow-prompts/workflowGeneration.js.map +1 -0
- package/dist/utils/workflow-prompts/workflowMatching.d.ts +2 -0
- package/dist/utils/workflow-prompts/workflowMatching.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/workflowMatching.js +23 -0
- package/dist/utils/workflow-prompts/workflowMatching.js.map +1 -0
- package/dist/utils/workflow.d.ts +62 -0
- package/dist/utils/workflow.d.ts.map +1 -0
- package/dist/utils/workflow.js +712 -0
- package/dist/utils/workflow.js.map +1 -0
- package/package.json +87 -0
- package/src/actions/index.ts +1 -0
- package/src/actions/workflow.ts +494 -0
- package/src/data/defaultNodes.json +9887 -0
- package/src/data/schemaIndex.json +1 -0
- package/src/data/triggerSchemaIndex.json +1 -0
- package/src/db/index.ts +8 -0
- package/src/db/schema.ts +94 -0
- package/src/index.ts +179 -0
- package/src/lib/automations-builder.ts +679 -0
- package/src/lib/automations-types.ts +391 -0
- package/src/lib/index.ts +8 -0
- package/src/lib/legacy-task-migration.ts +143 -0
- package/src/lib/legacy-text-trigger-migration.ts +178 -0
- package/src/lib/workflow-clarification.ts +497 -0
- package/src/plugin-routes.ts +164 -0
- package/src/providers/activeWorkflows.ts +81 -0
- package/src/providers/index.ts +3 -0
- package/src/providers/pendingDraft.ts +55 -0
- package/src/providers/workflowStatus.ts +88 -0
- package/src/register-routes.ts +6 -0
- package/src/routes/_helpers.ts +27 -0
- package/src/routes/automations.ts +46 -0
- package/src/routes/embedded-webhooks.ts +64 -0
- package/src/routes/executions.ts +75 -0
- package/src/routes/index.ts +16 -0
- package/src/routes/nodes.ts +211 -0
- package/src/routes/validation.ts +51 -0
- package/src/routes/workflow-routes.ts +469 -0
- package/src/routes/workflows.ts +310 -0
- package/src/schemas/draftIntent.ts +21 -0
- package/src/schemas/feasibility.ts +8 -0
- package/src/schemas/index.ts +4 -0
- package/src/schemas/keywordExtraction.ts +11 -0
- package/src/schemas/workflowMatching.ts +29 -0
- package/src/services/embedded-workflow-service.ts +2224 -0
- package/src/services/index.ts +17 -0
- package/src/services/workflow-credential-store.ts +132 -0
- package/src/services/workflow-dispatch.ts +121 -0
- package/src/services/workflow-service.ts +839 -0
- package/src/trigger-routes.ts +714 -0
- package/src/types/index.ts +562 -0
- package/src/utils/catalog.ts +260 -0
- package/src/utils/clarification.ts +52 -0
- package/src/utils/context.ts +22 -0
- package/src/utils/credentialResolver.ts +234 -0
- package/src/utils/generation.ts +987 -0
- package/src/utils/host-capabilities.ts +81 -0
- package/src/utils/inferSyntheticOutputSchema.ts +163 -0
- package/src/utils/outputSchema.ts +372 -0
- package/src/utils/validateAndRepair.ts +610 -0
- package/src/utils/workflow-prompts/actionResponse.ts +16 -0
- package/src/utils/workflow-prompts/draftIntent.ts +22 -0
- package/src/utils/workflow-prompts/feasibilityCheck.ts +20 -0
- package/src/utils/workflow-prompts/fieldCorrection.ts +20 -0
- package/src/utils/workflow-prompts/index.ts +10 -0
- package/src/utils/workflow-prompts/keywordExtraction.ts +20 -0
- package/src/utils/workflow-prompts/parameterCorrection.ts +29 -0
- package/src/utils/workflow-prompts/workflowGeneration.ts +528 -0
- package/src/utils/workflow-prompts/workflowMatching.ts +22 -0
- 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
|
+
}
|