@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,714 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
type TriggerRunRecord as CoreTriggerRunRecord,
|
|
4
|
+
type IAgentRuntime,
|
|
5
|
+
type RouteHelpers,
|
|
6
|
+
type RouteRequestContext,
|
|
7
|
+
stringToUuid,
|
|
8
|
+
type Task,
|
|
9
|
+
type TriggerConfig,
|
|
10
|
+
type TriggerKind,
|
|
11
|
+
type TriggerLastStatus,
|
|
12
|
+
type TriggerType,
|
|
13
|
+
type TriggerWakeMode,
|
|
14
|
+
type UUID,
|
|
15
|
+
} from '@elizaos/core';
|
|
16
|
+
|
|
17
|
+
export type TriggerRouteHelpers = RouteHelpers;
|
|
18
|
+
|
|
19
|
+
export interface TriggerTaskMetadata {
|
|
20
|
+
updatedAt?: number;
|
|
21
|
+
updateInterval?: number;
|
|
22
|
+
blocking?: boolean;
|
|
23
|
+
trigger?: TriggerConfig;
|
|
24
|
+
triggerRuns?: CoreTriggerRunRecord[];
|
|
25
|
+
[key: string]:
|
|
26
|
+
| string
|
|
27
|
+
| number
|
|
28
|
+
| boolean
|
|
29
|
+
| string[]
|
|
30
|
+
| number[]
|
|
31
|
+
| Record<string, string | number | boolean>
|
|
32
|
+
| undefined
|
|
33
|
+
| TriggerConfig
|
|
34
|
+
| CoreTriggerRunRecord[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface TriggerSummary {
|
|
38
|
+
id: UUID;
|
|
39
|
+
taskId: UUID;
|
|
40
|
+
displayName: string;
|
|
41
|
+
instructions: string;
|
|
42
|
+
triggerType: TriggerType;
|
|
43
|
+
enabled: boolean;
|
|
44
|
+
wakeMode: TriggerWakeMode;
|
|
45
|
+
createdBy: string;
|
|
46
|
+
timezone?: string;
|
|
47
|
+
intervalMs?: number;
|
|
48
|
+
scheduledAtIso?: string;
|
|
49
|
+
cronExpression?: string;
|
|
50
|
+
eventKind?: string;
|
|
51
|
+
maxRuns?: number;
|
|
52
|
+
runCount: number;
|
|
53
|
+
nextRunAtMs?: number;
|
|
54
|
+
lastRunAtIso?: string;
|
|
55
|
+
lastStatus?: TriggerLastStatus;
|
|
56
|
+
lastError?: string;
|
|
57
|
+
updatedAt?: number;
|
|
58
|
+
updateInterval?: number;
|
|
59
|
+
kind?: TriggerKind;
|
|
60
|
+
workflowId?: string;
|
|
61
|
+
workflowName?: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface TriggerHealthSnapshot {
|
|
65
|
+
triggersEnabled: boolean;
|
|
66
|
+
activeTriggers: number;
|
|
67
|
+
disabledTriggers: number;
|
|
68
|
+
totalExecutions: number;
|
|
69
|
+
totalFailures: number;
|
|
70
|
+
totalSkipped: number;
|
|
71
|
+
lastExecutionAt?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface NormalizedTriggerDraft {
|
|
75
|
+
displayName: string;
|
|
76
|
+
instructions: string;
|
|
77
|
+
triggerType: TriggerType;
|
|
78
|
+
wakeMode: TriggerWakeMode;
|
|
79
|
+
enabled: boolean;
|
|
80
|
+
createdBy: string;
|
|
81
|
+
timezone?: string;
|
|
82
|
+
intervalMs?: number;
|
|
83
|
+
scheduledAtIso?: string;
|
|
84
|
+
cronExpression?: string;
|
|
85
|
+
eventKind?: string;
|
|
86
|
+
maxRuns?: number;
|
|
87
|
+
kind?: TriggerKind;
|
|
88
|
+
workflowId?: string;
|
|
89
|
+
workflowName?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface TriggerExecutionOptions {
|
|
93
|
+
source: 'scheduler' | 'manual' | 'event';
|
|
94
|
+
force?: boolean;
|
|
95
|
+
event?: {
|
|
96
|
+
kind: string;
|
|
97
|
+
payload?: Record<string, unknown>;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface TriggerExecutionResult {
|
|
102
|
+
status: 'success' | 'error' | 'skipped';
|
|
103
|
+
error?: string;
|
|
104
|
+
taskDeleted: boolean;
|
|
105
|
+
runRecord?: CoreTriggerRunRecord;
|
|
106
|
+
trigger?: TriggerSummary | null;
|
|
107
|
+
executionId?: string;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface TextTriggerWorkflowDraft {
|
|
111
|
+
displayName: string;
|
|
112
|
+
instructions: string;
|
|
113
|
+
wakeMode: TriggerWakeMode;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface DeployedTriggerWorkflow {
|
|
117
|
+
id: string;
|
|
118
|
+
name: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
interface TriggerDraftInput {
|
|
122
|
+
displayName?: string;
|
|
123
|
+
instructions?: string;
|
|
124
|
+
triggerType?: TriggerType;
|
|
125
|
+
wakeMode?: TriggerWakeMode;
|
|
126
|
+
enabled?: boolean;
|
|
127
|
+
createdBy?: string;
|
|
128
|
+
timezone?: string;
|
|
129
|
+
intervalMs?: number;
|
|
130
|
+
scheduledAtIso?: string;
|
|
131
|
+
cronExpression?: string;
|
|
132
|
+
eventKind?: string;
|
|
133
|
+
maxRuns?: number;
|
|
134
|
+
kind?: TriggerKind;
|
|
135
|
+
workflowId?: string;
|
|
136
|
+
workflowName?: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
interface NormalizeTriggerDraftFallback {
|
|
140
|
+
displayName: string;
|
|
141
|
+
instructions: string;
|
|
142
|
+
triggerType: TriggerType;
|
|
143
|
+
wakeMode: TriggerWakeMode;
|
|
144
|
+
enabled: boolean;
|
|
145
|
+
createdBy: string;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface TriggerRouteContext extends RouteRequestContext {
|
|
149
|
+
runtime: IAgentRuntime | null;
|
|
150
|
+
executeTriggerTask: (
|
|
151
|
+
runtime: IAgentRuntime,
|
|
152
|
+
task: Task,
|
|
153
|
+
options: TriggerExecutionOptions
|
|
154
|
+
) => Promise<TriggerExecutionResult>;
|
|
155
|
+
getTriggerHealthSnapshot: (runtime: IAgentRuntime) => Promise<TriggerHealthSnapshot>;
|
|
156
|
+
getTriggerLimit: (runtime: IAgentRuntime) => number;
|
|
157
|
+
listTriggerTasks: (runtime: IAgentRuntime) => Promise<Task[]>;
|
|
158
|
+
readTriggerConfig: (task: Task) => TriggerConfig | null;
|
|
159
|
+
readTriggerRuns: (task: Task) => CoreTriggerRunRecord[];
|
|
160
|
+
taskToTriggerSummary: (task: Task) => TriggerSummary | null;
|
|
161
|
+
triggersFeatureEnabled: (runtime: IAgentRuntime) => boolean;
|
|
162
|
+
buildTriggerConfig: (params: {
|
|
163
|
+
draft: NormalizedTriggerDraft;
|
|
164
|
+
triggerId: UUID;
|
|
165
|
+
previous?: TriggerConfig;
|
|
166
|
+
}) => TriggerConfig;
|
|
167
|
+
buildTriggerMetadata: (params: {
|
|
168
|
+
existingMetadata?: TriggerTaskMetadata;
|
|
169
|
+
trigger: TriggerConfig;
|
|
170
|
+
nowMs: number;
|
|
171
|
+
}) => TriggerTaskMetadata | null;
|
|
172
|
+
normalizeTriggerDraft: (params: {
|
|
173
|
+
input: TriggerDraftInput;
|
|
174
|
+
fallback: NormalizeTriggerDraftFallback;
|
|
175
|
+
}) => { draft?: NormalizedTriggerDraft; error?: string };
|
|
176
|
+
/**
|
|
177
|
+
* Phase 2E: every persisted trigger is `kind: "workflow"`. When the
|
|
178
|
+
* caller submits `kind: "text"` (or omits `kind`), the route uses this
|
|
179
|
+
* to materialize a single-node `respondToEvent` workflow first, then
|
|
180
|
+
* stores the trigger as `kind: "workflow"` pointing at that workflow.
|
|
181
|
+
*/
|
|
182
|
+
deployTextTriggerWorkflow: (
|
|
183
|
+
runtime: IAgentRuntime,
|
|
184
|
+
draft: TextTriggerWorkflowDraft,
|
|
185
|
+
ownerId: string
|
|
186
|
+
) => Promise<DeployedTriggerWorkflow | null>;
|
|
187
|
+
DISABLED_TRIGGER_INTERVAL_MS: number;
|
|
188
|
+
TRIGGER_TASK_NAME: string;
|
|
189
|
+
TRIGGER_TASK_TAGS: string[];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function trim(value: string): string {
|
|
193
|
+
return value.trim().replace(/\s+/g, ' ');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function parseTriggerKind(value: unknown): TriggerKind | undefined {
|
|
197
|
+
if (value === 'text' || value === 'workflow') return value;
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
type ParsedTriggerKind = { ok: true; kind: TriggerKind } | { ok: false; error: string };
|
|
202
|
+
|
|
203
|
+
function parseTriggerKindStrict(value: unknown): ParsedTriggerKind | undefined {
|
|
204
|
+
if (value === undefined) return undefined;
|
|
205
|
+
if (value === 'text' || value === 'workflow') return { ok: true, kind: value };
|
|
206
|
+
return { ok: false, error: "kind must be 'text' or 'workflow'" };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function parseNonEmptyString(value: unknown): string | undefined {
|
|
210
|
+
if (typeof value !== 'string') return undefined;
|
|
211
|
+
const trimmed = value.trim();
|
|
212
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function parseEventPayload(value: unknown): Record<string, unknown> {
|
|
216
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
217
|
+
? (value as Record<string, unknown>)
|
|
218
|
+
: {};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function normalizeTriggerPath(pathname: string): {
|
|
222
|
+
normalizedPathname: string;
|
|
223
|
+
usingHeartbeatsAlias: boolean;
|
|
224
|
+
} {
|
|
225
|
+
if (pathname === '/api/heartbeats') {
|
|
226
|
+
return {
|
|
227
|
+
normalizedPathname: '/api/triggers',
|
|
228
|
+
usingHeartbeatsAlias: true,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (pathname.startsWith('/api/heartbeats/')) {
|
|
232
|
+
return {
|
|
233
|
+
normalizedPathname: pathname.replace('/api/heartbeats', '/api/triggers'),
|
|
234
|
+
usingHeartbeatsAlias: true,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
normalizedPathname: pathname,
|
|
239
|
+
usingHeartbeatsAlias: false,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function findTask(
|
|
244
|
+
runtime: IAgentRuntime,
|
|
245
|
+
id: string,
|
|
246
|
+
listTriggerTasks: (runtime: IAgentRuntime) => Promise<Task[]>,
|
|
247
|
+
readTriggerConfig: (task: Task) => TriggerConfig | null
|
|
248
|
+
): Promise<Task | null> {
|
|
249
|
+
const tasks = await listTriggerTasks(runtime);
|
|
250
|
+
return (
|
|
251
|
+
tasks.find((task) => {
|
|
252
|
+
const trigger = readTriggerConfig(task);
|
|
253
|
+
return trigger?.triggerId === id || task.id === id;
|
|
254
|
+
}) ?? null
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export async function handleTriggerRoutes(ctx: TriggerRouteContext): Promise<boolean> {
|
|
259
|
+
const {
|
|
260
|
+
method,
|
|
261
|
+
pathname,
|
|
262
|
+
req,
|
|
263
|
+
res,
|
|
264
|
+
runtime,
|
|
265
|
+
readJsonBody,
|
|
266
|
+
json,
|
|
267
|
+
error,
|
|
268
|
+
executeTriggerTask,
|
|
269
|
+
getTriggerHealthSnapshot,
|
|
270
|
+
getTriggerLimit,
|
|
271
|
+
listTriggerTasks,
|
|
272
|
+
readTriggerConfig,
|
|
273
|
+
readTriggerRuns,
|
|
274
|
+
taskToTriggerSummary,
|
|
275
|
+
triggersFeatureEnabled,
|
|
276
|
+
buildTriggerConfig,
|
|
277
|
+
buildTriggerMetadata,
|
|
278
|
+
normalizeTriggerDraft,
|
|
279
|
+
deployTextTriggerWorkflow,
|
|
280
|
+
DISABLED_TRIGGER_INTERVAL_MS,
|
|
281
|
+
TRIGGER_TASK_NAME,
|
|
282
|
+
TRIGGER_TASK_TAGS,
|
|
283
|
+
} = ctx;
|
|
284
|
+
|
|
285
|
+
const { normalizedPathname, usingHeartbeatsAlias } = normalizeTriggerPath(pathname);
|
|
286
|
+
const listResponse = (triggers: TriggerSummary[], status = 200): void => {
|
|
287
|
+
json(res, usingHeartbeatsAlias ? { triggers, heartbeats: triggers } : { triggers }, status);
|
|
288
|
+
};
|
|
289
|
+
const itemResponse = (summary: TriggerSummary, status = 200): void => {
|
|
290
|
+
json(
|
|
291
|
+
res,
|
|
292
|
+
usingHeartbeatsAlias ? { trigger: summary, heartbeat: summary } : { trigger: summary },
|
|
293
|
+
status
|
|
294
|
+
);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
if (!normalizedPathname.startsWith('/api/triggers') && !pathname.startsWith('/api/heartbeats'))
|
|
298
|
+
return false;
|
|
299
|
+
if (!runtime) {
|
|
300
|
+
error(res, 'Agent is not running', 503);
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
if (!triggersFeatureEnabled(runtime) && normalizedPathname !== '/api/triggers/health') {
|
|
304
|
+
error(res, 'Triggers are disabled by configuration', 503);
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (method === 'GET' && normalizedPathname === '/api/triggers/health') {
|
|
309
|
+
json(res, await getTriggerHealthSnapshot(runtime));
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (method === 'GET' && normalizedPathname === '/api/triggers') {
|
|
314
|
+
const tasks = await listTriggerTasks(runtime);
|
|
315
|
+
const triggers = tasks
|
|
316
|
+
.map(taskToTriggerSummary)
|
|
317
|
+
.filter((summary): summary is TriggerSummary => summary !== null)
|
|
318
|
+
.sort((a, b) => String(a.displayName ?? '').localeCompare(String(b.displayName ?? '')));
|
|
319
|
+
listResponse(triggers);
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (method === 'POST' && normalizedPathname === '/api/triggers') {
|
|
324
|
+
const body = await readJsonBody<Record<string, unknown>>(req, res);
|
|
325
|
+
if (!body) return true;
|
|
326
|
+
|
|
327
|
+
const creator = typeof body.createdBy === 'string' ? trim(body.createdBy) || 'api' : 'api';
|
|
328
|
+
const kindParsed = parseTriggerKindStrict(body.kind);
|
|
329
|
+
if (kindParsed !== undefined && kindParsed.ok === false) {
|
|
330
|
+
error(res, kindParsed.error, 400);
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
const requestedKind: TriggerKind | undefined = kindParsed?.ok ? kindParsed.kind : undefined;
|
|
334
|
+
let workflowId = parseNonEmptyString(body.workflowId);
|
|
335
|
+
let workflowName = parseNonEmptyString(body.workflowName);
|
|
336
|
+
if (requestedKind === 'workflow' && !workflowId) {
|
|
337
|
+
error(res, "workflowId is required when kind is 'workflow'", 400);
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Phase 2E: when the client submits `kind: "text"` or omits `kind`,
|
|
342
|
+
// materialize a single-node `respondToEvent` workflow up front so the
|
|
343
|
+
// persisted trigger is always `kind: "workflow"`.
|
|
344
|
+
if (requestedKind !== 'workflow') {
|
|
345
|
+
const rawDisplayName =
|
|
346
|
+
typeof body.displayName === 'string' && trim(body.displayName)
|
|
347
|
+
? trim(body.displayName)
|
|
348
|
+
: 'New Trigger';
|
|
349
|
+
const rawInstructions = typeof body.instructions === 'string' ? trim(body.instructions) : '';
|
|
350
|
+
if (!rawInstructions) {
|
|
351
|
+
error(res, 'instructions is required', 400);
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
const wakeModeForWorkflow: TriggerWakeMode =
|
|
355
|
+
typeof body.wakeMode === 'string' && body.wakeMode === 'next_autonomy_cycle'
|
|
356
|
+
? 'next_autonomy_cycle'
|
|
357
|
+
: 'inject_now';
|
|
358
|
+
const deployed = await deployTextTriggerWorkflow(
|
|
359
|
+
runtime,
|
|
360
|
+
{
|
|
361
|
+
displayName: rawDisplayName,
|
|
362
|
+
instructions: rawInstructions,
|
|
363
|
+
wakeMode: wakeModeForWorkflow,
|
|
364
|
+
},
|
|
365
|
+
creator
|
|
366
|
+
);
|
|
367
|
+
if (!deployed) {
|
|
368
|
+
error(res, 'Workflow plugin is not loaded; cannot create text triggers.', 503);
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
workflowId = deployed.id;
|
|
372
|
+
workflowName = deployed.name;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const kind: TriggerKind = 'workflow';
|
|
376
|
+
const inputDraft: TriggerDraftInput = {
|
|
377
|
+
displayName: typeof body.displayName === 'string' ? body.displayName : undefined,
|
|
378
|
+
instructions: typeof body.instructions === 'string' ? body.instructions : undefined,
|
|
379
|
+
triggerType:
|
|
380
|
+
typeof body.triggerType === 'string' ? (body.triggerType as TriggerType) : undefined,
|
|
381
|
+
wakeMode: typeof body.wakeMode === 'string' ? (body.wakeMode as TriggerWakeMode) : undefined,
|
|
382
|
+
enabled: !!(body.enabled ?? true),
|
|
383
|
+
createdBy: creator,
|
|
384
|
+
timezone: typeof body.timezone === 'string' ? body.timezone : undefined,
|
|
385
|
+
intervalMs: typeof body.intervalMs === 'number' ? body.intervalMs : undefined,
|
|
386
|
+
scheduledAtIso: typeof body.scheduledAtIso === 'string' ? body.scheduledAtIso : undefined,
|
|
387
|
+
cronExpression: typeof body.cronExpression === 'string' ? body.cronExpression : undefined,
|
|
388
|
+
eventKind: typeof body.eventKind === 'string' ? body.eventKind : undefined,
|
|
389
|
+
maxRuns: typeof body.maxRuns === 'number' ? body.maxRuns : undefined,
|
|
390
|
+
kind,
|
|
391
|
+
workflowId,
|
|
392
|
+
workflowName,
|
|
393
|
+
};
|
|
394
|
+
const normalized = normalizeTriggerDraft({
|
|
395
|
+
input: inputDraft,
|
|
396
|
+
fallback: {
|
|
397
|
+
displayName:
|
|
398
|
+
typeof body.displayName === 'string' && trim(body.displayName)
|
|
399
|
+
? trim(body.displayName)
|
|
400
|
+
: 'New Trigger',
|
|
401
|
+
instructions: typeof body.instructions === 'string' ? trim(body.instructions) : '',
|
|
402
|
+
triggerType:
|
|
403
|
+
typeof body.triggerType === 'string' ? (body.triggerType as TriggerType) : 'interval',
|
|
404
|
+
wakeMode:
|
|
405
|
+
typeof body.wakeMode === 'string' ? (body.wakeMode as TriggerWakeMode) : 'inject_now',
|
|
406
|
+
enabled: body.enabled === undefined ? true : body.enabled === true,
|
|
407
|
+
createdBy: creator,
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
if (!normalized.draft) {
|
|
411
|
+
error(res, normalized.error ?? 'Invalid trigger request', 400);
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const existingTasks = await listTriggerTasks(runtime);
|
|
416
|
+
const activeCount = existingTasks.filter((task) => {
|
|
417
|
+
const trigger = readTriggerConfig(task);
|
|
418
|
+
return trigger?.enabled && trigger.createdBy === creator;
|
|
419
|
+
}).length;
|
|
420
|
+
const limit = getTriggerLimit(runtime);
|
|
421
|
+
if (activeCount >= limit) {
|
|
422
|
+
error(res, `Active trigger limit reached (${limit})`, 429);
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const triggerId = stringToUuid(crypto.randomUUID());
|
|
427
|
+
const trigger = buildTriggerConfig({ draft: normalized.draft, triggerId });
|
|
428
|
+
|
|
429
|
+
const duplicate = existingTasks.find((task) => {
|
|
430
|
+
const existingTrigger = readTriggerConfig(task);
|
|
431
|
+
return (
|
|
432
|
+
existingTrigger?.enabled &&
|
|
433
|
+
existingTrigger.dedupeKey &&
|
|
434
|
+
existingTrigger.dedupeKey === trigger.dedupeKey
|
|
435
|
+
);
|
|
436
|
+
});
|
|
437
|
+
if (duplicate?.id) {
|
|
438
|
+
error(res, 'Equivalent trigger already exists', 409);
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const nowMs = Date.now();
|
|
443
|
+
const metadata = trigger.enabled
|
|
444
|
+
? buildTriggerMetadata({ trigger, nowMs })
|
|
445
|
+
: ({
|
|
446
|
+
updatedAt: nowMs,
|
|
447
|
+
updateInterval: DISABLED_TRIGGER_INTERVAL_MS,
|
|
448
|
+
trigger: {
|
|
449
|
+
...trigger,
|
|
450
|
+
nextRunAtMs: nowMs + DISABLED_TRIGGER_INTERVAL_MS,
|
|
451
|
+
},
|
|
452
|
+
} as TriggerTaskMetadata);
|
|
453
|
+
if (!metadata) {
|
|
454
|
+
error(res, 'Unable to compute trigger schedule', 400);
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const roomId = (
|
|
459
|
+
runtime.getService('AUTONOMY') as { getAutonomousRoomId?(): UUID } | null
|
|
460
|
+
)?.getAutonomousRoomId?.();
|
|
461
|
+
const taskId = await runtime.createTask({
|
|
462
|
+
name: TRIGGER_TASK_NAME,
|
|
463
|
+
description: trigger.displayName,
|
|
464
|
+
roomId,
|
|
465
|
+
tags: [...TRIGGER_TASK_TAGS],
|
|
466
|
+
metadata: metadata as Task['metadata'],
|
|
467
|
+
});
|
|
468
|
+
const created = await runtime.getTask(taskId);
|
|
469
|
+
const summary = created ? taskToTriggerSummary(created) : null;
|
|
470
|
+
if (!summary) {
|
|
471
|
+
error(res, 'Trigger created but summary could not be generated', 500);
|
|
472
|
+
return true;
|
|
473
|
+
}
|
|
474
|
+
itemResponse(summary, 201);
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const runsMatch = /^\/api\/triggers\/([^/]+)\/runs$/.exec(normalizedPathname);
|
|
479
|
+
if (method === 'GET' && runsMatch) {
|
|
480
|
+
const task = await findTask(
|
|
481
|
+
runtime,
|
|
482
|
+
decodeURIComponent(runsMatch[1]),
|
|
483
|
+
listTriggerTasks,
|
|
484
|
+
readTriggerConfig
|
|
485
|
+
);
|
|
486
|
+
if (!task) {
|
|
487
|
+
error(res, 'Trigger not found', 404);
|
|
488
|
+
return true;
|
|
489
|
+
}
|
|
490
|
+
json(res, { runs: readTriggerRuns(task) });
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const execMatch = /^\/api\/triggers\/([^/]+)\/execute$/.exec(normalizedPathname);
|
|
495
|
+
if (method === 'POST' && execMatch) {
|
|
496
|
+
const task = await findTask(
|
|
497
|
+
runtime,
|
|
498
|
+
decodeURIComponent(execMatch[1]),
|
|
499
|
+
listTriggerTasks,
|
|
500
|
+
readTriggerConfig
|
|
501
|
+
);
|
|
502
|
+
if (!task) {
|
|
503
|
+
error(res, 'Trigger not found', 404);
|
|
504
|
+
return true;
|
|
505
|
+
}
|
|
506
|
+
const result: TriggerExecutionResult = await executeTriggerTask(runtime, task, {
|
|
507
|
+
source: 'manual',
|
|
508
|
+
force: true,
|
|
509
|
+
});
|
|
510
|
+
const refreshed = task.id ? await runtime.getTask(task.id) : null;
|
|
511
|
+
const summary = refreshed ? taskToTriggerSummary(refreshed) : (result.trigger ?? null);
|
|
512
|
+
json(
|
|
513
|
+
res,
|
|
514
|
+
usingHeartbeatsAlias
|
|
515
|
+
? { ok: true, result, trigger: summary, heartbeat: summary }
|
|
516
|
+
: { ok: true, result, trigger: summary }
|
|
517
|
+
);
|
|
518
|
+
return true;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const eventMatch = /^\/api\/triggers\/events\/([^/]+)$/.exec(normalizedPathname);
|
|
522
|
+
if (method === 'POST' && eventMatch) {
|
|
523
|
+
const eventKind = decodeURIComponent(eventMatch[1] ?? '').trim();
|
|
524
|
+
if (!eventKind) {
|
|
525
|
+
error(res, 'event kind is required', 400);
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const body = await readJsonBody<Record<string, unknown>>(req, res);
|
|
530
|
+
if (!body) return true;
|
|
531
|
+
const payload = parseEventPayload(body.payload ?? body);
|
|
532
|
+
const tasks = await listTriggerTasks(runtime);
|
|
533
|
+
const matchingTasks = tasks.filter((task) => {
|
|
534
|
+
const trigger = readTriggerConfig(task);
|
|
535
|
+
return (
|
|
536
|
+
trigger?.enabled === true &&
|
|
537
|
+
trigger.triggerType === 'event' &&
|
|
538
|
+
trigger.eventKind === eventKind
|
|
539
|
+
);
|
|
540
|
+
});
|
|
541
|
+
const results: Array<{
|
|
542
|
+
taskId: string | undefined;
|
|
543
|
+
result: Awaited<ReturnType<typeof executeTriggerTask>>;
|
|
544
|
+
trigger: ReturnType<typeof taskToTriggerSummary> | null;
|
|
545
|
+
}> = [];
|
|
546
|
+
for (const task of matchingTasks) {
|
|
547
|
+
const result = await executeTriggerTask(runtime, task, {
|
|
548
|
+
source: 'event',
|
|
549
|
+
event: { kind: eventKind, payload },
|
|
550
|
+
});
|
|
551
|
+
const refreshed = task.id ? await runtime.getTask(task.id) : null;
|
|
552
|
+
results.push({
|
|
553
|
+
taskId: task.id,
|
|
554
|
+
result,
|
|
555
|
+
trigger: refreshed ? taskToTriggerSummary(refreshed) : (result.trigger ?? null),
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
json(res, {
|
|
559
|
+
ok: true,
|
|
560
|
+
eventKind,
|
|
561
|
+
matched: matchingTasks.length,
|
|
562
|
+
results,
|
|
563
|
+
});
|
|
564
|
+
return true;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const itemMatch = /^\/api\/triggers\/([^/]+)$/.exec(normalizedPathname);
|
|
568
|
+
if (!itemMatch) return false;
|
|
569
|
+
const triggerId = decodeURIComponent(itemMatch[1]);
|
|
570
|
+
|
|
571
|
+
if (method === 'GET') {
|
|
572
|
+
const task = await findTask(runtime, triggerId, listTriggerTasks, readTriggerConfig);
|
|
573
|
+
if (!task) {
|
|
574
|
+
error(res, 'Trigger not found', 404);
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
const summary = taskToTriggerSummary(task);
|
|
578
|
+
if (!summary) {
|
|
579
|
+
error(res, 'Trigger metadata is invalid', 500);
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
itemResponse(summary);
|
|
583
|
+
return true;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (method === 'DELETE') {
|
|
587
|
+
const task = await findTask(runtime, triggerId, listTriggerTasks, readTriggerConfig);
|
|
588
|
+
if (!task?.id) {
|
|
589
|
+
error(res, 'Trigger not found', 404);
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
await runtime.deleteTask(task.id);
|
|
593
|
+
json(res, { ok: true });
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
if (method === 'PUT') {
|
|
598
|
+
const task = await findTask(runtime, triggerId, listTriggerTasks, readTriggerConfig);
|
|
599
|
+
if (!task?.id) {
|
|
600
|
+
error(res, 'Trigger not found', 404);
|
|
601
|
+
return true;
|
|
602
|
+
}
|
|
603
|
+
const current = readTriggerConfig(task);
|
|
604
|
+
if (!current) {
|
|
605
|
+
error(res, 'Trigger metadata is invalid', 500);
|
|
606
|
+
return true;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const body = await readJsonBody<Record<string, unknown>>(req, res);
|
|
610
|
+
if (!body) return true;
|
|
611
|
+
|
|
612
|
+
const kindParsed = parseTriggerKindStrict(body.kind);
|
|
613
|
+
if (kindParsed !== undefined && kindParsed.ok === false) {
|
|
614
|
+
error(res, kindParsed.error, 400);
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
const parsedKind: TriggerKind | undefined = kindParsed?.ok ? kindParsed.kind : undefined;
|
|
618
|
+
const nextKind: TriggerKind | undefined = parsedKind ?? parseTriggerKind(current.kind);
|
|
619
|
+
const nextWorkflowId = parseNonEmptyString(body.workflowId) ?? current.workflowId;
|
|
620
|
+
const nextWorkflowName = parseNonEmptyString(body.workflowName) ?? current.workflowName;
|
|
621
|
+
if (nextKind === 'workflow' && !nextWorkflowId) {
|
|
622
|
+
error(res, "workflowId is required when kind is 'workflow'", 400);
|
|
623
|
+
return true;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const mergedInput: TriggerDraftInput = {
|
|
627
|
+
displayName: typeof body.displayName === 'string' ? body.displayName : undefined,
|
|
628
|
+
instructions: typeof body.instructions === 'string' ? body.instructions : undefined,
|
|
629
|
+
triggerType:
|
|
630
|
+
typeof body.triggerType === 'string' ? (body.triggerType as TriggerType) : undefined,
|
|
631
|
+
wakeMode: typeof body.wakeMode === 'string' ? (body.wakeMode as TriggerWakeMode) : undefined,
|
|
632
|
+
enabled: body.enabled === undefined ? current.enabled : body.enabled === true,
|
|
633
|
+
createdBy: current.createdBy,
|
|
634
|
+
timezone: typeof body.timezone === 'string' ? body.timezone : undefined,
|
|
635
|
+
intervalMs: typeof body.intervalMs === 'number' ? body.intervalMs : current.intervalMs,
|
|
636
|
+
scheduledAtIso:
|
|
637
|
+
typeof body.scheduledAtIso === 'string' ? body.scheduledAtIso : current.scheduledAtIso,
|
|
638
|
+
cronExpression:
|
|
639
|
+
typeof body.cronExpression === 'string' ? body.cronExpression : current.cronExpression,
|
|
640
|
+
eventKind: typeof body.eventKind === 'string' ? body.eventKind : current.eventKind,
|
|
641
|
+
maxRuns: typeof body.maxRuns === 'number' ? body.maxRuns : current.maxRuns,
|
|
642
|
+
kind: nextKind,
|
|
643
|
+
workflowId: nextWorkflowId,
|
|
644
|
+
workflowName: nextWorkflowName,
|
|
645
|
+
};
|
|
646
|
+
const normalized = normalizeTriggerDraft({
|
|
647
|
+
input: mergedInput,
|
|
648
|
+
fallback: {
|
|
649
|
+
displayName: current.displayName,
|
|
650
|
+
instructions: current.instructions,
|
|
651
|
+
triggerType: current.triggerType,
|
|
652
|
+
wakeMode: current.wakeMode,
|
|
653
|
+
enabled: body.enabled === undefined ? current.enabled : body.enabled === true,
|
|
654
|
+
createdBy: current.createdBy,
|
|
655
|
+
},
|
|
656
|
+
});
|
|
657
|
+
if (!normalized.draft) {
|
|
658
|
+
error(res, normalized.error ?? 'Invalid update', 400);
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const nextTrigger = buildTriggerConfig({
|
|
663
|
+
draft: normalized.draft,
|
|
664
|
+
triggerId: current.triggerId,
|
|
665
|
+
previous: current,
|
|
666
|
+
});
|
|
667
|
+
const existingMeta = (task.metadata ?? {}) as TriggerTaskMetadata;
|
|
668
|
+
const existingRuns = readTriggerRuns(task);
|
|
669
|
+
|
|
670
|
+
let nextMeta: TriggerTaskMetadata;
|
|
671
|
+
if (!nextTrigger.enabled) {
|
|
672
|
+
nextMeta = {
|
|
673
|
+
...existingMeta,
|
|
674
|
+
updatedAt: Date.now(),
|
|
675
|
+
updateInterval: DISABLED_TRIGGER_INTERVAL_MS,
|
|
676
|
+
trigger: {
|
|
677
|
+
...nextTrigger,
|
|
678
|
+
nextRunAtMs: Date.now() + DISABLED_TRIGGER_INTERVAL_MS,
|
|
679
|
+
},
|
|
680
|
+
triggerRuns: existingRuns,
|
|
681
|
+
};
|
|
682
|
+
} else {
|
|
683
|
+
const built = buildTriggerMetadata({
|
|
684
|
+
existingMetadata: existingMeta,
|
|
685
|
+
trigger: nextTrigger,
|
|
686
|
+
nowMs: Date.now(),
|
|
687
|
+
});
|
|
688
|
+
if (!built) {
|
|
689
|
+
error(res, 'Unable to compute trigger schedule', 400);
|
|
690
|
+
return true;
|
|
691
|
+
}
|
|
692
|
+
nextMeta = built;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
await runtime.updateTask(task.id, {
|
|
696
|
+
description: nextTrigger.displayName,
|
|
697
|
+
metadata: nextMeta as Task['metadata'],
|
|
698
|
+
});
|
|
699
|
+
const refreshed = await runtime.getTask(task.id);
|
|
700
|
+
if (!refreshed) {
|
|
701
|
+
error(res, 'Trigger updated but no longer available', 500);
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
const summary = taskToTriggerSummary(refreshed);
|
|
705
|
+
if (!summary) {
|
|
706
|
+
error(res, 'Trigger metadata is invalid', 500);
|
|
707
|
+
return true;
|
|
708
|
+
}
|
|
709
|
+
itemResponse(summary);
|
|
710
|
+
return true;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
return false;
|
|
714
|
+
}
|