@elizaos/plugin-workflow 2.0.0-beta.1 → 2.0.3-beta.6

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 (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +28 -26
  3. package/dist/actions/eval-code.d.ts +12 -0
  4. package/dist/actions/eval-code.d.ts.map +1 -0
  5. package/dist/actions/eval-code.js +59 -0
  6. package/dist/actions/eval-code.js.map +1 -0
  7. package/dist/actions/index.d.ts +1 -0
  8. package/dist/actions/index.d.ts.map +1 -1
  9. package/dist/actions/index.js +1 -0
  10. package/dist/actions/index.js.map +1 -1
  11. package/dist/actions/workflow.d.ts +7 -0
  12. package/dist/actions/workflow.d.ts.map +1 -1
  13. package/dist/actions/workflow.js +462 -10
  14. package/dist/actions/workflow.js.map +1 -1
  15. package/dist/db/schema.d.ts +196 -0
  16. package/dist/db/schema.d.ts.map +1 -1
  17. package/dist/db/schema.js +23 -0
  18. package/dist/db/schema.js.map +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +9 -64
  21. package/dist/index.js.map +1 -1
  22. package/dist/lib/automations-builder.d.ts.map +1 -1
  23. package/dist/lib/automations-builder.js +10 -35
  24. package/dist/lib/automations-builder.js.map +1 -1
  25. package/dist/lib/automations-types.d.ts +2 -2
  26. package/dist/lib/automations-types.d.ts.map +1 -1
  27. package/dist/lib/automations-types.js.map +1 -1
  28. package/dist/lib/index.d.ts +0 -2
  29. package/dist/lib/index.d.ts.map +1 -1
  30. package/dist/lib/index.js +1 -2
  31. package/dist/lib/index.js.map +1 -1
  32. package/dist/lib/workflow-clarification.d.ts +2 -2
  33. package/dist/lib/workflow-clarification.d.ts.map +1 -1
  34. package/dist/lib/workflow-clarification.js +15 -11
  35. package/dist/lib/workflow-clarification.js.map +1 -1
  36. package/dist/plugin-routes.d.ts.map +1 -1
  37. package/dist/plugin-routes.js +6 -0
  38. package/dist/plugin-routes.js.map +1 -1
  39. package/dist/providers/activeWorkflows.js +2 -2
  40. package/dist/providers/activeWorkflows.js.map +1 -1
  41. package/dist/providers/workflowStatus.js +1 -1
  42. package/dist/providers/workflowStatus.js.map +1 -1
  43. package/dist/routes/workflow-routes.d.ts.map +1 -1
  44. package/dist/routes/workflow-routes.js +68 -2
  45. package/dist/routes/workflow-routes.js.map +1 -1
  46. package/dist/routes/workflows.d.ts.map +1 -1
  47. package/dist/routes/workflows.js +5 -1
  48. package/dist/routes/workflows.js.map +1 -1
  49. package/dist/services/embedded-workflow-service.d.ts +74 -17
  50. package/dist/services/embedded-workflow-service.d.ts.map +1 -1
  51. package/dist/services/embedded-workflow-service.js +343 -149
  52. package/dist/services/embedded-workflow-service.js.map +1 -1
  53. package/dist/services/smithers-runtime.d.ts +47 -0
  54. package/dist/services/smithers-runtime.d.ts.map +1 -0
  55. package/dist/services/smithers-runtime.js +444 -0
  56. package/dist/services/smithers-runtime.js.map +1 -0
  57. package/dist/services/workflow-credential-store.js +1 -1
  58. package/dist/services/workflow-credential-store.js.map +1 -1
  59. package/dist/services/workflow-dispatch.d.ts +31 -1
  60. package/dist/services/workflow-dispatch.d.ts.map +1 -1
  61. package/dist/services/workflow-dispatch.js +75 -10
  62. package/dist/services/workflow-dispatch.js.map +1 -1
  63. package/dist/services/workflow-service.d.ts +27 -1
  64. package/dist/services/workflow-service.d.ts.map +1 -1
  65. package/dist/services/workflow-service.js +133 -11
  66. package/dist/services/workflow-service.js.map +1 -1
  67. package/dist/trigger-routes.d.ts +2 -18
  68. package/dist/trigger-routes.d.ts.map +1 -1
  69. package/dist/trigger-routes.js +11 -39
  70. package/dist/trigger-routes.js.map +1 -1
  71. package/dist/types/index.d.ts +82 -2
  72. package/dist/types/index.d.ts.map +1 -1
  73. package/dist/types/index.js.map +1 -1
  74. package/dist/types/workflow-contracts.d.ts +118 -0
  75. package/dist/types/workflow-contracts.d.ts.map +1 -0
  76. package/dist/types/workflow-contracts.js +2 -0
  77. package/dist/types/workflow-contracts.js.map +1 -0
  78. package/dist/utils/catalog.js +2 -2
  79. package/dist/utils/catalog.js.map +1 -1
  80. package/dist/utils/clarification.d.ts +1 -1
  81. package/dist/utils/clarification.d.ts.map +1 -1
  82. package/dist/utils/clarification.js +15 -4
  83. package/dist/utils/clarification.js.map +1 -1
  84. package/dist/utils/context.js +1 -1
  85. package/dist/utils/context.js.map +1 -1
  86. package/dist/utils/evaluation-samples.d.ts +6 -0
  87. package/dist/utils/evaluation-samples.d.ts.map +1 -0
  88. package/dist/utils/evaluation-samples.js +216 -0
  89. package/dist/utils/evaluation-samples.js.map +1 -0
  90. package/dist/utils/execution-diagnostics.d.ts +26 -0
  91. package/dist/utils/execution-diagnostics.d.ts.map +1 -0
  92. package/dist/utils/execution-diagnostics.js +159 -0
  93. package/dist/utils/execution-diagnostics.js.map +1 -0
  94. package/dist/utils/generation.d.ts.map +1 -1
  95. package/dist/utils/generation.js +134 -19
  96. package/dist/utils/generation.js.map +1 -1
  97. package/dist/utils/host-capabilities.d.ts.map +1 -1
  98. package/dist/utils/host-capabilities.js +20 -5
  99. package/dist/utils/host-capabilities.js.map +1 -1
  100. package/dist/utils/inferSyntheticOutputSchema.js +3 -3
  101. package/dist/utils/inferSyntheticOutputSchema.js.map +1 -1
  102. package/dist/utils/outputSchema.js +1 -1
  103. package/dist/utils/outputSchema.js.map +1 -1
  104. package/dist/utils/validateAndRepair.js +10 -10
  105. package/dist/utils/validateAndRepair.js.map +1 -1
  106. package/dist/utils/workflow-prompts/draftIntent.d.ts +1 -1
  107. package/dist/utils/workflow-prompts/draftIntent.d.ts.map +1 -1
  108. package/dist/utils/workflow-prompts/draftIntent.js +1 -1
  109. package/dist/utils/workflow-prompts/keywordExtraction.d.ts +1 -1
  110. package/dist/utils/workflow-prompts/keywordExtraction.d.ts.map +1 -1
  111. package/dist/utils/workflow-prompts/keywordExtraction.js +1 -1
  112. package/dist/utils/workflow-prompts/workflowGeneration.d.ts +1 -1
  113. package/dist/utils/workflow-prompts/workflowGeneration.d.ts.map +1 -1
  114. package/dist/utils/workflow-prompts/workflowGeneration.js +4 -4
  115. package/dist/utils/workflow-prompts/workflowMatching.d.ts +1 -1
  116. package/dist/utils/workflow-prompts/workflowMatching.d.ts.map +1 -1
  117. package/dist/utils/workflow-prompts/workflowMatching.js +1 -1
  118. package/dist/utils/workflow.d.ts +1 -0
  119. package/dist/utils/workflow.d.ts.map +1 -1
  120. package/dist/utils/workflow.js +44 -8
  121. package/dist/utils/workflow.js.map +1 -1
  122. package/package.json +27 -8
  123. package/registry-entry.json +25 -0
  124. package/src/actions/eval-code.ts +81 -0
  125. package/src/actions/index.ts +1 -0
  126. package/src/actions/workflow.ts +518 -10
  127. package/src/db/schema.ts +31 -0
  128. package/src/index.ts +9 -82
  129. package/src/lib/automations-builder.ts +11 -35
  130. package/src/lib/automations-types.ts +1 -2
  131. package/src/lib/index.ts +0 -8
  132. package/src/lib/workflow-clarification.ts +18 -13
  133. package/src/plugin-routes.ts +6 -0
  134. package/src/providers/activeWorkflows.ts +2 -2
  135. package/src/providers/workflowStatus.ts +1 -1
  136. package/src/routes/workflow-routes.ts +100 -2
  137. package/src/routes/workflows.ts +5 -1
  138. package/src/services/embedded-workflow-service.ts +447 -172
  139. package/src/services/smithers-runtime.ts +526 -0
  140. package/src/services/workflow-credential-store.ts +1 -1
  141. package/src/services/workflow-dispatch.ts +116 -13
  142. package/src/services/workflow-service.ts +186 -10
  143. package/src/trigger-routes.ts +12 -70
  144. package/src/types/index.ts +94 -2
  145. package/src/types/workflow-contracts.ts +166 -0
  146. package/src/utils/catalog.ts +2 -2
  147. package/src/utils/clarification.ts +19 -5
  148. package/src/utils/context.ts +1 -1
  149. package/src/utils/evaluation-samples.ts +239 -0
  150. package/src/utils/execution-diagnostics.ts +192 -0
  151. package/src/utils/generation.ts +224 -32
  152. package/src/utils/host-capabilities.ts +21 -5
  153. package/src/utils/inferSyntheticOutputSchema.ts +3 -3
  154. package/src/utils/outputSchema.ts +1 -1
  155. package/src/utils/validateAndRepair.ts +10 -10
  156. package/src/utils/workflow-prompts/draftIntent.ts +1 -1
  157. package/src/utils/workflow-prompts/keywordExtraction.ts +1 -1
  158. package/src/utils/workflow-prompts/workflowGeneration.ts +4 -4
  159. package/src/utils/workflow-prompts/workflowMatching.ts +1 -1
  160. package/src/utils/workflow.ts +56 -8
  161. package/dist/lib/legacy-task-migration.d.ts +0 -20
  162. package/dist/lib/legacy-task-migration.d.ts.map +0 -1
  163. package/dist/lib/legacy-task-migration.js +0 -110
  164. package/dist/lib/legacy-task-migration.js.map +0 -1
  165. package/dist/lib/legacy-text-trigger-migration.d.ts +0 -18
  166. package/dist/lib/legacy-text-trigger-migration.d.ts.map +0 -1
  167. package/dist/lib/legacy-text-trigger-migration.js +0 -131
  168. package/dist/lib/legacy-text-trigger-migration.js.map +0 -1
  169. package/src/lib/legacy-task-migration.ts +0 -143
  170. package/src/lib/legacy-text-trigger-migration.ts +0 -178
@@ -0,0 +1,192 @@
1
+ import type { WorkflowExecution } from '../types/index';
2
+
3
+ export type WorkflowExecutionTone = 'success' | 'danger' | 'warning' | 'muted';
4
+
5
+ export interface WorkflowExecutionRunRow {
6
+ nodeName: string;
7
+ status: 'success' | 'error' | 'unknown';
8
+ startTime?: number;
9
+ executionTimeMs?: number;
10
+ itemCount: number;
11
+ preview: string;
12
+ error?: string;
13
+ }
14
+
15
+ export interface WorkflowExecutionSummary {
16
+ statusLabel: string;
17
+ tone: WorkflowExecutionTone;
18
+ durationLabel: string;
19
+ nodeCount: number;
20
+ lastNode?: string;
21
+ error?: string;
22
+ }
23
+
24
+ function isRecord(value: unknown): value is Record<string, unknown> {
25
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
26
+ }
27
+
28
+ function readNumber(value: unknown): number | undefined {
29
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
30
+ }
31
+
32
+ function readString(value: unknown): string | undefined {
33
+ return typeof value === 'string' && value.trim() ? value : undefined;
34
+ }
35
+
36
+ function countMainItems(data: unknown): number {
37
+ if (!isRecord(data) || !Array.isArray(data.main)) return 0;
38
+ return data.main.reduce((total, output) => {
39
+ if (!Array.isArray(output)) return total;
40
+ return total + output.length;
41
+ }, 0);
42
+ }
43
+
44
+ function previewMainData(data: unknown): string {
45
+ if (!isRecord(data) || !Array.isArray(data.main)) return 'No output';
46
+ for (const output of data.main) {
47
+ if (!Array.isArray(output)) continue;
48
+ const first = output.find(isRecord);
49
+ if (!first) continue;
50
+ const json = isRecord(first.json) ? first.json : first;
51
+ try {
52
+ const preview = JSON.stringify(json);
53
+ return preview.length > 160 ? `${preview.slice(0, 157)}...` : preview;
54
+ } catch {
55
+ return 'Output could not be previewed';
56
+ }
57
+ }
58
+ return 'No output';
59
+ }
60
+
61
+ function getRunError(run: unknown): string | undefined {
62
+ if (!isRecord(run)) return undefined;
63
+ const error = run.error;
64
+ if (isRecord(error)) {
65
+ return readString(error.message) ?? readString(error.description);
66
+ }
67
+ return readString(error);
68
+ }
69
+
70
+ export function getWorkflowExecutionRunRows(
71
+ execution: WorkflowExecution
72
+ ): WorkflowExecutionRunRow[] {
73
+ const runData = execution.data?.resultData?.runData;
74
+ if (!runData) return [];
75
+
76
+ return Object.entries(runData).flatMap(([nodeName, runs]) => {
77
+ if (!Array.isArray(runs)) return [];
78
+ return runs.map((run): WorkflowExecutionRunRow => {
79
+ const record = isRecord(run) ? run : {};
80
+ const error = getRunError(record);
81
+ const data = record.data;
82
+ return {
83
+ nodeName,
84
+ status: error ? 'error' : execution.status === 'success' ? 'success' : 'unknown',
85
+ startTime: readNumber(record.startTime),
86
+ executionTimeMs: readNumber(record.executionTime),
87
+ itemCount: countMainItems(data),
88
+ preview: previewMainData(data),
89
+ error,
90
+ };
91
+ });
92
+ });
93
+ }
94
+
95
+ export function getWorkflowExecutionError(execution: WorkflowExecution): string | undefined {
96
+ const error = execution.data?.resultData?.error;
97
+ if (error?.message) return error.message;
98
+ for (const row of getWorkflowExecutionRunRows(execution)) {
99
+ if (row.error) return row.error;
100
+ }
101
+ return undefined;
102
+ }
103
+
104
+ export function formatWorkflowExecutionDuration(
105
+ startedAt?: string,
106
+ stoppedAt?: string | null
107
+ ): string {
108
+ if (!startedAt) return 'Unknown';
109
+ const startMs = Date.parse(startedAt);
110
+ const stopMs = stoppedAt ? Date.parse(stoppedAt) : Date.now();
111
+ if (!Number.isFinite(startMs) || !Number.isFinite(stopMs)) return 'Unknown';
112
+ const durationMs = Math.max(0, stopMs - startMs);
113
+ if (durationMs < 1000) return `${durationMs} ms`;
114
+ if (durationMs < 60_000) return `${(durationMs / 1000).toFixed(1)} s`;
115
+ return `${Math.round(durationMs / 60_000)} min`;
116
+ }
117
+
118
+ export function summarizeWorkflowExecution(execution: WorkflowExecution): WorkflowExecutionSummary {
119
+ const rows = getWorkflowExecutionRunRows(execution);
120
+ const status = execution.status;
121
+ const tone: WorkflowExecutionTone =
122
+ status === 'success'
123
+ ? 'success'
124
+ : status === 'error' || status === 'crashed' || status === 'canceled'
125
+ ? 'danger'
126
+ : status === 'running' || status === 'waiting'
127
+ ? 'warning'
128
+ : 'muted';
129
+ const statusLabel =
130
+ status === 'success'
131
+ ? 'Succeeded'
132
+ : status === 'error'
133
+ ? 'Failed'
134
+ : status === 'running'
135
+ ? 'Running'
136
+ : status === 'waiting'
137
+ ? 'Waiting'
138
+ : status.charAt(0).toUpperCase() + status.slice(1);
139
+
140
+ return {
141
+ statusLabel,
142
+ tone,
143
+ durationLabel: formatWorkflowExecutionDuration(execution.startedAt, execution.stoppedAt),
144
+ nodeCount: rows.length,
145
+ lastNode: execution.data?.resultData?.lastNodeExecuted,
146
+ error: getWorkflowExecutionError(execution),
147
+ };
148
+ }
149
+
150
+ export function formatWorkflowEngineMetrics(execution: WorkflowExecution): string | null {
151
+ const engine = execution.data?.resultData?.engine;
152
+ if (!engine) return null;
153
+ const skipped = engine.skipped > 0 ? ` / ${engine.skipped} skipped` : '';
154
+ const retries = engine.retries > 0 ? ` / ${engine.retries} retries` : '';
155
+ return `${engine.nodes} nodes / ${engine.levels} levels / ${engine.maxConcurrency} max parallel${skipped}${retries}`;
156
+ }
157
+
158
+ export function buildWorkflowExecutionDiagnostics(execution: WorkflowExecution): string {
159
+ const summary = summarizeWorkflowExecution(execution);
160
+ const rows = getWorkflowExecutionRunRows(execution);
161
+ const engineMetrics = formatWorkflowEngineMetrics(execution);
162
+ const lines = [
163
+ `Workflow execution ${execution.id}`,
164
+ `Status: ${summary.statusLabel}`,
165
+ `Workflow: ${execution.workflowId}`,
166
+ `Mode: ${execution.mode}`,
167
+ `Started: ${execution.startedAt}`,
168
+ `Stopped: ${execution.stoppedAt ?? 'still running'}`,
169
+ `Duration: ${summary.durationLabel}`,
170
+ summary.lastNode ? `Last node: ${summary.lastNode}` : null,
171
+ engineMetrics ? `Engine: ${engineMetrics}` : null,
172
+ summary.error ? `Error: ${summary.error}` : null,
173
+ ].filter((line): line is string => Boolean(line));
174
+
175
+ if (rows.length === 0) {
176
+ lines.push('Nodes: none recorded');
177
+ return lines.join('\n');
178
+ }
179
+
180
+ lines.push('Nodes:');
181
+ for (const row of rows) {
182
+ const elapsed =
183
+ typeof row.executionTimeMs === 'number' ? `${row.executionTimeMs} ms` : 'unknown';
184
+ const result = row.error ? `error=${row.error}` : `preview=${row.preview}`;
185
+ lines.push(
186
+ `- ${row.nodeName}: ${row.status}; ${row.itemCount} item${
187
+ row.itemCount === 1 ? '' : 's'
188
+ }; ${elapsed}; ${result}`
189
+ );
190
+ }
191
+ return lines.join('\n');
192
+ }
@@ -48,16 +48,179 @@ type StructuredModelRunner = {
48
48
  ): Promise<T>;
49
49
  };
50
50
 
51
+ type WorkflowTextModelType = typeof ModelType.TEXT_SMALL | typeof ModelType.TEXT_LARGE;
52
+ type WorkflowGenerateTextParams = GenerateTextParams & { model?: string };
53
+
54
+ interface WorkflowModelRouting {
55
+ model?: string;
56
+ requestedProvider?: string;
57
+ runtimeProvider?: string;
58
+ }
59
+
60
+ const WORKFLOW_MODEL_PROVIDER_KEYS = [
61
+ 'WORKFLOW_LLM_PROVIDER',
62
+ 'WORKFLOW_MODEL_PROVIDER',
63
+ 'WORKFLOW_TEST_PROVIDER',
64
+ ] as const;
65
+
66
+ const WORKFLOW_MODEL_KEYS = [
67
+ 'WORKFLOW_LLM_MODEL',
68
+ 'WORKFLOW_MODEL',
69
+ 'WORKFLOW_TEST_MODEL',
70
+ ] as const;
71
+
72
+ const WORKFLOW_RUNTIME_PROVIDER_KEYS = [
73
+ 'WORKFLOW_LLM_RUNTIME_PROVIDER',
74
+ 'WORKFLOW_MODEL_RUNTIME_PROVIDER',
75
+ ] as const;
76
+
77
+ function readStringSetting(runtime: IAgentRuntime, key: string): string | undefined {
78
+ const runtimeValue = runtime.getSetting(key);
79
+ if (typeof runtimeValue === 'string' && runtimeValue.trim().length > 0) {
80
+ return runtimeValue.trim();
81
+ }
82
+ if (typeof process !== 'undefined') {
83
+ const envValue = process.env[key];
84
+ if (typeof envValue === 'string' && envValue.trim().length > 0) {
85
+ return envValue.trim();
86
+ }
87
+ }
88
+ return undefined;
89
+ }
90
+
91
+ function readFirstStringSetting(
92
+ runtime: IAgentRuntime,
93
+ keys: readonly string[]
94
+ ): string | undefined {
95
+ for (const key of keys) {
96
+ const value = readStringSetting(runtime, key);
97
+ if (value) {
98
+ return value;
99
+ }
100
+ }
101
+ return undefined;
102
+ }
103
+
104
+ function isRecord(value: unknown): value is Record<string, unknown> {
105
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
106
+ }
107
+
108
+ function isCerebrasHost(value: string): boolean {
109
+ const host = value.toLowerCase();
110
+ return host === 'cerebras.ai' || host.endsWith('.cerebras.ai');
111
+ }
112
+
113
+ function isCerebrasBaseUrl(value: string): boolean {
114
+ try {
115
+ return isCerebrasHost(new URL(value).hostname);
116
+ } catch {
117
+ const host = value.replace(/^[a-z][a-z0-9+.-]*:\/\//i, '').split(/[/?#:]/, 1)[0];
118
+ return isCerebrasHost(host);
119
+ }
120
+ }
121
+
122
+ function inferCerebrasMode(runtime: IAgentRuntime): boolean {
123
+ const explicitProvider = readStringSetting(runtime, 'ELIZA_PROVIDER');
124
+ if (explicitProvider?.toLowerCase() === 'cerebras') {
125
+ return true;
126
+ }
127
+ const openAiBaseUrl = readStringSetting(runtime, 'OPENAI_BASE_URL');
128
+ if (openAiBaseUrl && isCerebrasBaseUrl(openAiBaseUrl)) {
129
+ return true;
130
+ }
131
+ const cerebrasKey = readStringSetting(runtime, 'CEREBRAS_API_KEY');
132
+ return !!cerebrasKey && !readStringSetting(runtime, 'OPENAI_API_KEY') && !openAiBaseUrl;
133
+ }
134
+
135
+ function resolveWorkflowModelRouting(runtime: IAgentRuntime): WorkflowModelRouting | null {
136
+ const explicitProvider = readFirstStringSetting(runtime, WORKFLOW_MODEL_PROVIDER_KEYS);
137
+ const requestedProvider =
138
+ explicitProvider ?? (inferCerebrasMode(runtime) ? 'cerebras' : undefined);
139
+ const normalizedProvider = requestedProvider?.toLowerCase();
140
+ const model =
141
+ readFirstStringSetting(runtime, WORKFLOW_MODEL_KEYS) ??
142
+ (normalizedProvider === 'cerebras'
143
+ ? (readStringSetting(runtime, 'CEREBRAS_MODEL') ?? 'gpt-oss-120b')
144
+ : undefined);
145
+ const explicitRuntimeProvider = readFirstStringSetting(runtime, WORKFLOW_RUNTIME_PROVIDER_KEYS);
146
+ const runtimeProvider =
147
+ explicitRuntimeProvider ?? (normalizedProvider === 'cerebras' ? 'openai' : requestedProvider);
148
+
149
+ if (!model && !requestedProvider && !runtimeProvider) {
150
+ return null;
151
+ }
152
+
153
+ return {
154
+ ...(model ? { model } : {}),
155
+ ...(requestedProvider ? { requestedProvider } : {}),
156
+ ...(runtimeProvider ? { runtimeProvider } : {}),
157
+ };
158
+ }
159
+
160
+ function withWorkflowModelRouting(
161
+ runtime: IAgentRuntime,
162
+ params: GenerateTextParams,
163
+ callSite: string
164
+ ): { params: WorkflowGenerateTextParams; provider?: string } {
165
+ const routing = resolveWorkflowModelRouting(runtime);
166
+ if (!routing) {
167
+ return { params };
168
+ }
169
+
170
+ const existingProviderOptions = isRecord(params.providerOptions) ? params.providerOptions : {};
171
+ const existingWorkflowOptions = isRecord(existingProviderOptions.workflow)
172
+ ? existingProviderOptions.workflow
173
+ : {};
174
+
175
+ return {
176
+ provider: routing.runtimeProvider,
177
+ params: {
178
+ ...params,
179
+ ...(routing.model ? { model: routing.model } : {}),
180
+ providerOptions: {
181
+ ...existingProviderOptions,
182
+ workflow: {
183
+ ...existingWorkflowOptions,
184
+ callSite,
185
+ ...(routing.model ? { model: routing.model } : {}),
186
+ ...(routing.requestedProvider ? { requestedProvider: routing.requestedProvider } : {}),
187
+ ...(routing.runtimeProvider ? { runtimeProvider: routing.runtimeProvider } : {}),
188
+ },
189
+ },
190
+ },
191
+ };
192
+ }
193
+
51
194
  async function useStructuredModel<T>(
52
195
  runtime: IAgentRuntime,
53
196
  prompt: string,
54
- schema: unknown
197
+ schema: unknown,
198
+ callSite: string
55
199
  ): Promise<T> {
56
200
  const structuredRuntime = runtime as IAgentRuntime & StructuredModelRunner;
57
- return (await structuredRuntime.useModel<T>(ModelType.TEXT_SMALL, {
58
- prompt,
59
- responseSchema: schema as never,
60
- })) as T;
201
+ const routed = withWorkflowModelRouting(
202
+ runtime,
203
+ {
204
+ prompt,
205
+ responseSchema: schema as never,
206
+ },
207
+ callSite
208
+ );
209
+ return (await structuredRuntime.useModel<T>(
210
+ ModelType.TEXT_SMALL,
211
+ routed.params as GenerateTextParams & { responseSchema: unknown },
212
+ routed.provider
213
+ )) as T;
214
+ }
215
+
216
+ async function useWorkflowTextModel(
217
+ runtime: IAgentRuntime,
218
+ modelType: WorkflowTextModelType,
219
+ params: GenerateTextParams,
220
+ callSite: string
221
+ ): Promise<string> {
222
+ const routed = withWorkflowModelRouting(runtime, params, callSite);
223
+ return (await runtime.useModel(modelType, routed.params, routed.provider)) as string;
61
224
  }
62
225
 
63
226
  /**
@@ -85,7 +248,8 @@ export async function extractKeywords(
85
248
  result = await useStructuredModel<KeywordExtractionResult>(
86
249
  runtime,
87
250
  `${KEYWORD_EXTRACTION_SYSTEM_PROMPT}${buildPreferredProvidersDirective(preferredProviders)}\n\nUser request: ${userPrompt}`,
88
- keywordExtractionSchema
251
+ keywordExtractionSchema,
252
+ 'extractKeywords'
89
253
  );
90
254
  } catch (error) {
91
255
  const errMsg = error instanceof Error ? error.message : String(error);
@@ -97,7 +261,7 @@ export async function extractKeywords(
97
261
  }
98
262
 
99
263
  // Validate structure
100
- if (!result?.keywords || !Array.isArray(result.keywords)) {
264
+ if (!result || typeof result !== 'object' || !Array.isArray(result.keywords)) {
101
265
  logger.error(
102
266
  {
103
267
  src: 'plugin:workflow:generation:keywords',
@@ -153,7 +317,8 @@ ${workflowList}`;
153
317
  result = await useStructuredModel<WorkflowMatchResult>(
154
318
  runtime,
155
319
  `${WORKFLOW_MATCHING_SYSTEM_PROMPT}\n\n${userPrompt}`,
156
- workflowMatchingSchema
320
+ workflowMatchingSchema,
321
+ 'matchWorkflow'
157
322
  );
158
323
  } catch (innerError) {
159
324
  const errMsg = innerError instanceof Error ? innerError.message : String(innerError);
@@ -218,7 +383,8 @@ ${draftSummary}
218
383
  ## User Message
219
384
 
220
385
  ${userMessage}`,
221
- draftIntentSchema
386
+ draftIntentSchema,
387
+ 'classifyDraftIntent'
222
388
  );
223
389
  } catch (error) {
224
390
  const errMsg = error instanceof Error ? error.message : String(error);
@@ -233,7 +399,7 @@ ${userMessage}`,
233
399
  }
234
400
 
235
401
  const validIntents = ['confirm', 'cancel', 'modify', 'new'] as const;
236
- if (!result?.intent || !validIntents.includes(result.intent as (typeof validIntents)[number])) {
402
+ if (!result.intent || !validIntents.includes(result.intent as (typeof validIntents)[number])) {
237
403
  logger.warn(
238
404
  { src: 'plugin:workflow:generation:intent' },
239
405
  `Invalid intent from LLM: ${JSON.stringify(result)}, re-showing preview`
@@ -301,11 +467,16 @@ Return the COMPLETE corrected workflow JSON. Preserve every field that was not p
301
467
 
302
468
  let response: string;
303
469
  try {
304
- response = (await runtime.useModel(ModelType.TEXT_LARGE, {
305
- prompt: fixPrompt,
306
- temperature: 0,
307
- responseFormat: { type: 'json_object' },
308
- })) as string;
470
+ response = await useWorkflowTextModel(
471
+ runtime,
472
+ ModelType.TEXT_LARGE,
473
+ {
474
+ prompt: fixPrompt,
475
+ temperature: 0,
476
+ responseFormat: { type: 'json_object' },
477
+ },
478
+ 'fixWorkflowErrors'
479
+ );
309
480
  } catch (err) {
310
481
  const errMsg = err instanceof Error ? err.message : String(err);
311
482
  logger.error(
@@ -501,11 +672,16 @@ async function callLlmAndParseWorkflow(
501
672
  context: 'generateWorkflow' | 'modifyWorkflow'
502
673
  ): Promise<WorkflowDefinition> {
503
674
  const callOnce = async (extraInstruction?: string): Promise<string> =>
504
- (await runtime.useModel(ModelType.TEXT_LARGE, {
505
- prompt: extraInstruction ? `${prompt}\n\n${extraInstruction}` : prompt,
506
- temperature: 0,
507
- responseFormat: { type: 'json_object' },
508
- })) as string;
675
+ useWorkflowTextModel(
676
+ runtime,
677
+ ModelType.TEXT_LARGE,
678
+ {
679
+ prompt: extraInstruction ? `${prompt}\n\n${extraInstruction}` : prompt,
680
+ temperature: 0,
681
+ responseFormat: { type: 'json_object' },
682
+ },
683
+ context
684
+ );
509
685
 
510
686
  const firstResponse = await callOnce();
511
687
  try {
@@ -630,9 +806,14 @@ export async function formatActionResponse(
630
806
  data: Record<string, unknown>
631
807
  ): Promise<string> {
632
808
  try {
633
- const response = await runtime.useModel(ModelType.TEXT_SMALL, {
634
- prompt: `${ACTION_RESPONSE_SYSTEM_PROMPT}\n\nType: ${responseType}\n\nData:\n${formatActionDataForPrompt(data)}`,
635
- });
809
+ const response = await useWorkflowTextModel(
810
+ runtime,
811
+ ModelType.TEXT_SMALL,
812
+ {
813
+ prompt: `${ACTION_RESPONSE_SYSTEM_PROMPT}\n\nType: ${responseType}\n\nData:\n${formatActionDataForPrompt(data)}`,
814
+ },
815
+ 'formatActionResponse'
816
+ );
636
817
 
637
818
  return (response as string).trim();
638
819
  } catch (error) {
@@ -713,7 +894,8 @@ export async function assessFeasibility(
713
894
  `\n\n## Removed Integrations (unavailable)\n${removedList}` +
714
895
  `\n\n## Available Service Integrations\n${availableList}` +
715
896
  `\n\n## Available Utility Nodes\n${utilityList}`,
716
- feasibilitySchema
897
+ feasibilitySchema,
898
+ 'assessFeasibility'
717
899
  );
718
900
 
719
901
  return result;
@@ -756,10 +938,15 @@ export async function correctFieldReferences(
756
938
  ref.expression
757
939
  ).replace('{availableFields}', ref.availableFields.join('\n'));
758
940
 
759
- const corrected = await runtime.useModel(ModelType.TEXT_SMALL, {
760
- prompt: `${FIELD_CORRECTION_SYSTEM_PROMPT}\n\n${userPrompt}`,
761
- temperature: 0,
762
- });
941
+ const corrected = await useWorkflowTextModel(
942
+ runtime,
943
+ ModelType.TEXT_SMALL,
944
+ {
945
+ prompt: `${FIELD_CORRECTION_SYSTEM_PROMPT}\n\n${userPrompt}`,
946
+ temperature: 0,
947
+ },
948
+ 'correctFieldReferences'
949
+ );
763
950
 
764
951
  const cleaned = (corrected as string).trim();
765
952
  return {
@@ -915,10 +1102,15 @@ export async function correctParameterNames(
915
1102
  .replace('{currentParams}', JSON.stringify(detection.currentParams, null, 2))
916
1103
  .replace('{propertyDefs}', JSON.stringify(detection.propertyDefs, null, 2));
917
1104
 
918
- const response = await runtime.useModel(ModelType.TEXT_SMALL, {
919
- prompt: `${PARAM_CORRECTION_SYSTEM_PROMPT}\n\n${userPrompt}`,
920
- temperature: 0,
921
- });
1105
+ const response = await useWorkflowTextModel(
1106
+ runtime,
1107
+ ModelType.TEXT_SMALL,
1108
+ {
1109
+ prompt: `${PARAM_CORRECTION_SYSTEM_PROMPT}\n\n${userPrompt}`,
1110
+ temperature: 0,
1111
+ },
1112
+ 'correctParameterNames'
1113
+ );
922
1114
 
923
1115
  const cleaned = (response as string)
924
1116
  .replace(/^[\s\S]*?```(?:json)?\s*\n?/i, '')
@@ -31,7 +31,7 @@ export function detectHostCapabilities(): HostCapabilities {
31
31
  // Cloudflare Workers — runtime exposes navigator.userAgent === 'Cloudflare-Workers'.
32
32
  if (
33
33
  typeof navigator !== 'undefined' &&
34
- typeof navigator?.userAgent === 'string' &&
34
+ typeof navigator.userAgent === 'string' &&
35
35
  navigator.userAgent.includes('Cloudflare-Workers')
36
36
  ) {
37
37
  return {
@@ -45,24 +45,40 @@ export function detectHostCapabilities(): HostCapabilities {
45
45
  }
46
46
 
47
47
  // Capacitor (iOS / Android). Capacitor exposes a global on the window/globalThis.
48
+ // `longRunning` is conditional on a registered BackgroundRunner plugin: without
49
+ // it, the JS context suspends within seconds of backgrounding (iOS WKWebView
50
+ // aggressively) and any `requiresLongRunning` node (scheduleTrigger, etc.)
51
+ // is dead the moment the user leaves the app. With it, the OS may wake the
52
+ // runner JS context periodically (≥15 min on both platforms) and the engine
53
+ // can argue it has cross-suspend continuity. We detect by probing for the
54
+ // plugin instance rather than trusting a build-time flag.
48
55
  const capacitor: unknown = Reflect.get(globalThis, 'Capacitor');
49
56
  if (capacitor && typeof capacitor === 'object') {
57
+ const plugins: unknown = Reflect.get(capacitor as object, 'Plugins');
58
+ const bgRunner: unknown =
59
+ plugins && typeof plugins === 'object'
60
+ ? Reflect.get(plugins as object, 'BackgroundRunner')
61
+ : undefined;
62
+ const hasBgRunner = typeof bgRunner === 'object' && bgRunner !== null;
50
63
  return {
51
64
  fs: false,
52
65
  inbound: false, // No public HTTP without plugin-tunnel
53
- longRunning: true, // App stays alive while running; bg runner handles wake-ups
66
+ longRunning: hasBgRunner,
54
67
  childProcess: false,
55
68
  net: false,
56
- label: 'Mobile (Capacitor)',
69
+ label: hasBgRunner
70
+ ? 'Mobile (Capacitor + BackgroundRunner)'
71
+ : 'Mobile (Capacitor, foreground-only)',
57
72
  };
58
73
  }
59
74
 
60
- // Browser without Capacitor — pure web.
75
+ // Browser without Capacitor — pure web. Browser tabs can be backgrounded
76
+ // and discarded; treat as short-lived for scheduling purposes.
61
77
  if (typeof window !== 'undefined' && typeof process === 'undefined') {
62
78
  return {
63
79
  fs: false,
64
80
  inbound: false,
65
- longRunning: true,
81
+ longRunning: false,
66
82
  childProcess: false,
67
83
  net: false,
68
84
  label: 'Browser',
@@ -39,7 +39,7 @@ function inferSummarizeFields(node: WorkflowNode): string[] | null {
39
39
  }
40
40
  const out: string[] = [];
41
41
  for (const entry of fields.values) {
42
- if (typeof entry?.aggregation !== 'string' || typeof entry?.field !== 'string') {
42
+ if (typeof entry.aggregation !== 'string' || typeof entry.field !== 'string') {
43
43
  continue;
44
44
  }
45
45
  const prefix = SUMMARIZE_AGG_PREFIX[entry.aggregation];
@@ -64,7 +64,7 @@ function inferSetFields(node: WorkflowNode): string[] | null {
64
64
  const modern = params.assignments as { assignments?: Array<{ name?: string }> } | undefined;
65
65
  if (modern?.assignments && Array.isArray(modern.assignments)) {
66
66
  const names = modern.assignments
67
- .map((a) => a?.name)
67
+ .map((a) => a.name)
68
68
  .filter((n): n is string => typeof n === 'string' && n.length > 0);
69
69
  if (names.length > 0) {
70
70
  return names;
@@ -80,7 +80,7 @@ function inferSetFields(node: WorkflowNode): string[] | null {
80
80
  continue;
81
81
  }
82
82
  for (const v of arr) {
83
- if (typeof v?.name === 'string' && v.name.length > 0) {
83
+ if (typeof v.name === 'string' && v.name.length > 0) {
84
84
  names.push(v.name);
85
85
  }
86
86
  }
@@ -93,7 +93,7 @@ export function loadTriggerOutputSchema(
93
93
  return null;
94
94
  }
95
95
  const entry = TRIGGER_SCHEMAS[nodeType];
96
- if (!entry?.outputSchema?.properties || Object.keys(entry.outputSchema.properties).length === 0) {
96
+ if (!entry?.outputSchema.properties || Object.keys(entry.outputSchema.properties).length === 0) {
97
97
  return null;
98
98
  }
99
99
  return {