@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
package/src/db/schema.ts CHANGED
@@ -45,6 +45,30 @@ export const embeddedWorkflows = workflowSchema.table(
45
45
  })
46
46
  );
47
47
 
48
+ export const workflowRevisions = workflowSchema.table(
49
+ 'workflow_revisions',
50
+ {
51
+ id: text('id').primaryKey(),
52
+ workflowId: text('workflow_id').notNull(),
53
+ versionId: text('version_id').notNull(),
54
+ name: text('name').notNull(),
55
+ active: boolean('active').default(false).notNull(),
56
+ workflow: jsonb('workflow').$type<WorkflowDefinition>().notNull(),
57
+ createdAt: text('created_at').notNull(),
58
+ updatedAt: text('updated_at').notNull(),
59
+ capturedAt: text('captured_at').notNull(),
60
+ operation: text('operation').notNull(),
61
+ },
62
+ (table) => ({
63
+ workflowIdx: index('idx_workflow_revisions_workflow_id').on(table.workflowId),
64
+ versionIdx: uniqueIndex('idx_workflow_revisions_workflow_version').on(
65
+ table.workflowId,
66
+ table.versionId
67
+ ),
68
+ capturedAtIdx: index('idx_workflow_revisions_captured_at').on(table.capturedAt),
69
+ })
70
+ );
71
+
48
72
  export const embeddedExecutions = workflowSchema.table(
49
73
  'embedded_executions',
50
74
  {
@@ -56,11 +80,18 @@ export const embeddedExecutions = workflowSchema.table(
56
80
  startedAt: text('started_at').notNull(),
57
81
  stoppedAt: text('stopped_at'),
58
82
  execution: jsonb('execution').$type<WorkflowExecution>().notNull(),
83
+ /**
84
+ * Per-dispatch idempotency key. Scheduled dispatches use
85
+ * `${workflowId}:${minuteBucket}` so re-arms inside the same minute
86
+ * collapse to a single execution. Null for ad-hoc / manual runs.
87
+ */
88
+ idempotencyKey: text('idempotency_key'),
59
89
  },
60
90
  (table) => ({
61
91
  workflowIdx: index('idx_embedded_executions_workflow_id').on(table.workflowId),
62
92
  statusIdx: index('idx_embedded_executions_status').on(table.status),
63
93
  startedAtIdx: index('idx_embedded_executions_started_at').on(table.startedAt),
94
+ idempotencyKeyIdx: index('idx_embedded_executions_idempotency_key').on(table.idempotencyKey),
64
95
  })
65
96
  );
66
97
 
package/src/index.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { type IAgentRuntime, logger, type Plugin } from '@elizaos/core';
2
- import { workflowAction } from './actions/index';
2
+ import { evalCodeAction, workflowAction } from './actions/index';
3
3
  import * as dbSchema from './db/index';
4
- import { migrateLegacyTextTriggers, migrateLegacyWorkbenchTasks } from './lib/index';
5
4
  import {
6
5
  activeWorkflowsProvider,
7
6
  pendingDraftProvider,
@@ -11,7 +10,6 @@ import { workflowRoutes } from './routes/index';
11
10
  import {
12
11
  EmbeddedWorkflowService,
13
12
  registerWorkflowDispatchService,
14
- WORKFLOW_SERVICE_TYPE,
15
13
  WorkflowCredentialStore,
16
14
  WorkflowService,
17
15
  } from './services/index';
@@ -55,9 +53,15 @@ export const workflowPlugin: Plugin = {
55
53
 
56
54
  services: [EmbeddedWorkflowService, WorkflowService, WorkflowCredentialStore],
57
55
 
56
+ async dispose(runtime: IAgentRuntime) {
57
+ await runtime.getService<WorkflowService>(WorkflowService.serviceType)?.stop();
58
+ await runtime.getService<EmbeddedWorkflowService>(EmbeddedWorkflowService.serviceType)?.stop();
59
+ await runtime.getService<WorkflowCredentialStore>(WorkflowCredentialStore.serviceType)?.stop();
60
+ },
61
+
58
62
  schema: dbSchema,
59
63
 
60
- actions: [workflowAction],
64
+ actions: [workflowAction, evalCodeAction],
61
65
 
62
66
  providers: [workflowStatusProvider, activeWorkflowsProvider, pendingDraftProvider],
63
67
 
@@ -66,7 +70,7 @@ export const workflowPlugin: Plugin = {
66
70
  init: async (_config: Record<string, string>, runtime: IAgentRuntime): Promise<void> => {
67
71
  // Check for pre-configured credentials (optional)
68
72
  // Note: runtime.getSetting() only returns primitives — nested objects must be read directly
69
- const workflowSettings = runtime.character?.settings?.workflows as
73
+ const workflowSettings = runtime.character.settings?.workflows as
70
74
  | { credentials?: Record<string, string> }
71
75
  | undefined;
72
76
  if (workflowSettings?.credentials) {
@@ -83,13 +87,6 @@ export const workflowPlugin: Plugin = {
83
87
  // runtime.getService("WORKFLOW_DISPATCH").execute(workflowId).
84
88
  registerWorkflowDispatchService(runtime);
85
89
 
86
- // Schedule one-shot legacy migrations off the init critical path. Service
87
- // start-order is not guaranteed at init: WorkflowService may not be in
88
- // the registry yet. Poll up to 10 times (1s spacing) and bail quietly
89
- // if it never appears. Each migration is idempotent so a duplicate run
90
- // on a future boot is harmless.
91
- scheduleLegacyMigrations(runtime);
92
-
93
90
  logger.info(
94
91
  { src: 'plugin:workflow:plugin:init' },
95
92
  'Workflow Plugin initialized successfully (in-process runtime)'
@@ -97,76 +94,6 @@ export const workflowPlugin: Plugin = {
97
94
  },
98
95
  };
99
96
 
100
- const MIGRATION_RETRY_LIMIT = 10;
101
- const MIGRATION_RETRY_INTERVAL_MS = 1000;
102
-
103
- function scheduleLegacyMigrations(runtime: IAgentRuntime): void {
104
- let attempts = 0;
105
- const tick = (): void => {
106
- attempts += 1;
107
- const ready = runtime.getService(WORKFLOW_SERVICE_TYPE);
108
- if (!ready) {
109
- if (attempts >= MIGRATION_RETRY_LIMIT) {
110
- logger.warn(
111
- { src: 'plugin:workflow:plugin:migration' },
112
- `WorkflowService still not registered after ${MIGRATION_RETRY_LIMIT} retries; legacy migrations will run on next boot`
113
- );
114
- return;
115
- }
116
- setTimeout(tick, MIGRATION_RETRY_INTERVAL_MS);
117
- return;
118
- }
119
- void runLegacyMigrations(runtime);
120
- };
121
- // Defer the first attempt off the init stack so the host runtime can
122
- // finish wiring before we probe the service registry.
123
- setImmediate(tick);
124
- }
125
-
126
- async function runLegacyMigrations(runtime: IAgentRuntime): Promise<void> {
127
- try {
128
- const summary = await migrateLegacyWorkbenchTasks(runtime);
129
- logger.info(
130
- {
131
- src: 'plugin:workflow:plugin:migration',
132
- migrated: summary.migrated,
133
- skipped: summary.skipped,
134
- failed: summary.failed,
135
- },
136
- `Workbench-task migration: ${summary.migrated} migrated, ${summary.skipped} skipped, ${summary.failed} failed`
137
- );
138
- } catch (err) {
139
- logger.warn(
140
- {
141
- src: 'plugin:workflow:plugin:migration',
142
- err: err instanceof Error ? err.message : String(err),
143
- },
144
- 'Workbench-task migration threw; continuing'
145
- );
146
- }
147
-
148
- try {
149
- const summary = await migrateLegacyTextTriggers(runtime);
150
- logger.info(
151
- {
152
- src: 'plugin:workflow:plugin:migration',
153
- migrated: summary.migrated,
154
- skipped: summary.skipped,
155
- failed: summary.failed,
156
- },
157
- `Text-trigger migration: ${summary.migrated} migrated, ${summary.skipped} skipped, ${summary.failed} failed`
158
- );
159
- } catch (err) {
160
- logger.warn(
161
- {
162
- src: 'plugin:workflow:plugin:migration',
163
- err: err instanceof Error ? err.message : String(err),
164
- },
165
- 'Text-trigger migration threw; continuing'
166
- );
167
- }
168
- }
169
-
170
97
  export default workflowPlugin;
171
98
 
172
99
  export * from './plugin-routes.js';
@@ -90,35 +90,17 @@ function normalizeDateValue(value: unknown): string | null {
90
90
  }
91
91
 
92
92
  function resolveAgentName(runtime: AgentRuntime): string {
93
- return runtime.character?.name?.trim() || 'Eliza';
93
+ return runtime.character.name?.trim() || 'Eliza';
94
94
  }
95
95
 
96
96
  function isSystemTask(task: WorkbenchTaskView): boolean {
97
97
  if (SYSTEM_TASK_NAMES.has(task.name)) {
98
98
  return true;
99
99
  }
100
- const tags = new Set(task.tags ?? []);
100
+ const tags = new Set(task.tags);
101
101
  return tags.has('queue') && tags.has('repeat');
102
102
  }
103
103
 
104
- /**
105
- * True when the raw runtime task carries the `migratedToWorkflowId` metadata
106
- * flag set by `migrateLegacyWorkbenchTasks`. The workflow representation is
107
- * already surfaced by the workflow listing path; the original task should not
108
- * also appear as a coordinator-text item.
109
- */
110
- function taskHasMigrationFlag(rawTasks: Task[], taskId: string): boolean {
111
- for (const raw of rawTasks) {
112
- if (raw.id !== taskId) continue;
113
- const meta = isRecord(raw.metadata) ? raw.metadata : null;
114
- if (meta && typeof meta.migratedToWorkflowId === 'string') {
115
- return true;
116
- }
117
- return false;
118
- }
119
- return false;
120
- }
121
-
122
104
  function choosePreferredSystemTask(
123
105
  current: WorkbenchTaskView,
124
106
  candidate: WorkbenchTaskView
@@ -263,8 +245,7 @@ async function listTriggerTasks(runtime: AgentRuntime): Promise<Task[]> {
263
245
 
264
246
  const merged = new Map<string, Task>();
265
247
  for (const task of [...triggerTasks, ...heartbeatTasks]) {
266
- const key =
267
- task.id ?? `${task.name ?? ''}:${task.description ?? ''}:${(task.tags ?? []).join(',')}`;
248
+ const key = task.id ?? `${task.name}:${task.description ?? ''}:${(task.tags ?? []).join(',')}`;
268
249
  if (!merged.has(key)) {
269
250
  merged.set(key, task);
270
251
  }
@@ -296,7 +277,7 @@ function buildCoordinatorTaskItem(
296
277
  };
297
278
  }
298
279
 
299
- function buildCoordinatorTriggerItem(
280
+ function _buildCoordinatorTriggerItem(
300
281
  trigger: TriggerSummary,
301
282
  room: AutomationRoomRecord | undefined
302
283
  ): AutomationItem {
@@ -455,7 +436,7 @@ function normalizeLastExecution(raw: WorkflowExecution): AutomationLastExecution
455
436
  }
456
437
 
457
438
  function getWorkflowService(runtime: AgentRuntime): WorkflowService | null {
458
- const candidate = runtime.getService?.(WORKFLOW_SERVICE_TYPE);
439
+ const candidate = runtime.getService(WORKFLOW_SERVICE_TYPE);
459
440
  return (candidate as WorkflowService | null) ?? null;
460
441
  }
461
442
 
@@ -524,7 +505,7 @@ export async function buildAutomationListResponse(
524
505
  .filter((room) => room.metadata.taskId)
525
506
  .map((room) => [room.metadata.taskId as string, room])
526
507
  );
527
- const triggerRooms = new Map(
508
+ const _triggerRooms = new Map(
528
509
  rooms
529
510
  .filter((room) => room.metadata.triggerId)
530
511
  .map((room) => [room.metadata.triggerId as string, room])
@@ -548,11 +529,6 @@ export async function buildAutomationListResponse(
548
529
  allTasks
549
530
  .map((task) => toWorkbenchTaskView(task))
550
531
  .filter((task): task is WorkbenchTaskView => task !== null)
551
- // Tasks migrated to workflows by plugin-workflow's boot migration carry
552
- // a `migratedToWorkflowId` metadata flag; their workflow representation
553
- // is already in the workflowItemsById map below, so skip them here to
554
- // avoid duplicate Automations entries.
555
- .filter((task) => !taskHasMigrationFlag(allTasks, task.id))
556
532
  );
557
533
 
558
534
  const triggerTaskRecords = await listTriggerTasks(runtime);
@@ -608,7 +584,7 @@ export async function buildAutomationListResponse(
608
584
  // current workflow list is an ORPHAN: the workflow was deleted but the chat
609
585
  // room/conversation wasn't cleaned up. Surfacing those creates ghost
610
586
  // rows the user can't dismiss. Skip them; the UI's deleteWorkflow path
611
- // also deletes the conversation now, so future deletions won't leak rooms.
587
+ // also deletes the conversation, so later deletions won't leak rooms.
612
588
  const workflowOffline = workflowFetchError !== null;
613
589
  if (workflowOffline) {
614
590
  for (const [workflowId, room] of workflowRooms.entries()) {
@@ -642,15 +618,15 @@ export async function buildAutomationListResponse(
642
618
  );
643
619
  }
644
620
 
645
- const coordinatorTriggerItems = triggerItems
646
- .filter((trigger) => trigger.kind !== 'workflow')
647
- .map((trigger) => buildCoordinatorTriggerItem(trigger, triggerRooms.get(trigger.id)));
621
+ const coordinatorTriggerItems = triggerItems.map((trigger) =>
622
+ _buildCoordinatorTriggerItem(trigger, _triggerRooms.get(trigger.id))
623
+ );
648
624
 
649
625
  const automations = [
650
626
  ...automationDraftItems,
651
627
  ...workflowDraftItems,
652
- ...taskItems,
653
628
  ...coordinatorTriggerItems,
629
+ ...taskItems,
654
630
  ...workflowItemsById.values(),
655
631
  ].sort(compareAutomationItems);
656
632
 
@@ -25,7 +25,6 @@ export type ConversationScope =
25
25
  | 'page-connectors'
26
26
  | 'page-phone'
27
27
  | 'page-plugins'
28
- | 'page-lifeops'
29
28
  | 'page-settings'
30
29
  | 'page-wallet'
31
30
  | 'page-browser'
@@ -64,7 +63,7 @@ export function isAutomationConversationMetadata(
64
63
  export type TriggerType = 'interval' | 'once' | 'cron' | 'event';
65
64
  export type TriggerWakeMode = 'inject_now' | 'next_autonomy_cycle';
66
65
  export type TriggerLastStatus = 'success' | 'error' | 'skipped';
67
- export type TriggerKind = 'text' | 'workflow';
66
+ export type TriggerKind = 'workflow';
68
67
 
69
68
  export interface TriggerSummary {
70
69
  id: string;
package/src/lib/index.ts CHANGED
@@ -1,8 +0,0 @@
1
- export {
2
- type LegacyTaskMigrationSummary,
3
- migrateLegacyWorkbenchTasks,
4
- } from './legacy-task-migration';
5
- export {
6
- type LegacyTextTriggerMigrationSummary,
7
- migrateLegacyTextTriggers,
8
- } from './legacy-text-trigger-migration';
@@ -41,6 +41,10 @@ type RawStructuredClarification = Partial<WorkflowClarificationRequest> & {
41
41
  question: string;
42
42
  };
43
43
 
44
+ function isRecord(value: unknown): value is Record<string, unknown> {
45
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
46
+ }
47
+
44
48
  const VALID_KINDS: ReadonlySet<WorkflowClarificationRequest['kind']> = new Set([
45
49
  'target_channel',
46
50
  'target_server',
@@ -330,24 +334,25 @@ function appendUserNote(draft: Record<string, unknown>, value: string): void {
330
334
 
331
335
  export function applyResolutions(
332
336
  draft: Record<string, unknown>,
333
- resolutions: ReadonlyArray<WorkflowClarificationResolution>
337
+ resolutions: ReadonlyArray<unknown>
334
338
  ): { ok: true } | { ok: false; error: string; paramPath?: string } {
335
339
  for (const r of resolutions) {
336
- if (!r || typeof r.paramPath !== 'string') {
340
+ if (!isRecord(r) || typeof r.paramPath !== 'string') {
337
341
  return { ok: false, error: 'resolution missing paramPath' };
338
342
  }
339
- if (typeof r.value !== 'string') {
343
+ const { paramPath, value } = r;
344
+ if (typeof value !== 'string') {
340
345
  return {
341
346
  ok: false,
342
347
  error: 'resolution value must be a string',
343
- paramPath: r.paramPath,
348
+ paramPath,
344
349
  };
345
350
  }
346
- if (r.paramPath.length === 0) {
351
+ if (paramPath.length === 0) {
347
352
  // Free-form clarification with no field to wire into. Record the user's
348
353
  // answer under draft._meta.userNotes so subsequent LLM iterations can
349
354
  // consume the context, but don't mutate the workflow itself.
350
- appendUserNote(draft, r.value);
355
+ appendUserNote(draft, value);
351
356
  continue;
352
357
  }
353
358
  // Surface structural parse errors (unterminated bracket, empty
@@ -355,18 +360,18 @@ export function applyResolutions(
355
360
  // malformed LLM emission and cannot be silently recovered into
356
361
  // userNotes without losing the failure mode in the metrics pipeline.
357
362
  try {
358
- parseParamPath(r.paramPath);
363
+ parseParamPath(paramPath);
359
364
  } catch (err) {
360
365
  return {
361
366
  ok: false,
362
367
  error: `paramPath is structurally invalid: ${
363
368
  err instanceof Error ? err.message : String(err)
364
369
  }`,
365
- paramPath: r.paramPath,
370
+ paramPath,
366
371
  };
367
372
  }
368
373
  try {
369
- setByDotPath(draft, r.paramPath, r.value);
374
+ setByDotPath(draft, paramPath, value);
370
375
  } catch (err) {
371
376
  // Lookup-time failure: the path parsed cleanly but didn't resolve
372
377
  // against the current draft (e.g. references a node the LLM didn't
@@ -380,11 +385,11 @@ export function applyResolutions(
380
385
  {
381
386
  src: 'plugin:workflow:clarification:applyResolutions',
382
387
  err: errMsg,
383
- paramPath: r.paramPath,
388
+ paramPath,
384
389
  },
385
- `setByDotPath failed for paramPath "${r.paramPath}"; recording "${r.value}" as a free-form note instead`
390
+ `setByDotPath failed for paramPath "${paramPath}"; recording "${value}" as a free-form note instead`
386
391
  );
387
- appendUserNote(draft, r.value);
392
+ appendUserNote(draft, value);
388
393
  }
389
394
  }
390
395
  return { ok: true };
@@ -452,7 +457,7 @@ export function pruneResolvedClarifications(
452
457
 
453
458
  /**
454
459
  * Subset of `ElizaConnectorTargetCatalog` used by the route. Declared here
455
- * (vs. imported from the service) so route tests can stub it without
460
+ * (vs. imported from the service) so route tests can provide it without
456
461
  * spinning up the full service.
457
462
  */
458
463
  export interface CatalogLike {
@@ -145,6 +145,12 @@ const workflowRouteList: Route[] = [
145
145
  rawPath: true,
146
146
  handler: workflowHandler,
147
147
  },
148
+ {
149
+ type: 'GET',
150
+ path: '/api/workflow/workflows/:id/evaluation-samples',
151
+ rawPath: true,
152
+ handler: workflowHandler,
153
+ },
148
154
  // Cross-cutting `/api/automations` surface — combines workflows, triggers,
149
155
  // workbench tasks, and draft conversations into a single list view.
150
156
  {
@@ -44,7 +44,7 @@ export const activeWorkflowsProvider: Provider = {
44
44
  .slice(0, 20)
45
45
  .map((wf) => {
46
46
  const status = wf.active ? 'ACTIVE' : 'INACTIVE';
47
- const nodeCount = wf.nodes?.length || 0;
47
+ const nodeCount = wf.nodes.length || 0;
48
48
  return `- **${wf.name}** (ID: ${wf.id}, Status: ${status}, Nodes: ${nodeCount})`;
49
49
  })
50
50
  .join('\n');
@@ -58,7 +58,7 @@ export const activeWorkflowsProvider: Provider = {
58
58
  id: wf.id,
59
59
  name: wf.name,
60
60
  active: wf.active || false,
61
- nodeCount: wf.nodes?.length || 0,
61
+ nodeCount: wf.nodes.length || 0,
62
62
  })),
63
63
  },
64
64
  values: {
@@ -42,7 +42,7 @@ export const workflowStatusProvider: Provider = {
42
42
  for (const workflow of workflows.slice(0, 10)) {
43
43
  const statusEmoji = workflow.active ? '✅' : '⏸️';
44
44
  status += `${statusEmoji} ${workflow.name} (ID: ${workflow.id})\n`;
45
- status += ` Nodes: ${workflow.nodes?.length || 0}\n`;
45
+ status += ` Nodes: ${workflow.nodes.length || 0}\n`;
46
46
 
47
47
  // Try to get last execution (if possible)
48
48
  try {
@@ -73,10 +73,13 @@ function getWorkflowService(ctx: WorkflowRouteContext): WorkflowService | null {
73
73
  return (ctx.runtime?.getService?.(WORKFLOW_SERVICE_TYPE) as WorkflowService | null) ?? null;
74
74
  }
75
75
 
76
+ function isCatalogLike(value: unknown): value is CatalogLike {
77
+ return isRecord(value) && typeof value.listGroups === 'function';
78
+ }
79
+
76
80
  function getConnectorTargetCatalog(ctx: WorkflowRouteContext): CatalogLike | null {
77
81
  const raw = ctx.runtime?.getService?.('connector_target_catalog');
78
- const candidate = raw as unknown as CatalogLike | undefined;
79
- return candidate && typeof candidate.listGroups === 'function' ? candidate : null;
82
+ return isCatalogLike(raw) ? raw : null;
80
83
  }
81
84
 
82
85
  function isRecord(value: unknown): value is Record<string, unknown> {
@@ -247,6 +250,33 @@ async function handleGet(
247
250
  sendJson(ctx, 200, await service.getWorkflow(id));
248
251
  }
249
252
 
253
+ async function handleListRevisions(
254
+ ctx: WorkflowRouteContext,
255
+ service: WorkflowService,
256
+ id: string
257
+ ): Promise<void> {
258
+ const url = new URL(`http://x${ctx.req.url ?? ''}`);
259
+ const rawLimit = url.searchParams.get('limit');
260
+ const limit = Math.min(Math.max(1, Number(rawLimit) || 20), 50);
261
+ const [workflow, revisions] = await Promise.all([
262
+ service.getWorkflow(id),
263
+ service.listWorkflowRevisions(id, limit),
264
+ ]);
265
+ sendJson(ctx, 200, {
266
+ currentVersionId: workflow.versionId,
267
+ revisions,
268
+ });
269
+ }
270
+
271
+ async function handleRestoreRevision(
272
+ ctx: WorkflowRouteContext,
273
+ service: WorkflowService,
274
+ id: string,
275
+ versionId: string
276
+ ): Promise<void> {
277
+ sendJson(ctx, 200, await service.restoreWorkflowRevision(id, versionId));
278
+ }
279
+
250
280
  async function handleGenerate(ctx: WorkflowRouteContext, service: WorkflowService): Promise<void> {
251
281
  const body = await readJsonBody(ctx.req, ctx.res);
252
282
  if (!isRecord(body)) {
@@ -392,6 +422,37 @@ async function handleListExecutions(
392
422
  sendJson(ctx, 200, { executions: response.data });
393
423
  }
394
424
 
425
+ async function handleEvaluationSamples(
426
+ ctx: WorkflowRouteContext,
427
+ service: WorkflowService,
428
+ id: string
429
+ ): Promise<void> {
430
+ const url = new URL(`http://x${ctx.req.url ?? ''}`);
431
+ const rawLimit = url.searchParams.get('limit');
432
+ const limit = Math.min(Math.max(1, Number(rawLimit) || 10), 50);
433
+ sendJson(ctx, 200, await service.getWorkflowEvaluationSuite(id, limit));
434
+ }
435
+
436
+ async function handleRunWorkflow(
437
+ ctx: WorkflowRouteContext,
438
+ service: WorkflowService,
439
+ id: string
440
+ ): Promise<void> {
441
+ const execution = await service.runWorkflow(id, {
442
+ mode: 'manual',
443
+ throwOnError: false,
444
+ });
445
+ sendJson(ctx, 200, { execution });
446
+ }
447
+
448
+ async function handleGetExecution(
449
+ ctx: WorkflowRouteContext,
450
+ service: WorkflowService,
451
+ id: string
452
+ ): Promise<void> {
453
+ sendJson(ctx, 200, { execution: await service.getExecutionDetail(id) });
454
+ }
455
+
395
456
  export async function handleWorkflowRoutes(ctx: WorkflowRouteContext): Promise<void> {
396
457
  const path = normalizePath(ctx.pathname);
397
458
  const method = ctx.method.toUpperCase();
@@ -433,11 +494,36 @@ export async function handleWorkflowRoutes(ctx: WorkflowRouteContext): Promise<v
433
494
  return;
434
495
  }
435
496
 
497
+ if (method === 'GET' && path.startsWith('/executions/')) {
498
+ const executionId = decodeURIComponent(path.slice('/executions/'.length));
499
+ if (executionId) {
500
+ await handleGetExecution(ctx, service, executionId);
501
+ return;
502
+ }
503
+ }
504
+
436
505
  const id = readId(path);
437
506
  if (id && method === 'GET' && path === `/workflows/${encodeURIComponent(id)}`) {
438
507
  await handleGet(ctx, service, id);
439
508
  return;
440
509
  }
510
+ if (id && method === 'GET' && path === `/workflows/${encodeURIComponent(id)}/revisions`) {
511
+ await handleListRevisions(ctx, service, id);
512
+ return;
513
+ }
514
+ if (
515
+ id &&
516
+ method === 'POST' &&
517
+ path.startsWith(`/workflows/${encodeURIComponent(id)}/revisions/`)
518
+ ) {
519
+ const prefix = `/workflows/${encodeURIComponent(id)}/revisions/`;
520
+ const suffix = path.slice(prefix.length);
521
+ const versionId = suffix.endsWith('/restore') ? suffix.slice(0, -'/restore'.length) : '';
522
+ if (versionId) {
523
+ await handleRestoreRevision(ctx, service, id, decodeURIComponent(versionId));
524
+ return;
525
+ }
526
+ }
441
527
  if (id && method === 'PUT' && path === `/workflows/${encodeURIComponent(id)}`) {
442
528
  await handleWrite(ctx, service, id);
443
529
  return;
@@ -455,6 +541,18 @@ export async function handleWorkflowRoutes(ctx: WorkflowRouteContext): Promise<v
455
541
  await handleToggle(ctx, service, id, false);
456
542
  return;
457
543
  }
544
+ if (id && method === 'POST' && path === `/workflows/${encodeURIComponent(id)}/run`) {
545
+ await handleRunWorkflow(ctx, service, id);
546
+ return;
547
+ }
548
+ if (
549
+ id &&
550
+ method === 'GET' &&
551
+ path === `/workflows/${encodeURIComponent(id)}/evaluation-samples`
552
+ ) {
553
+ await handleEvaluationSamples(ctx, service, id);
554
+ return;
555
+ }
458
556
  if (id && method === 'GET' && path === `/workflows/${encodeURIComponent(id)}/executions`) {
459
557
  await handleListExecutions(ctx, service, id);
460
558
  return;
@@ -44,8 +44,12 @@ async function listWorkflows(
44
44
  ): Promise<void> {
45
45
  try {
46
46
  const userId = req.query?.userId as string | undefined;
47
+ const q = req.query?.q as string | undefined;
47
48
  const service = getService(runtime);
48
- const workflows = await service.listWorkflows(userId);
49
+ // `?q=` performs a ranked free-text search; otherwise list all (#8913).
50
+ const workflows = q?.trim()
51
+ ? await service.searchWorkflows(q, userId)
52
+ : await service.listWorkflows(userId);
49
53
  res.json({ success: true, data: workflows });
50
54
  } catch (error) {
51
55
  res.status(500).json({