@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,679 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builder for the `/api/automations` response surface.
|
|
3
|
+
*
|
|
4
|
+
* Reads workflows in-process via WorkflowService, runtime tasks via the core
|
|
5
|
+
* runtime task API, and draft conversations via runtime room APIs. There is
|
|
6
|
+
* no dynamic import of plugin-workflow because this code IS the plugin.
|
|
7
|
+
*
|
|
8
|
+
* Trigger task lookup: the runtime stores trigger tasks under three tag
|
|
9
|
+
* combinations — `["queue","repeat","trigger"]` for user-defined triggers and
|
|
10
|
+
* `["queue","repeat","heartbeat"]` for plugin-owned heartbeats. We replicate
|
|
11
|
+
* `listTriggerTasks` from `packages/agent/src/triggers/runtime.ts` here
|
|
12
|
+
* because plugin-workflow cannot depend on @elizaos/agent.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { AgentRuntime, Room, Task, UUID } from '@elizaos/core';
|
|
16
|
+
import { stringToUuid } from '@elizaos/core';
|
|
17
|
+
import type { WorkflowStatusResponse } from '../routes/workflow-routes';
|
|
18
|
+
import { WORKFLOW_SERVICE_TYPE, type WorkflowService } from '../services/workflow-service';
|
|
19
|
+
import type {
|
|
20
|
+
WorkflowDefinition,
|
|
21
|
+
WorkflowDefinitionResponse,
|
|
22
|
+
WorkflowExecution,
|
|
23
|
+
} from '../types/index';
|
|
24
|
+
import {
|
|
25
|
+
type AutomationItem,
|
|
26
|
+
type AutomationLastExecution,
|
|
27
|
+
type AutomationListResponse,
|
|
28
|
+
type AutomationRoomBinding,
|
|
29
|
+
type AutomationSummary,
|
|
30
|
+
type ConversationMetadata,
|
|
31
|
+
type ConversationScope,
|
|
32
|
+
isAutomationConversationMetadata,
|
|
33
|
+
type TriggerSummary,
|
|
34
|
+
taskToTriggerSummary,
|
|
35
|
+
toWorkbenchTaskView,
|
|
36
|
+
type WorkbenchTaskView,
|
|
37
|
+
} from './automations-types';
|
|
38
|
+
|
|
39
|
+
const WORKFLOW_DRAFT_TITLE = 'New Workflow Draft';
|
|
40
|
+
|
|
41
|
+
const SYSTEM_TASK_NAMES = new Set([
|
|
42
|
+
'EMBEDDING_DRAIN',
|
|
43
|
+
'PROACTIVE_AGENT',
|
|
44
|
+
'LIFEOPS_SCHEDULER',
|
|
45
|
+
'TRIGGER_DISPATCH',
|
|
46
|
+
'heartbeat',
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
// 30s cache for last-execution data — avoids hammering the workflow runtime on
|
|
50
|
+
// every automations poll. null data = checked and found no executions yet
|
|
51
|
+
// (still cached to avoid re-polling).
|
|
52
|
+
const lastExecutionCache = new Map<
|
|
53
|
+
string,
|
|
54
|
+
{ data: AutomationLastExecution | null; expiresAt: number }
|
|
55
|
+
>();
|
|
56
|
+
const LAST_EXECUTION_TTL_MS = 30_000;
|
|
57
|
+
|
|
58
|
+
interface AutomationRoomRecord {
|
|
59
|
+
title: string;
|
|
60
|
+
roomId: string;
|
|
61
|
+
conversationId: string | null;
|
|
62
|
+
metadata: ConversationMetadata;
|
|
63
|
+
updatedAt: string | null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
67
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function asString(value: unknown): string | undefined {
|
|
71
|
+
if (typeof value !== 'string') {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
const trimmed = value.trim();
|
|
75
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeDateValue(value: unknown): string | null {
|
|
79
|
+
if (typeof value === 'string') {
|
|
80
|
+
const parsed = Date.parse(value);
|
|
81
|
+
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
|
|
82
|
+
}
|
|
83
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
84
|
+
return new Date(value).toISOString();
|
|
85
|
+
}
|
|
86
|
+
if (value instanceof Date) {
|
|
87
|
+
return value.toISOString();
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function resolveAgentName(runtime: AgentRuntime): string {
|
|
93
|
+
return runtime.character?.name?.trim() || 'Eliza';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isSystemTask(task: WorkbenchTaskView): boolean {
|
|
97
|
+
if (SYSTEM_TASK_NAMES.has(task.name)) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
const tags = new Set(task.tags ?? []);
|
|
101
|
+
return tags.has('queue') && tags.has('repeat');
|
|
102
|
+
}
|
|
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
|
+
function choosePreferredSystemTask(
|
|
123
|
+
current: WorkbenchTaskView,
|
|
124
|
+
candidate: WorkbenchTaskView
|
|
125
|
+
): WorkbenchTaskView {
|
|
126
|
+
const currentHasDescription = current.description.trim().length > 0;
|
|
127
|
+
const candidateHasDescription = candidate.description.trim().length > 0;
|
|
128
|
+
if (candidateHasDescription && !currentHasDescription) {
|
|
129
|
+
return candidate;
|
|
130
|
+
}
|
|
131
|
+
if (currentHasDescription && !candidateHasDescription) {
|
|
132
|
+
return current;
|
|
133
|
+
}
|
|
134
|
+
return (candidate.updatedAt ?? 0) > (current.updatedAt ?? 0) ? candidate : current;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function deduplicateSystemTasks(tasks: WorkbenchTaskView[]): WorkbenchTaskView[] {
|
|
138
|
+
const systemTasksByName = new Map<string, WorkbenchTaskView>();
|
|
139
|
+
const userTasks: WorkbenchTaskView[] = [];
|
|
140
|
+
|
|
141
|
+
for (const task of tasks) {
|
|
142
|
+
if (!isSystemTask(task)) {
|
|
143
|
+
userTasks.push(task);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const existing = systemTasksByName.get(task.name);
|
|
147
|
+
if (!existing) {
|
|
148
|
+
systemTasksByName.set(task.name, task);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
systemTasksByName.set(task.name, choosePreferredSystemTask(existing, task));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return [...userTasks, ...systemTasksByName.values()];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function buildRoomBinding(room: AutomationRoomRecord | undefined): AutomationRoomBinding | null {
|
|
158
|
+
if (!room) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
conversationId: room.conversationId,
|
|
163
|
+
roomId: room.roomId,
|
|
164
|
+
scope: (room.metadata.scope ?? 'general') as ConversationScope,
|
|
165
|
+
...(room.metadata.sourceConversationId
|
|
166
|
+
? { sourceConversationId: room.metadata.sourceConversationId }
|
|
167
|
+
: {}),
|
|
168
|
+
...(room.metadata.terminalBridgeConversationId
|
|
169
|
+
? {
|
|
170
|
+
terminalBridgeConversationId: room.metadata.terminalBridgeConversationId,
|
|
171
|
+
}
|
|
172
|
+
: {}),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function extractConversationMetadataFromRoom(
|
|
177
|
+
room: Pick<Room, 'metadata'> | null | undefined
|
|
178
|
+
): ConversationMetadata | undefined {
|
|
179
|
+
const roomMetadata = isRecord(room?.metadata) ? room.metadata : null;
|
|
180
|
+
if (!roomMetadata) {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
const stored = isRecord(roomMetadata.webConversation) ? roomMetadata.webConversation : null;
|
|
184
|
+
if (!stored) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
// The full sanitization lives in @elizaos/agent; here we just extract the
|
|
188
|
+
// metadata fields we actually consume in the automations response.
|
|
189
|
+
const next: ConversationMetadata = {};
|
|
190
|
+
const scope = asString(stored.scope);
|
|
191
|
+
if (scope) next.scope = scope as ConversationScope;
|
|
192
|
+
const automationType = asString(stored.automationType);
|
|
193
|
+
if (automationType === 'coordinator_text' || automationType === 'workflow') {
|
|
194
|
+
next.automationType = automationType;
|
|
195
|
+
}
|
|
196
|
+
const taskId = asString(stored.taskId);
|
|
197
|
+
if (taskId) next.taskId = taskId;
|
|
198
|
+
const triggerId = asString(stored.triggerId);
|
|
199
|
+
if (triggerId) next.triggerId = triggerId;
|
|
200
|
+
const workflowId = asString(stored.workflowId);
|
|
201
|
+
if (workflowId) next.workflowId = workflowId;
|
|
202
|
+
const workflowName = asString(stored.workflowName);
|
|
203
|
+
if (workflowName) next.workflowName = workflowName;
|
|
204
|
+
const draftId = asString(stored.draftId);
|
|
205
|
+
if (draftId) next.draftId = draftId;
|
|
206
|
+
const sourceConversationId = asString(stored.sourceConversationId);
|
|
207
|
+
if (sourceConversationId) next.sourceConversationId = sourceConversationId;
|
|
208
|
+
const terminalBridgeConversationId = asString(stored.terminalBridgeConversationId);
|
|
209
|
+
if (terminalBridgeConversationId)
|
|
210
|
+
next.terminalBridgeConversationId = terminalBridgeConversationId;
|
|
211
|
+
return Object.keys(next).length > 0 ? next : undefined;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function readAutomationRoomRecord(
|
|
215
|
+
room: Room & { updatedAt?: unknown }
|
|
216
|
+
): AutomationRoomRecord | null {
|
|
217
|
+
const roomId = asString(room.id);
|
|
218
|
+
if (!roomId) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const metadata = extractConversationMetadataFromRoom(room);
|
|
223
|
+
if (!metadata || !isAutomationConversationMetadata(metadata)) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const roomMetadata = isRecord(room.metadata) ? room.metadata : null;
|
|
228
|
+
const webConversation = isRecord(roomMetadata?.webConversation)
|
|
229
|
+
? roomMetadata.webConversation
|
|
230
|
+
: null;
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
title: asString(room.name) ?? 'Automation',
|
|
234
|
+
roomId,
|
|
235
|
+
conversationId: asString(webConversation?.conversationId) ?? null,
|
|
236
|
+
metadata,
|
|
237
|
+
updatedAt: normalizeDateValue(room.updatedAt),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function listAutomationRooms(
|
|
242
|
+
runtime: AgentRuntime,
|
|
243
|
+
agentName: string
|
|
244
|
+
): Promise<AutomationRoomRecord[]> {
|
|
245
|
+
const worldId = stringToUuid(`${agentName}-web-chat-world`) as UUID;
|
|
246
|
+
const rooms = await runtime.getRooms(worldId);
|
|
247
|
+
return rooms
|
|
248
|
+
.map((room) => readAutomationRoomRecord(room))
|
|
249
|
+
.filter((room): room is AutomationRoomRecord => room !== null);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Replicates `listTriggerTasks` from @elizaos/agent's triggers/runtime.ts.
|
|
254
|
+
* We can't import from @elizaos/agent (would create a dependency cycle),
|
|
255
|
+
* so we hit the runtime's task API directly with the same tag filters.
|
|
256
|
+
* `runtime.getTasks` automatically scopes by `agentId`.
|
|
257
|
+
*/
|
|
258
|
+
async function listTriggerTasks(runtime: AgentRuntime): Promise<Task[]> {
|
|
259
|
+
const [triggerTasks, heartbeatTasks] = await Promise.all([
|
|
260
|
+
runtime.getTasks({ tags: ['repeat', 'trigger'] }),
|
|
261
|
+
runtime.getTasks({ tags: ['repeat', 'heartbeat'] }),
|
|
262
|
+
]);
|
|
263
|
+
|
|
264
|
+
const merged = new Map<string, Task>();
|
|
265
|
+
for (const task of [...triggerTasks, ...heartbeatTasks]) {
|
|
266
|
+
const key =
|
|
267
|
+
task.id ?? `${task.name ?? ''}:${task.description ?? ''}:${(task.tags ?? []).join(',')}`;
|
|
268
|
+
if (!merged.has(key)) {
|
|
269
|
+
merged.set(key, task);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return [...merged.values()];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function buildCoordinatorTaskItem(
|
|
276
|
+
task: WorkbenchTaskView,
|
|
277
|
+
room: AutomationRoomRecord | undefined
|
|
278
|
+
): AutomationItem {
|
|
279
|
+
const system = isSystemTask(task);
|
|
280
|
+
return {
|
|
281
|
+
id: `task:${task.id}`,
|
|
282
|
+
type: 'coordinator_text',
|
|
283
|
+
source: 'workbench_task',
|
|
284
|
+
title: task.name,
|
|
285
|
+
description: task.description,
|
|
286
|
+
status: system ? 'system' : task.isCompleted ? 'completed' : 'active',
|
|
287
|
+
enabled: !task.isCompleted,
|
|
288
|
+
system,
|
|
289
|
+
isDraft: false,
|
|
290
|
+
hasBackingWorkflow: false,
|
|
291
|
+
updatedAt: room?.updatedAt ?? normalizeDateValue(task.updatedAt),
|
|
292
|
+
taskId: task.id,
|
|
293
|
+
task,
|
|
294
|
+
schedules: [],
|
|
295
|
+
room: buildRoomBinding(room),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function buildCoordinatorTriggerItem(
|
|
300
|
+
trigger: TriggerSummary,
|
|
301
|
+
room: AutomationRoomRecord | undefined
|
|
302
|
+
): AutomationItem {
|
|
303
|
+
return {
|
|
304
|
+
id: `trigger:${trigger.id}`,
|
|
305
|
+
type: 'coordinator_text',
|
|
306
|
+
source: 'trigger',
|
|
307
|
+
title: trigger.displayName,
|
|
308
|
+
description: trigger.instructions,
|
|
309
|
+
status: trigger.enabled ? 'active' : 'paused',
|
|
310
|
+
enabled: trigger.enabled,
|
|
311
|
+
system: false,
|
|
312
|
+
isDraft: false,
|
|
313
|
+
hasBackingWorkflow: false,
|
|
314
|
+
updatedAt:
|
|
315
|
+
room?.updatedAt ??
|
|
316
|
+
normalizeDateValue(trigger.updatedAt) ??
|
|
317
|
+
normalizeDateValue(trigger.lastRunAtIso),
|
|
318
|
+
triggerId: trigger.id,
|
|
319
|
+
trigger,
|
|
320
|
+
schedules: [trigger],
|
|
321
|
+
room: buildRoomBinding(room),
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function buildWorkflowDraftItem(room: AutomationRoomRecord): AutomationItem {
|
|
326
|
+
const metadata = room.metadata;
|
|
327
|
+
const title = metadata.workflowName?.trim() || room.title.trim() || WORKFLOW_DRAFT_TITLE;
|
|
328
|
+
return {
|
|
329
|
+
id: `workflow-draft:${metadata.draftId}`,
|
|
330
|
+
type: 'workflow',
|
|
331
|
+
source: 'workflow_draft',
|
|
332
|
+
title,
|
|
333
|
+
description: '',
|
|
334
|
+
status: 'draft',
|
|
335
|
+
enabled: true,
|
|
336
|
+
system: false,
|
|
337
|
+
isDraft: true,
|
|
338
|
+
hasBackingWorkflow: false,
|
|
339
|
+
updatedAt: room.updatedAt,
|
|
340
|
+
draftId: room.metadata.draftId,
|
|
341
|
+
schedules: [],
|
|
342
|
+
room: buildRoomBinding(room),
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function buildAutomationDraftItem(room: AutomationRoomRecord): AutomationItem {
|
|
347
|
+
const metadata = room.metadata;
|
|
348
|
+
const trimmedTitle = room.title.trim();
|
|
349
|
+
const title =
|
|
350
|
+
trimmedTitle && trimmedTitle.toLowerCase() !== 'default' ? trimmedTitle : 'New automation';
|
|
351
|
+
return {
|
|
352
|
+
id: `automation-draft:${metadata.draftId}`,
|
|
353
|
+
type: 'automation_draft',
|
|
354
|
+
source: 'automation_draft',
|
|
355
|
+
title,
|
|
356
|
+
description: '',
|
|
357
|
+
status: 'draft',
|
|
358
|
+
enabled: true,
|
|
359
|
+
system: false,
|
|
360
|
+
isDraft: true,
|
|
361
|
+
hasBackingWorkflow: false,
|
|
362
|
+
updatedAt: room.updatedAt,
|
|
363
|
+
draftId: metadata.draftId,
|
|
364
|
+
schedules: [],
|
|
365
|
+
room: buildRoomBinding(room),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function buildWorkflowItem(
|
|
370
|
+
workflow: WorkflowDefinition | undefined,
|
|
371
|
+
room: AutomationRoomRecord | undefined,
|
|
372
|
+
fallback: {
|
|
373
|
+
workflowId: string;
|
|
374
|
+
workflowName?: string;
|
|
375
|
+
trigger?: TriggerSummary;
|
|
376
|
+
}
|
|
377
|
+
): AutomationItem {
|
|
378
|
+
const missingBackingWorkflow = !workflow && !fallback.trigger;
|
|
379
|
+
const title =
|
|
380
|
+
workflow?.name?.trim() ||
|
|
381
|
+
room?.metadata.workflowName?.trim() ||
|
|
382
|
+
fallback.workflowName?.trim() ||
|
|
383
|
+
fallback.workflowId;
|
|
384
|
+
const enabled =
|
|
385
|
+
missingBackingWorkflow === true
|
|
386
|
+
? false
|
|
387
|
+
: (workflow?.active ?? fallback.trigger?.enabled ?? false);
|
|
388
|
+
const description =
|
|
389
|
+
(workflow as { description?: string } | undefined)?.description?.trim() ||
|
|
390
|
+
(fallback.trigger ? `Scheduled workflow automation for ${title}.` : '');
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
id: `workflow:${fallback.workflowId}`,
|
|
394
|
+
type: 'workflow',
|
|
395
|
+
source: workflow ? 'workflow' : 'workflow_shadow',
|
|
396
|
+
title,
|
|
397
|
+
description,
|
|
398
|
+
status: missingBackingWorkflow ? 'draft' : enabled ? 'active' : 'paused',
|
|
399
|
+
enabled,
|
|
400
|
+
system: false,
|
|
401
|
+
isDraft: missingBackingWorkflow,
|
|
402
|
+
hasBackingWorkflow: Boolean(workflow),
|
|
403
|
+
updatedAt:
|
|
404
|
+
room?.updatedAt ??
|
|
405
|
+
normalizeDateValue(fallback.trigger?.updatedAt) ??
|
|
406
|
+
normalizeDateValue(fallback.trigger?.lastRunAtIso),
|
|
407
|
+
workflowId: fallback.workflowId,
|
|
408
|
+
workflow,
|
|
409
|
+
schedules: fallback.trigger ? [fallback.trigger] : [],
|
|
410
|
+
room: buildRoomBinding(room),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function compareAutomationItems(left: AutomationItem, right: AutomationItem): number {
|
|
415
|
+
if (left.system !== right.system) {
|
|
416
|
+
return left.system ? 1 : -1;
|
|
417
|
+
}
|
|
418
|
+
if (left.isDraft !== right.isDraft) {
|
|
419
|
+
return left.isDraft ? -1 : 1;
|
|
420
|
+
}
|
|
421
|
+
const leftUpdated = left.updatedAt ? Date.parse(left.updatedAt) : 0;
|
|
422
|
+
const rightUpdated = right.updatedAt ? Date.parse(right.updatedAt) : 0;
|
|
423
|
+
if (rightUpdated !== leftUpdated) {
|
|
424
|
+
return rightUpdated - leftUpdated;
|
|
425
|
+
}
|
|
426
|
+
return left.title.localeCompare(right.title);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function normalizeLastExecution(raw: WorkflowExecution): AutomationLastExecution | null {
|
|
430
|
+
const rawStatus = raw.status;
|
|
431
|
+
if (typeof rawStatus !== 'string') return null;
|
|
432
|
+
const STATUS_MAP: Record<string, AutomationLastExecution['status']> = {
|
|
433
|
+
success: 'success',
|
|
434
|
+
error: 'error',
|
|
435
|
+
crashed: 'error',
|
|
436
|
+
running: 'running',
|
|
437
|
+
waiting: 'waiting',
|
|
438
|
+
};
|
|
439
|
+
const status = STATUS_MAP[rawStatus] ?? 'unknown';
|
|
440
|
+
const startedAt = typeof raw.startedAt === 'string' ? raw.startedAt : null;
|
|
441
|
+
if (!startedAt) return null;
|
|
442
|
+
const stoppedAt = typeof raw.stoppedAt === 'string' ? raw.stoppedAt : null;
|
|
443
|
+
const errorMessage = (() => {
|
|
444
|
+
const data = isRecord(raw.data) ? raw.data : null;
|
|
445
|
+
const resultData = isRecord(data?.resultData) ? data.resultData : null;
|
|
446
|
+
const error = isRecord(resultData?.error) ? resultData.error : null;
|
|
447
|
+
return typeof error?.message === 'string' ? error.message : undefined;
|
|
448
|
+
})();
|
|
449
|
+
return {
|
|
450
|
+
status,
|
|
451
|
+
startedAt,
|
|
452
|
+
stoppedAt,
|
|
453
|
+
...(errorMessage ? { errorMessage } : {}),
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function getWorkflowService(runtime: AgentRuntime): WorkflowService | null {
|
|
458
|
+
const candidate = runtime.getService?.(WORKFLOW_SERVICE_TYPE);
|
|
459
|
+
return (candidate as WorkflowService | null) ?? null;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function buildWorkflowStatus(service: WorkflowService | null): WorkflowStatusResponse {
|
|
463
|
+
return {
|
|
464
|
+
mode: service ? 'local' : 'disabled',
|
|
465
|
+
host: 'in-process',
|
|
466
|
+
status: service ? 'ready' : 'error',
|
|
467
|
+
cloudConnected: false,
|
|
468
|
+
localEnabled: Boolean(service),
|
|
469
|
+
platform: 'desktop',
|
|
470
|
+
cloudHealth: 'unknown',
|
|
471
|
+
errorMessage: service ? null : 'Workflow service is not registered',
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async function loadWorkflowList(service: WorkflowService | null): Promise<{
|
|
476
|
+
workflows: WorkflowDefinitionResponse[];
|
|
477
|
+
workflowFetchError: string | null;
|
|
478
|
+
}> {
|
|
479
|
+
if (!service) {
|
|
480
|
+
return { workflows: [], workflowFetchError: 'Workflow service is not registered' };
|
|
481
|
+
}
|
|
482
|
+
try {
|
|
483
|
+
const workflows = await service.listWorkflows();
|
|
484
|
+
return { workflows, workflowFetchError: null };
|
|
485
|
+
} catch (error) {
|
|
486
|
+
return {
|
|
487
|
+
workflows: [],
|
|
488
|
+
workflowFetchError: error instanceof Error ? error.message : 'Unable to load workflows',
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async function fetchLastExecution(
|
|
494
|
+
service: WorkflowService,
|
|
495
|
+
workflowId: string
|
|
496
|
+
): Promise<AutomationLastExecution | null> {
|
|
497
|
+
const cached = lastExecutionCache.get(workflowId);
|
|
498
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
499
|
+
return cached.data;
|
|
500
|
+
}
|
|
501
|
+
const response = await service.listExecutions({ workflowId, limit: 1 });
|
|
502
|
+
if (!response.data || response.data.length === 0) {
|
|
503
|
+
lastExecutionCache.set(workflowId, {
|
|
504
|
+
data: null,
|
|
505
|
+
expiresAt: Date.now() + LAST_EXECUTION_TTL_MS,
|
|
506
|
+
});
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
const exec = normalizeLastExecution(response.data[0]);
|
|
510
|
+
lastExecutionCache.set(workflowId, {
|
|
511
|
+
data: exec,
|
|
512
|
+
expiresAt: Date.now() + LAST_EXECUTION_TTL_MS,
|
|
513
|
+
});
|
|
514
|
+
return exec;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
export async function buildAutomationListResponse(
|
|
518
|
+
runtime: AgentRuntime
|
|
519
|
+
): Promise<AutomationListResponse> {
|
|
520
|
+
const agentName = resolveAgentName(runtime);
|
|
521
|
+
const rooms = await listAutomationRooms(runtime, agentName);
|
|
522
|
+
const taskRooms = new Map(
|
|
523
|
+
rooms
|
|
524
|
+
.filter((room) => room.metadata.taskId)
|
|
525
|
+
.map((room) => [room.metadata.taskId as string, room])
|
|
526
|
+
);
|
|
527
|
+
const triggerRooms = new Map(
|
|
528
|
+
rooms
|
|
529
|
+
.filter((room) => room.metadata.triggerId)
|
|
530
|
+
.map((room) => [room.metadata.triggerId as string, room])
|
|
531
|
+
);
|
|
532
|
+
const workflowRooms = new Map(
|
|
533
|
+
rooms
|
|
534
|
+
.filter((room) => room.metadata.workflowId)
|
|
535
|
+
.map((room) => [room.metadata.workflowId as string, room])
|
|
536
|
+
);
|
|
537
|
+
const workflowDraftItems = rooms
|
|
538
|
+
.filter((room) => room.metadata.scope === 'automation-workflow-draft')
|
|
539
|
+
.filter((room) => typeof room.metadata.draftId === 'string')
|
|
540
|
+
.map((room) => buildWorkflowDraftItem(room));
|
|
541
|
+
const automationDraftItems = rooms
|
|
542
|
+
.filter((room) => room.metadata.scope === 'automation-draft')
|
|
543
|
+
.filter((room) => typeof room.metadata.draftId === 'string')
|
|
544
|
+
.map((room) => buildAutomationDraftItem(room));
|
|
545
|
+
|
|
546
|
+
const allTasks = await runtime.getTasks({});
|
|
547
|
+
const tasks = deduplicateSystemTasks(
|
|
548
|
+
allTasks
|
|
549
|
+
.map((task) => toWorkbenchTaskView(task))
|
|
550
|
+
.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
|
+
);
|
|
557
|
+
|
|
558
|
+
const triggerTaskRecords = await listTriggerTasks(runtime);
|
|
559
|
+
const triggerItems = triggerTaskRecords
|
|
560
|
+
.map((task) => taskToTriggerSummary(task))
|
|
561
|
+
.filter((trigger): trigger is TriggerSummary => trigger !== null);
|
|
562
|
+
const triggerTaskIds = new Set(triggerItems.map((trigger) => trigger.taskId));
|
|
563
|
+
const taskItems = tasks
|
|
564
|
+
.filter((task) => !triggerTaskIds.has(task.id))
|
|
565
|
+
.map((task) => buildCoordinatorTaskItem(task, taskRooms.get(task.id)));
|
|
566
|
+
|
|
567
|
+
const service = getWorkflowService(runtime);
|
|
568
|
+
const workflowStatus = buildWorkflowStatus(service);
|
|
569
|
+
const { workflows: workflowList, workflowFetchError } = await loadWorkflowList(service);
|
|
570
|
+
|
|
571
|
+
const workflowItemsById = new Map<string, AutomationItem>();
|
|
572
|
+
for (const workflow of workflowList) {
|
|
573
|
+
workflowItemsById.set(
|
|
574
|
+
workflow.id,
|
|
575
|
+
buildWorkflowItem(workflow, workflowRooms.get(workflow.id), {
|
|
576
|
+
workflowId: workflow.id,
|
|
577
|
+
workflowName: workflow.name,
|
|
578
|
+
})
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
for (const trigger of triggerItems) {
|
|
583
|
+
if (trigger.kind === 'workflow' && trigger.workflowId) {
|
|
584
|
+
const existing = workflowItemsById.get(trigger.workflowId);
|
|
585
|
+
if (existing) {
|
|
586
|
+
existing.schedules = [...existing.schedules, trigger];
|
|
587
|
+
existing.updatedAt =
|
|
588
|
+
existing.updatedAt ??
|
|
589
|
+
normalizeDateValue(trigger.updatedAt) ??
|
|
590
|
+
normalizeDateValue(trigger.lastRunAtIso);
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
workflowItemsById.set(
|
|
594
|
+
trigger.workflowId,
|
|
595
|
+
buildWorkflowItem(undefined, workflowRooms.get(trigger.workflowId), {
|
|
596
|
+
workflowId: trigger.workflowId,
|
|
597
|
+
workflowName: trigger.workflowName,
|
|
598
|
+
trigger,
|
|
599
|
+
})
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Only synthesize workflow items from rooms when workflow runtime is offline
|
|
605
|
+
// (`workflowFetchError` set) — in that case the room is the most-recent
|
|
606
|
+
// ground truth we have and should be surfaced. When workflow runtime is online and
|
|
607
|
+
// returned a list, any workflowId in `workflowRooms` that isn't in the
|
|
608
|
+
// current workflow list is an ORPHAN: the workflow was deleted but the chat
|
|
609
|
+
// room/conversation wasn't cleaned up. Surfacing those creates ghost
|
|
610
|
+
// 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.
|
|
612
|
+
const workflowOffline = workflowFetchError !== null;
|
|
613
|
+
if (workflowOffline) {
|
|
614
|
+
for (const [workflowId, room] of workflowRooms.entries()) {
|
|
615
|
+
if (!workflowItemsById.has(workflowId)) {
|
|
616
|
+
workflowItemsById.set(
|
|
617
|
+
workflowId,
|
|
618
|
+
buildWorkflowItem(undefined, room, {
|
|
619
|
+
workflowId,
|
|
620
|
+
workflowName: room.metadata.workflowName,
|
|
621
|
+
})
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Fetch last execution for each live workflow in parallel.
|
|
628
|
+
// Promise.allSettled ensures one failure does not block the full list.
|
|
629
|
+
if (!workflowOffline && service && workflowItemsById.size > 0) {
|
|
630
|
+
const now = Date.now();
|
|
631
|
+
for (const [k, v] of lastExecutionCache) {
|
|
632
|
+
if (v.expiresAt < now) lastExecutionCache.delete(k);
|
|
633
|
+
}
|
|
634
|
+
const workflowIds = [...workflowItemsById.keys()];
|
|
635
|
+
await Promise.allSettled(
|
|
636
|
+
workflowIds.map(async (workflowId) => {
|
|
637
|
+
const exec = await fetchLastExecution(service, workflowId);
|
|
638
|
+
if (!exec) return;
|
|
639
|
+
const item = workflowItemsById.get(workflowId);
|
|
640
|
+
if (item) item.lastExecution = exec;
|
|
641
|
+
})
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const coordinatorTriggerItems = triggerItems
|
|
646
|
+
.filter((trigger) => trigger.kind !== 'workflow')
|
|
647
|
+
.map((trigger) => buildCoordinatorTriggerItem(trigger, triggerRooms.get(trigger.id)));
|
|
648
|
+
|
|
649
|
+
const automations = [
|
|
650
|
+
...automationDraftItems,
|
|
651
|
+
...workflowDraftItems,
|
|
652
|
+
...taskItems,
|
|
653
|
+
...coordinatorTriggerItems,
|
|
654
|
+
...workflowItemsById.values(),
|
|
655
|
+
].sort(compareAutomationItems);
|
|
656
|
+
|
|
657
|
+
const summary: AutomationSummary = {
|
|
658
|
+
total: automations.length,
|
|
659
|
+
coordinatorCount: automations.filter((automation) => automation.type === 'coordinator_text')
|
|
660
|
+
.length,
|
|
661
|
+
workflowCount: automations.filter((automation) => automation.type === 'workflow').length,
|
|
662
|
+
scheduledCount: automations.filter((automation) => automation.schedules.length > 0).length,
|
|
663
|
+
draftCount: automations.filter((automation) => automation.isDraft).length,
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
return {
|
|
667
|
+
automations,
|
|
668
|
+
summary,
|
|
669
|
+
workflowStatus,
|
|
670
|
+
workflowFetchError,
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Test-only: clear the last-execution cache between cases.
|
|
676
|
+
*/
|
|
677
|
+
export function __resetAutomationsCacheForTests(): void {
|
|
678
|
+
lastExecutionCache.clear();
|
|
679
|
+
}
|