@elevasis/sdk 1.21.0 → 1.22.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/dist/cli.cjs +1239 -173
- package/dist/index.d.ts +1752 -464
- package/dist/index.js +3477 -143
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +19 -1
- package/dist/test-utils/index.d.ts +1188 -127
- package/dist/test-utils/index.js +3359 -152
- package/dist/worker/index.js +3148 -80
- package/package.json +2 -2
- package/reference/claude-config/hooks/post-edit-validate.mjs +98 -98
- package/reference/claude-config/hooks/scaffold-registry-reminder.mjs +188 -188
- package/reference/claude-config/hooks/tool-failure-recovery.mjs +73 -73
- package/reference/claude-config/registries/graph-skills.json +4 -4
- package/reference/claude-config/registries/knowledge-flags.json +0 -2
- package/reference/claude-config/rules/active-change-index.md +80 -80
- package/reference/claude-config/rules/agent-start-here.md +277 -277
- package/reference/claude-config/rules/deployment.md +57 -57
- package/reference/claude-config/rules/error-handling.md +56 -56
- package/reference/claude-config/rules/execution.md +40 -40
- package/reference/claude-config/rules/frontend.md +4 -4
- package/reference/claude-config/rules/observability.md +31 -31
- package/reference/claude-config/rules/operations.md +29 -17
- package/reference/claude-config/rules/organization-model.md +113 -81
- package/reference/claude-config/rules/organization-os.md +115 -113
- package/reference/claude-config/rules/package-taxonomy.md +33 -33
- package/reference/claude-config/rules/platform.md +42 -42
- package/reference/claude-config/rules/shared-types.md +49 -46
- package/reference/claude-config/rules/task-tracking.md +47 -47
- package/reference/claude-config/rules/ui.md +200 -200
- package/reference/claude-config/rules/vibe.md +235 -235
- package/reference/claude-config/scripts/statusline-command.js +18 -18
- package/reference/claude-config/settings.json +34 -34
- package/reference/claude-config/skills/deploy/{SKILL.md → skill.md} +156 -156
- package/reference/claude-config/skills/dsp/SKILL.md +66 -66
- package/reference/claude-config/skills/elevasis/SKILL.md +235 -235
- package/reference/claude-config/skills/explore/SKILL.md +6 -6
- package/reference/claude-config/skills/git-sync/SKILL.md +126 -126
- package/reference/claude-config/skills/knowledge/SKILL.md +314 -299
- package/reference/claude-config/skills/knowledge/operations/codify-level-a.md +100 -100
- package/reference/claude-config/skills/knowledge/operations/codify-level-b.md +159 -159
- package/reference/claude-config/skills/knowledge/operations/customers.md +109 -109
- package/reference/claude-config/skills/knowledge/operations/features.md +76 -76
- package/reference/claude-config/skills/knowledge/operations/goals.md +118 -118
- package/reference/claude-config/skills/knowledge/operations/identity.md +93 -93
- package/reference/claude-config/skills/knowledge/operations/labels.md +94 -94
- package/reference/claude-config/skills/knowledge/operations/offerings.md +109 -109
- package/reference/claude-config/skills/knowledge/operations/roles.md +99 -99
- package/reference/claude-config/skills/knowledge/operations/techStack.md +30 -30
- package/reference/claude-config/skills/project/SKILL.md +1088 -1088
- package/reference/claude-config/skills/run-ui/SKILL.md +73 -73
- package/reference/claude-config/skills/save/SKILL.md +3 -3
- package/reference/claude-config/skills/setup/SKILL.md +275 -275
- package/reference/claude-config/skills/status/SKILL.md +59 -59
- package/reference/claude-config/skills/submit-request/SKILL.md +180 -180
- package/reference/claude-config/skills/sync/SKILL.md +47 -47
- package/reference/claude-config/skills/tutorial/SKILL.md +259 -259
- package/reference/claude-config/skills/tutorial/progress-template.md +74 -74
- package/reference/claude-config/skills/tutorial/technical.md +1303 -1303
- package/reference/claude-config/skills/tutorial/vibe-coder.md +890 -890
- package/reference/claude-config/sync-notes/2026-04-22-git-sync-and-sync-notes.md +27 -27
- package/reference/claude-config/sync-notes/2026-04-22-lead-gen-deliverability-removal.md +30 -30
- package/reference/claude-config/sync-notes/2026-04-24-test-utils-and-template-tests.md +73 -73
- package/reference/claude-config/sync-notes/2026-04-24-ui-consolidation-and-sdk-cli-train.md +86 -86
- package/reference/claude-config/sync-notes/2026-04-25-auth-role-system-and-settings-roles.md +55 -55
- package/reference/claude-config/sync-notes/2026-04-27-crm-hitl-action-layer-cutover.md +97 -97
- package/reference/claude-config/sync-notes/2026-04-27-lead-gen-substrate-train.md +112 -112
- package/reference/claude-config/sync-notes/2026-04-29-crm-state-and-lead-gen-processing-status.md +93 -93
- package/reference/claude-config/sync-notes/2026-05-02-crm-ownership-next-action.md +58 -58
- package/reference/claude-config/sync-notes/2026-05-02-template-hardcode-workos-config.md +56 -56
- package/reference/claude-config/sync-notes/2026-05-04-elevasis-workspace.md +71 -71
- package/reference/claude-config/sync-notes/2026-05-04-knowledge-bundle.md +83 -83
- package/reference/claude-config/sync-notes/2026-05-04-template-skills-run-ui-and-tutorial.md +59 -59
- package/reference/claude-config/sync-notes/2026-05-05-list-builder.md +42 -42
- package/reference/claude-config/sync-notes/2026-05-06-crm-spine.md +60 -60
- package/reference/claude-config/sync-notes/2026-05-06-sdk-changes-release-train.md +37 -37
- package/reference/claude-config/sync-notes/2026-05-07-sdk-changes-release-train.md +34 -34
- package/reference/claude-config/sync-notes/2026-05-08-resource-governance-scaffold-guidance.md +38 -38
- package/reference/claude-config/sync-notes/2026-05-09-clients-domain.md +32 -32
- package/reference/claude-config/sync-notes/2026-05-09-command-system.md +33 -33
- package/reference/claude-config/sync-notes/2026-05-09-resource-governance-and-misc.md +69 -69
- package/reference/claude-config/sync-notes/2026-05-12-sdk-ready-release-train.md +30 -30
- package/reference/claude-config/sync-notes/2026-05-14-organization-model-ontology-refactor.md +45 -0
- package/reference/claude-config/sync-notes/README.md +43 -43
- package/reference/cli.mdx +808 -808
- package/reference/concepts.mdx +146 -146
- package/reference/deployment/api.mdx +297 -297
- package/reference/deployment/command-center.mdx +209 -209
- package/reference/deployment/index.mdx +195 -195
- package/reference/deployment/provided-features.mdx +107 -107
- package/reference/deployment/ui-execution.mdx +250 -250
- package/reference/examples/organization-model.ts +171 -84
- package/reference/framework/agent.mdx +156 -156
- package/reference/framework/index.mdx +195 -195
- package/reference/framework/interaction-guidance.mdx +182 -182
- package/reference/framework/memory.mdx +326 -326
- package/reference/framework/project-structure.mdx +282 -282
- package/reference/framework/tutorial-system.mdx +135 -135
- package/reference/getting-started.mdx +142 -142
- package/reference/index.mdx +106 -106
- package/reference/packages/core/src/README.md +14 -14
- package/reference/packages/core/src/business/README.md +2 -2
- package/reference/packages/core/src/knowledge/README.md +32 -32
- package/reference/packages/core/src/organization-model/README.md +149 -149
- package/reference/packages/core/src/test-utils/README.md +37 -37
- package/reference/packages/ui/src/api/README.md +18 -18
- package/reference/packages/ui/src/app/README.md +24 -24
- package/reference/packages/ui/src/auth/README.md +18 -18
- package/reference/packages/ui/src/components/README.md +24 -24
- package/reference/packages/ui/src/execution/README.md +16 -16
- package/reference/packages/ui/src/features/README.md +28 -28
- package/reference/packages/ui/src/graph/README.md +16 -16
- package/reference/packages/ui/src/hooks/README.md +23 -23
- package/reference/packages/ui/src/initialization/README.md +19 -19
- package/reference/packages/ui/src/knowledge/README.md +31 -31
- package/reference/packages/ui/src/organization/README.md +18 -18
- package/reference/packages/ui/src/profile/README.md +19 -19
- package/reference/packages/ui/src/provider/README.md +32 -32
- package/reference/packages/ui/src/router/README.md +18 -18
- package/reference/packages/ui/src/sse/README.md +13 -13
- package/reference/packages/ui/src/test-utils/README.md +7 -7
- package/reference/packages/ui/src/theme/README.md +23 -23
- package/reference/packages/ui/src/theme/presets/README.md +19 -19
- package/reference/packages/ui/src/types/README.md +16 -16
- package/reference/packages/ui/src/utils/README.md +18 -18
- package/reference/packages/ui/src/zustand/README.md +18 -18
- package/reference/platform-tools/adapters-integration.mdx +301 -301
- package/reference/platform-tools/adapters-platform.mdx +553 -553
- package/reference/platform-tools/index.mdx +217 -217
- package/reference/platform-tools/type-safety.mdx +82 -82
- package/reference/resources/index.mdx +349 -349
- package/reference/resources/patterns.mdx +449 -449
- package/reference/resources/types.mdx +116 -116
- package/reference/roadmap.mdx +165 -165
- package/reference/runtime.mdx +173 -173
- package/reference/scaffold/core/organization-graph.mdx +110 -90
- package/reference/scaffold/core/organization-model.mdx +225 -213
- package/reference/scaffold/index.mdx +67 -67
- package/reference/scaffold/operations/propagation-pipeline.md +77 -77
- package/reference/scaffold/operations/scaffold-maintenance.md +12 -12
- package/reference/scaffold/operations/workflow-recipes.md +138 -138
- package/reference/scaffold/recipes/add-a-feature.md +307 -85
- package/reference/scaffold/recipes/add-a-resource.md +137 -103
- package/reference/scaffold/recipes/customize-knowledge-browser.md +5 -5
- package/reference/scaffold/recipes/customize-organization-model.md +275 -138
- package/reference/scaffold/recipes/extend-a-base-entity.md +8 -8
- package/reference/scaffold/recipes/extend-crm.md +3 -3
- package/reference/scaffold/recipes/extend-lead-gen.md +394 -394
- package/reference/scaffold/recipes/gate-by-feature-or-admin.md +118 -118
- package/reference/scaffold/recipes/index.md +46 -46
- package/reference/scaffold/recipes/query-the-knowledge-graph.md +197 -170
- package/reference/scaffold/reference/contracts.md +2136 -2093
- package/reference/scaffold/reference/glossary.md +76 -76
- package/reference/scaffold/ui/composition-extensibility.mdx +233 -233
- package/reference/scaffold/ui/customization.md +243 -243
- package/reference/scaffold/ui/feature-flags-and-gating.md +46 -46
- package/reference/scaffold/ui/feature-shell.mdx +72 -72
- package/reference/scaffold/ui/recipes.md +221 -214
- package/reference/spine/spine-primer.md +96 -96
- package/reference/templates/index.mdx +47 -47
- package/reference/troubleshooting.mdx +223 -223
|
@@ -1,449 +1,449 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Common Patterns
|
|
3
|
-
description: Common resource patterns for Elevasis SDK developers -- sequential steps, conditional branching, platform tools, error handling, and resource status management
|
|
4
|
-
loadWhen: "Building or modifying a workflow"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
This page collects the patterns you will reach for most often when writing resources. Each pattern is self-contained and can be adapted directly into your project.
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## Sequential Workflow Steps
|
|
12
|
-
|
|
13
|
-
The simplest pattern: a chain of steps where each step feeds its output into the next.
|
|
14
|
-
|
|
15
|
-
```typescript
|
|
16
|
-
import { z } from 'zod';
|
|
17
|
-
import { StepType } from '@elevasis/sdk';
|
|
18
|
-
import type { WorkflowDefinition, WorkflowStep } from '@elevasis/sdk';
|
|
19
|
-
|
|
20
|
-
const inputSchema = z.object({ orderId: z.string() });
|
|
21
|
-
const outputSchema = z.object({ shipped: z.boolean(), trackingNumber: z.string() });
|
|
22
|
-
|
|
23
|
-
type Input = z.infer<typeof inputSchema>;
|
|
24
|
-
type Output = z.infer<typeof outputSchema>;
|
|
25
|
-
|
|
26
|
-
const validateStep: WorkflowStep = {
|
|
27
|
-
type: StepType.LINEAR,
|
|
28
|
-
handler: async (input: Input) => {
|
|
29
|
-
const order = await getOrder(input.orderId);
|
|
30
|
-
if (!order) throw new Error(`Order ${input.orderId} not found`);
|
|
31
|
-
return { order };
|
|
32
|
-
},
|
|
33
|
-
next: { type: 'linear', target: 'ship' },
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const shipStep: WorkflowStep = {
|
|
37
|
-
type: StepType.LINEAR,
|
|
38
|
-
handler: async (input) => {
|
|
39
|
-
const tracking = await createShipment(input.order);
|
|
40
|
-
return { shipped: true, trackingNumber: tracking.number };
|
|
41
|
-
},
|
|
42
|
-
next: null, // terminal -- no further steps
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const fulfillOrder: WorkflowDefinition = {
|
|
46
|
-
config: { resourceId: 'fulfill-order', name: 'Fulfill Order', type: 'workflow', description: 'Validates and ships an order', version: '1.0.0', status: 'dev' },
|
|
47
|
-
contract: { inputSchema, outputSchema },
|
|
48
|
-
steps: { validate: validateStep, ship: shipStep },
|
|
49
|
-
entryPoint: 'validate',
|
|
50
|
-
};
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
**Key points:**
|
|
54
|
-
|
|
55
|
-
- `next: { target: 'stepName' }` routes to the next step
|
|
56
|
-
- `next: null` marks the terminal step
|
|
57
|
-
- Each step receives the full return value of the previous step as its `input`
|
|
58
|
-
- The terminal step's return value must satisfy `contract.output`
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## Conditional Branching
|
|
63
|
-
|
|
64
|
-
Use `StepType.CONDITIONAL` when the next step depends on the output of the current step.
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
import { StepType } from '@elevasis/sdk';
|
|
68
|
-
import type { WorkflowStep } from '@elevasis/sdk';
|
|
69
|
-
|
|
70
|
-
const scoreStep: WorkflowStep = {
|
|
71
|
-
type: StepType.CONDITIONAL,
|
|
72
|
-
handler: async (input) => {
|
|
73
|
-
const score = await calculateRiskScore(input.applicationId);
|
|
74
|
-
return { score, applicationId: input.applicationId };
|
|
75
|
-
},
|
|
76
|
-
next: {
|
|
77
|
-
type: 'conditional',
|
|
78
|
-
routes: [
|
|
79
|
-
{
|
|
80
|
-
condition: (output) => output.score \>= 80,
|
|
81
|
-
target: 'autoApprove',
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
condition: (output) => output.score \>= 40,
|
|
85
|
-
target: 'manualReview',
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
default: 'autoReject', // used when no condition matches
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
**Key points:**
|
|
94
|
-
|
|
95
|
-
- Routes are evaluated in order -- the first matching condition wins
|
|
96
|
-
- `default` is required and acts as the `else` branch
|
|
97
|
-
- The condition function receives the full handler output
|
|
98
|
-
- All route targets and `default` must be keys in your `steps` record
|
|
99
|
-
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
## Using Platform Tools in Steps
|
|
103
|
-
|
|
104
|
-
Platform tools let your steps call integrations managed by Elevasis (email, CRM, databases, etc.). Import `platform` from `@elevasis/sdk/worker` and call it with the tool name, method, parameters, and an optional credential reference when the tool requires one.
|
|
105
|
-
|
|
106
|
-
```typescript
|
|
107
|
-
import { platform, PlatformToolError } from '@elevasis/sdk/worker';
|
|
108
|
-
import type { WorkflowStep } from '@elevasis/sdk';
|
|
109
|
-
import { StepType } from '@elevasis/sdk';
|
|
110
|
-
|
|
111
|
-
const sendEmailStep: WorkflowStep = {
|
|
112
|
-
type: StepType.LINEAR,
|
|
113
|
-
handler: async (input, context) => {
|
|
114
|
-
const result = await platform.call({
|
|
115
|
-
tool: 'email',
|
|
116
|
-
method: 'send',
|
|
117
|
-
params: {
|
|
118
|
-
to: input.recipientEmail,
|
|
119
|
-
subject: input.subject,
|
|
120
|
-
body: input.body,
|
|
121
|
-
},
|
|
122
|
-
credential: 'sendgrid', // name of the stored credential
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
context.logger.info('Email sent', { messageId: result.messageId });
|
|
126
|
-
return { sent: true, messageId: result.messageId };
|
|
127
|
-
},
|
|
128
|
-
next: null,
|
|
129
|
-
};
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
**Key points:**
|
|
133
|
-
|
|
134
|
-
- `platform.call()` is async and times out after 60 seconds
|
|
135
|
-
- `credential` is the name of a platform environment variable set via `elevasis-sdk env set` when the tool needs one
|
|
136
|
-
- On failure, `platform.call()` throws `PlatformToolError` (not `ToolingError`)
|
|
137
|
-
- Always log success so executions are easy to debug in the dashboard
|
|
138
|
-
|
|
139
|
-
---
|
|
140
|
-
|
|
141
|
-
## Error Handling
|
|
142
|
-
|
|
143
|
-
### Catching Tool Errors
|
|
144
|
-
|
|
145
|
-
Use `PlatformToolError` (from `@elevasis/sdk/worker`) to handle tool-specific failures without catching everything:
|
|
146
|
-
|
|
147
|
-
```typescript
|
|
148
|
-
import { platform, PlatformToolError } from '@elevasis/sdk/worker';
|
|
149
|
-
|
|
150
|
-
const step = async (input) => {
|
|
151
|
-
try {
|
|
152
|
-
const deals = await platform.call({
|
|
153
|
-
tool: 'crm',
|
|
154
|
-
method: 'listDeals',
|
|
155
|
-
params: { stage: 'proposal', limit: 10 },
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
const deal = deals[0]
|
|
159
|
-
? await platform.call({
|
|
160
|
-
tool: 'crm',
|
|
161
|
-
method: 'getDeal',
|
|
162
|
-
params: { dealId: deals[0].id },
|
|
163
|
-
})
|
|
164
|
-
: null;
|
|
165
|
-
|
|
166
|
-
if (deal) {
|
|
167
|
-
await platform.call({
|
|
168
|
-
tool: 'crm',
|
|
169
|
-
method: 'updateDealStage',
|
|
170
|
-
params: { dealId: deal.id, stage: 'closing' },
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
await platform.call({
|
|
174
|
-
tool: 'crm',
|
|
175
|
-
method: 'createDealNote',
|
|
176
|
-
params: {
|
|
177
|
-
dealId: deal.id,
|
|
178
|
-
body: 'Reviewed proposal and moved the deal forward.',
|
|
179
|
-
},
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
await platform.call({
|
|
183
|
-
tool: 'crm',
|
|
184
|
-
method: 'createDealTask',
|
|
185
|
-
params: {
|
|
186
|
-
dealId: deal.id,
|
|
187
|
-
title: 'Send updated proposal',
|
|
188
|
-
dueAt: input.followUpAt ?? null,
|
|
189
|
-
},
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
await platform.call({
|
|
193
|
-
tool: 'crm',
|
|
194
|
-
method: 'recordActivity',
|
|
195
|
-
params: {
|
|
196
|
-
dealId: deal.id,
|
|
197
|
-
type: 'stage_changed',
|
|
198
|
-
title: 'Deal moved to closing',
|
|
199
|
-
description: 'Reviewed the proposal and moved the deal forward.',
|
|
200
|
-
},
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return { dealId: deal?.id ?? null, dealCount: deals.length };
|
|
205
|
-
} catch (err) {
|
|
206
|
-
if (err instanceof PlatformToolError) {
|
|
207
|
-
// Tool failed -- log it and return a degraded result
|
|
208
|
-
console.error('CRM tool failed:', err.message);
|
|
209
|
-
return { dealId: null, error: err.message };
|
|
210
|
-
}
|
|
211
|
-
throw err; // re-throw unexpected errors
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
### Failing an Execution Explicitly
|
|
217
|
-
|
|
218
|
-
Use `ExecutionError` when your step detects a condition that should mark the entire execution as failed:
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
import { ExecutionError } from '@elevasis/sdk';
|
|
222
|
-
|
|
223
|
-
const validateStep = async (input) => {
|
|
224
|
-
if (!input.userId) {
|
|
225
|
-
throw new ExecutionError('userId is required', { code: 'MISSING_INPUT' });
|
|
226
|
-
}
|
|
227
|
-
if (input.amount \<= 0) {
|
|
228
|
-
throw new ExecutionError('amount must be positive', { code: 'INVALID_AMOUNT' });
|
|
229
|
-
}
|
|
230
|
-
return { valid: true };
|
|
231
|
-
};
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
`ExecutionError` messages and metadata appear in the Elevasis dashboard under the failed execution's detail view.
|
|
235
|
-
|
|
236
|
-
### Using ToolingError
|
|
237
|
-
|
|
238
|
-
`ToolingError` is thrown by lower-level platform operations (not `platform.call()` directly). You may encounter it in advanced scenarios:
|
|
239
|
-
|
|
240
|
-
```typescript
|
|
241
|
-
import { ToolingError } from '@elevasis/sdk';
|
|
242
|
-
|
|
243
|
-
const step = async (input) => {
|
|
244
|
-
try {
|
|
245
|
-
return await doSomething(input);
|
|
246
|
-
} catch (err) {
|
|
247
|
-
if (err instanceof ToolingError) {
|
|
248
|
-
// check err.type for the error category
|
|
249
|
-
console.error('Tooling error:', err.type, err.message);
|
|
250
|
-
}
|
|
251
|
-
throw err;
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
---
|
|
257
|
-
|
|
258
|
-
## Logging in Steps
|
|
259
|
-
|
|
260
|
-
The `context.logger` writes structured logs attached to the execution. Use it instead of `console.log` so logs appear in the dashboard alongside the execution record.
|
|
261
|
-
|
|
262
|
-
```typescript
|
|
263
|
-
import type { StepHandler } from '@elevasis/sdk';
|
|
264
|
-
|
|
265
|
-
const processStep: StepHandler = async (input, context) => {
|
|
266
|
-
context.logger.info('Starting process', { userId: input.userId });
|
|
267
|
-
|
|
268
|
-
const result = await doWork(input);
|
|
269
|
-
|
|
270
|
-
context.logger.info('Process complete', { resultId: result.id });
|
|
271
|
-
return result;
|
|
272
|
-
};
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
Avoid logging sensitive values (API keys, passwords, PII) since logs are stored and visible in the dashboard.
|
|
276
|
-
|
|
277
|
-
---
|
|
278
|
-
|
|
279
|
-
## Using the Execution Store
|
|
280
|
-
|
|
281
|
-
`context.store` is a simple key-value store scoped to the current execution. Use it to pass data between steps without coupling step interfaces, or to checkpoint long-running work.
|
|
282
|
-
|
|
283
|
-
```typescript
|
|
284
|
-
const firstStep: StepHandler = async (input, context) => {
|
|
285
|
-
const data = await fetchExpensiveData(input.id);
|
|
286
|
-
|
|
287
|
-
// Save for use by later steps
|
|
288
|
-
await context.store.set('fetchedData', JSON.stringify(data));
|
|
289
|
-
|
|
290
|
-
return { fetched: true };
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
const secondStep: StepHandler = async (input, context) => {
|
|
294
|
-
const raw = await context.store.get('fetchedData');
|
|
295
|
-
const data = JSON.parse(raw ?? '{}');
|
|
296
|
-
|
|
297
|
-
return { processed: transform(data) };
|
|
298
|
-
};
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
Store values are strings. Serialize objects with `JSON.stringify` and parse with `JSON.parse`.
|
|
302
|
-
|
|
303
|
-
---
|
|
304
|
-
|
|
305
|
-
## Resource Status
|
|
306
|
-
|
|
307
|
-
### Dev vs Production
|
|
308
|
-
|
|
309
|
-
While building a resource, set `config.status` to `'dev'`:
|
|
310
|
-
|
|
311
|
-
```typescript
|
|
312
|
-
const myWorkflow: WorkflowDefinition = {
|
|
313
|
-
config: {
|
|
314
|
-
name: 'my-workflow',
|
|
315
|
-
description: 'Does something useful',
|
|
316
|
-
status: 'dev', // only manually triggerable
|
|
317
|
-
},
|
|
318
|
-
// ...
|
|
319
|
-
};
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
Dev resources:
|
|
323
|
-
|
|
324
|
-
- Appear in `elevasis-sdk resources` output
|
|
325
|
-
- Can be triggered with `elevasis-sdk exec my-workflow --input '{...}'`
|
|
326
|
-
- Will NOT receive scheduled or webhook-triggered executions
|
|
327
|
-
- Will NOT appear as available to external callers
|
|
328
|
-
|
|
329
|
-
When you are ready to go live, change to `'prod'` and redeploy:
|
|
330
|
-
|
|
331
|
-
```typescript
|
|
332
|
-
config: {
|
|
333
|
-
name: 'my-workflow',
|
|
334
|
-
description: 'Does something useful',
|
|
335
|
-
status: 'prod', // receives all execution sources
|
|
336
|
-
},
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
### Global Default Status
|
|
340
|
-
|
|
341
|
-
Set a project-wide default in `elevasis.config.ts` to keep all resources in `'dev'` mode during development without touching each resource file:
|
|
342
|
-
|
|
343
|
-
```typescript
|
|
344
|
-
import type { ElevasConfig } from '@elevasis/sdk';
|
|
345
|
-
|
|
346
|
-
const config: ElevasConfig = {
|
|
347
|
-
defaultStatus: 'dev',
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
export default config;
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
Individual resources that set their own `config.status` override this default.
|
|
354
|
-
|
|
355
|
-
---
|
|
356
|
-
|
|
357
|
-
## Organizing Multiple Resources
|
|
358
|
-
|
|
359
|
-
As your project grows, organize resources by business domain. Each domain gets its own directory with an `index.ts` barrel that exports `workflows` and `agents` arrays:
|
|
360
|
-
|
|
361
|
-
```typescript
|
|
362
|
-
// src/orders/fulfill-order.ts
|
|
363
|
-
export const fulfillOrder: WorkflowDefinition = { ... };
|
|
364
|
-
|
|
365
|
-
// src/billing/send-invoice.ts
|
|
366
|
-
export const sendInvoice: WorkflowDefinition = { ... };
|
|
367
|
-
|
|
368
|
-
// src/orders/index.ts
|
|
369
|
-
import { fulfillOrder } from './fulfill-order.js';
|
|
370
|
-
export const workflows = [fulfillOrder];
|
|
371
|
-
export const agents = [];
|
|
372
|
-
|
|
373
|
-
// src/billing/index.ts
|
|
374
|
-
import { sendInvoice } from './send-invoice.js';
|
|
375
|
-
export const workflows = [sendInvoice];
|
|
376
|
-
export const agents = [];
|
|
377
|
-
|
|
378
|
-
// src/index.ts
|
|
379
|
-
import type { DeploymentSpec } from '@elevasis/sdk';
|
|
380
|
-
import * as orders from './orders/index.js';
|
|
381
|
-
import * as billing from './billing/index.js';
|
|
382
|
-
|
|
383
|
-
const org: DeploymentSpec = {
|
|
384
|
-
workflows: [
|
|
385
|
-
...orders.workflows,
|
|
386
|
-
...billing.workflows,
|
|
387
|
-
],
|
|
388
|
-
agents: [
|
|
389
|
-
...orders.agents,
|
|
390
|
-
...billing.agents,
|
|
391
|
-
],
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
export default org;
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
The keys in `workflows` and `agents` are the resource identifiers used in CLI commands and the dashboard. Choose names that are descriptive and use kebab-case for consistency.
|
|
398
|
-
|
|
399
|
-
---
|
|
400
|
-
|
|
401
|
-
---
|
|
402
|
-
|
|
403
|
-
## Human-in-the-Loop UI Integration
|
|
404
|
-
|
|
405
|
-
The platform's HITL mechanism works in two parts: your workflow code creates an approval task, and a UI surfaces that task to a reviewer.
|
|
406
|
-
|
|
407
|
-
### Creating approval tasks
|
|
408
|
-
|
|
409
|
-
Call `approval.create()` from any workflow step to pause execution and emit a task to the Command Queue:
|
|
410
|
-
|
|
411
|
-
```typescript
|
|
412
|
-
import { approval } from '@elevasis/sdk/worker'
|
|
413
|
-
|
|
414
|
-
const task = await approval.create({
|
|
415
|
-
actions: [
|
|
416
|
-
{ id: 'approve', label: 'Approve', type: 'primary' },
|
|
417
|
-
{ id: 'reject', label: 'Reject', type: 'danger' },
|
|
418
|
-
],
|
|
419
|
-
context: { dealId, proposalUrl },
|
|
420
|
-
description: 'Review proposal before sending',
|
|
421
|
-
})
|
|
422
|
-
|
|
423
|
-
if (task.actionId === 'approve') {
|
|
424
|
-
// continue with approved path
|
|
425
|
-
}
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
The workflow step suspends at `approval.create()` and resumes only after a reviewer submits an action. See [Platform Adapters](../platform-tools/adapters-platform.mdx) for the full `approval.create()` reference.
|
|
429
|
-
|
|
430
|
-
### Built-in Command Center handling
|
|
431
|
-
|
|
432
|
-
The Command Queue page in the Command Center surfaces all pending approval tasks automatically. Reviewers can filter by resource, approve or reject with an optional comment, and view decision history. No custom UI code is required -- deploy your workflow and the queue populates as tasks arrive.
|
|
433
|
-
|
|
434
|
-
### Custom UI handling
|
|
435
|
-
|
|
436
|
-
`@elevasis/ui` exposes the following hooks for approval task operations, exported from `@elevasis/ui/hooks`:
|
|
437
|
-
|
|
438
|
-
- `useCommandQueue` -- fetches the list of pending tasks for the organization
|
|
439
|
-
- `useSubmitAction` -- POSTs an action decision to `/command-queue/{taskId}/action`
|
|
440
|
-
- `usePatchTask` -- updates task metadata
|
|
441
|
-
- `useDeleteTask` -- removes a task
|
|
442
|
-
|
|
443
|
-
Use these hooks to build a custom approval queue surface in your template UI. `useSubmitAction` accepts `taskId`, `actionId`, and optional `notes`, and optimistically marks the task as `processing` while the request is in flight.
|
|
444
|
-
|
|
445
|
-
For triggering executions from custom pages (the other side of the workflow interaction), see [UI Execution](../deployment/ui-execution.mdx).
|
|
446
|
-
|
|
447
|
-
---
|
|
448
|
-
|
|
449
|
-
**Last Updated:** 2026-02-25
|
|
1
|
+
---
|
|
2
|
+
title: Common Patterns
|
|
3
|
+
description: Common resource patterns for Elevasis SDK developers -- sequential steps, conditional branching, platform tools, error handling, and resource status management
|
|
4
|
+
loadWhen: "Building or modifying a workflow"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
This page collects the patterns you will reach for most often when writing resources. Each pattern is self-contained and can be adapted directly into your project.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Sequential Workflow Steps
|
|
12
|
+
|
|
13
|
+
The simplest pattern: a chain of steps where each step feeds its output into the next.
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { StepType } from '@elevasis/sdk';
|
|
18
|
+
import type { WorkflowDefinition, WorkflowStep } from '@elevasis/sdk';
|
|
19
|
+
|
|
20
|
+
const inputSchema = z.object({ orderId: z.string() });
|
|
21
|
+
const outputSchema = z.object({ shipped: z.boolean(), trackingNumber: z.string() });
|
|
22
|
+
|
|
23
|
+
type Input = z.infer<typeof inputSchema>;
|
|
24
|
+
type Output = z.infer<typeof outputSchema>;
|
|
25
|
+
|
|
26
|
+
const validateStep: WorkflowStep = {
|
|
27
|
+
type: StepType.LINEAR,
|
|
28
|
+
handler: async (input: Input) => {
|
|
29
|
+
const order = await getOrder(input.orderId);
|
|
30
|
+
if (!order) throw new Error(`Order ${input.orderId} not found`);
|
|
31
|
+
return { order };
|
|
32
|
+
},
|
|
33
|
+
next: { type: 'linear', target: 'ship' },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const shipStep: WorkflowStep = {
|
|
37
|
+
type: StepType.LINEAR,
|
|
38
|
+
handler: async (input) => {
|
|
39
|
+
const tracking = await createShipment(input.order);
|
|
40
|
+
return { shipped: true, trackingNumber: tracking.number };
|
|
41
|
+
},
|
|
42
|
+
next: null, // terminal -- no further steps
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const fulfillOrder: WorkflowDefinition = {
|
|
46
|
+
config: { resourceId: 'fulfill-order', name: 'Fulfill Order', type: 'workflow', description: 'Validates and ships an order', version: '1.0.0', status: 'dev' },
|
|
47
|
+
contract: { inputSchema, outputSchema },
|
|
48
|
+
steps: { validate: validateStep, ship: shipStep },
|
|
49
|
+
entryPoint: 'validate',
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Key points:**
|
|
54
|
+
|
|
55
|
+
- `next: { target: 'stepName' }` routes to the next step
|
|
56
|
+
- `next: null` marks the terminal step
|
|
57
|
+
- Each step receives the full return value of the previous step as its `input`
|
|
58
|
+
- The terminal step's return value must satisfy `contract.output`
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Conditional Branching
|
|
63
|
+
|
|
64
|
+
Use `StepType.CONDITIONAL` when the next step depends on the output of the current step.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { StepType } from '@elevasis/sdk';
|
|
68
|
+
import type { WorkflowStep } from '@elevasis/sdk';
|
|
69
|
+
|
|
70
|
+
const scoreStep: WorkflowStep = {
|
|
71
|
+
type: StepType.CONDITIONAL,
|
|
72
|
+
handler: async (input) => {
|
|
73
|
+
const score = await calculateRiskScore(input.applicationId);
|
|
74
|
+
return { score, applicationId: input.applicationId };
|
|
75
|
+
},
|
|
76
|
+
next: {
|
|
77
|
+
type: 'conditional',
|
|
78
|
+
routes: [
|
|
79
|
+
{
|
|
80
|
+
condition: (output) => output.score \>= 80,
|
|
81
|
+
target: 'autoApprove',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
condition: (output) => output.score \>= 40,
|
|
85
|
+
target: 'manualReview',
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
default: 'autoReject', // used when no condition matches
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Key points:**
|
|
94
|
+
|
|
95
|
+
- Routes are evaluated in order -- the first matching condition wins
|
|
96
|
+
- `default` is required and acts as the `else` branch
|
|
97
|
+
- The condition function receives the full handler output
|
|
98
|
+
- All route targets and `default` must be keys in your `steps` record
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Using Platform Tools in Steps
|
|
103
|
+
|
|
104
|
+
Platform tools let your steps call integrations managed by Elevasis (email, CRM, databases, etc.). Import `platform` from `@elevasis/sdk/worker` and call it with the tool name, method, parameters, and an optional credential reference when the tool requires one.
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { platform, PlatformToolError } from '@elevasis/sdk/worker';
|
|
108
|
+
import type { WorkflowStep } from '@elevasis/sdk';
|
|
109
|
+
import { StepType } from '@elevasis/sdk';
|
|
110
|
+
|
|
111
|
+
const sendEmailStep: WorkflowStep = {
|
|
112
|
+
type: StepType.LINEAR,
|
|
113
|
+
handler: async (input, context) => {
|
|
114
|
+
const result = await platform.call({
|
|
115
|
+
tool: 'email',
|
|
116
|
+
method: 'send',
|
|
117
|
+
params: {
|
|
118
|
+
to: input.recipientEmail,
|
|
119
|
+
subject: input.subject,
|
|
120
|
+
body: input.body,
|
|
121
|
+
},
|
|
122
|
+
credential: 'sendgrid', // name of the stored credential
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
context.logger.info('Email sent', { messageId: result.messageId });
|
|
126
|
+
return { sent: true, messageId: result.messageId };
|
|
127
|
+
},
|
|
128
|
+
next: null,
|
|
129
|
+
};
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Key points:**
|
|
133
|
+
|
|
134
|
+
- `platform.call()` is async and times out after 60 seconds
|
|
135
|
+
- `credential` is the name of a platform environment variable set via `elevasis-sdk env set` when the tool needs one
|
|
136
|
+
- On failure, `platform.call()` throws `PlatformToolError` (not `ToolingError`)
|
|
137
|
+
- Always log success so executions are easy to debug in the dashboard
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Error Handling
|
|
142
|
+
|
|
143
|
+
### Catching Tool Errors
|
|
144
|
+
|
|
145
|
+
Use `PlatformToolError` (from `@elevasis/sdk/worker`) to handle tool-specific failures without catching everything:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { platform, PlatformToolError } from '@elevasis/sdk/worker';
|
|
149
|
+
|
|
150
|
+
const step = async (input) => {
|
|
151
|
+
try {
|
|
152
|
+
const deals = await platform.call({
|
|
153
|
+
tool: 'crm',
|
|
154
|
+
method: 'listDeals',
|
|
155
|
+
params: { stage: 'proposal', limit: 10 },
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const deal = deals[0]
|
|
159
|
+
? await platform.call({
|
|
160
|
+
tool: 'crm',
|
|
161
|
+
method: 'getDeal',
|
|
162
|
+
params: { dealId: deals[0].id },
|
|
163
|
+
})
|
|
164
|
+
: null;
|
|
165
|
+
|
|
166
|
+
if (deal) {
|
|
167
|
+
await platform.call({
|
|
168
|
+
tool: 'crm',
|
|
169
|
+
method: 'updateDealStage',
|
|
170
|
+
params: { dealId: deal.id, stage: 'closing' },
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
await platform.call({
|
|
174
|
+
tool: 'crm',
|
|
175
|
+
method: 'createDealNote',
|
|
176
|
+
params: {
|
|
177
|
+
dealId: deal.id,
|
|
178
|
+
body: 'Reviewed proposal and moved the deal forward.',
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await platform.call({
|
|
183
|
+
tool: 'crm',
|
|
184
|
+
method: 'createDealTask',
|
|
185
|
+
params: {
|
|
186
|
+
dealId: deal.id,
|
|
187
|
+
title: 'Send updated proposal',
|
|
188
|
+
dueAt: input.followUpAt ?? null,
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
await platform.call({
|
|
193
|
+
tool: 'crm',
|
|
194
|
+
method: 'recordActivity',
|
|
195
|
+
params: {
|
|
196
|
+
dealId: deal.id,
|
|
197
|
+
type: 'stage_changed',
|
|
198
|
+
title: 'Deal moved to closing',
|
|
199
|
+
description: 'Reviewed the proposal and moved the deal forward.',
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return { dealId: deal?.id ?? null, dealCount: deals.length };
|
|
205
|
+
} catch (err) {
|
|
206
|
+
if (err instanceof PlatformToolError) {
|
|
207
|
+
// Tool failed -- log it and return a degraded result
|
|
208
|
+
console.error('CRM tool failed:', err.message);
|
|
209
|
+
return { dealId: null, error: err.message };
|
|
210
|
+
}
|
|
211
|
+
throw err; // re-throw unexpected errors
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Failing an Execution Explicitly
|
|
217
|
+
|
|
218
|
+
Use `ExecutionError` when your step detects a condition that should mark the entire execution as failed:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { ExecutionError } from '@elevasis/sdk';
|
|
222
|
+
|
|
223
|
+
const validateStep = async (input) => {
|
|
224
|
+
if (!input.userId) {
|
|
225
|
+
throw new ExecutionError('userId is required', { code: 'MISSING_INPUT' });
|
|
226
|
+
}
|
|
227
|
+
if (input.amount \<= 0) {
|
|
228
|
+
throw new ExecutionError('amount must be positive', { code: 'INVALID_AMOUNT' });
|
|
229
|
+
}
|
|
230
|
+
return { valid: true };
|
|
231
|
+
};
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`ExecutionError` messages and metadata appear in the Elevasis dashboard under the failed execution's detail view.
|
|
235
|
+
|
|
236
|
+
### Using ToolingError
|
|
237
|
+
|
|
238
|
+
`ToolingError` is thrown by lower-level platform operations (not `platform.call()` directly). You may encounter it in advanced scenarios:
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import { ToolingError } from '@elevasis/sdk';
|
|
242
|
+
|
|
243
|
+
const step = async (input) => {
|
|
244
|
+
try {
|
|
245
|
+
return await doSomething(input);
|
|
246
|
+
} catch (err) {
|
|
247
|
+
if (err instanceof ToolingError) {
|
|
248
|
+
// check err.type for the error category
|
|
249
|
+
console.error('Tooling error:', err.type, err.message);
|
|
250
|
+
}
|
|
251
|
+
throw err;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Logging in Steps
|
|
259
|
+
|
|
260
|
+
The `context.logger` writes structured logs attached to the execution. Use it instead of `console.log` so logs appear in the dashboard alongside the execution record.
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import type { StepHandler } from '@elevasis/sdk';
|
|
264
|
+
|
|
265
|
+
const processStep: StepHandler = async (input, context) => {
|
|
266
|
+
context.logger.info('Starting process', { userId: input.userId });
|
|
267
|
+
|
|
268
|
+
const result = await doWork(input);
|
|
269
|
+
|
|
270
|
+
context.logger.info('Process complete', { resultId: result.id });
|
|
271
|
+
return result;
|
|
272
|
+
};
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Avoid logging sensitive values (API keys, passwords, PII) since logs are stored and visible in the dashboard.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Using the Execution Store
|
|
280
|
+
|
|
281
|
+
`context.store` is a simple key-value store scoped to the current execution. Use it to pass data between steps without coupling step interfaces, or to checkpoint long-running work.
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
const firstStep: StepHandler = async (input, context) => {
|
|
285
|
+
const data = await fetchExpensiveData(input.id);
|
|
286
|
+
|
|
287
|
+
// Save for use by later steps
|
|
288
|
+
await context.store.set('fetchedData', JSON.stringify(data));
|
|
289
|
+
|
|
290
|
+
return { fetched: true };
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const secondStep: StepHandler = async (input, context) => {
|
|
294
|
+
const raw = await context.store.get('fetchedData');
|
|
295
|
+
const data = JSON.parse(raw ?? '{}');
|
|
296
|
+
|
|
297
|
+
return { processed: transform(data) };
|
|
298
|
+
};
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Store values are strings. Serialize objects with `JSON.stringify` and parse with `JSON.parse`.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Resource Status
|
|
306
|
+
|
|
307
|
+
### Dev vs Production
|
|
308
|
+
|
|
309
|
+
While building a resource, set `config.status` to `'dev'`:
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
const myWorkflow: WorkflowDefinition = {
|
|
313
|
+
config: {
|
|
314
|
+
name: 'my-workflow',
|
|
315
|
+
description: 'Does something useful',
|
|
316
|
+
status: 'dev', // only manually triggerable
|
|
317
|
+
},
|
|
318
|
+
// ...
|
|
319
|
+
};
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Dev resources:
|
|
323
|
+
|
|
324
|
+
- Appear in `elevasis-sdk resources` output
|
|
325
|
+
- Can be triggered with `elevasis-sdk exec my-workflow --input '{...}'`
|
|
326
|
+
- Will NOT receive scheduled or webhook-triggered executions
|
|
327
|
+
- Will NOT appear as available to external callers
|
|
328
|
+
|
|
329
|
+
When you are ready to go live, change to `'prod'` and redeploy:
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
config: {
|
|
333
|
+
name: 'my-workflow',
|
|
334
|
+
description: 'Does something useful',
|
|
335
|
+
status: 'prod', // receives all execution sources
|
|
336
|
+
},
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Global Default Status
|
|
340
|
+
|
|
341
|
+
Set a project-wide default in `elevasis.config.ts` to keep all resources in `'dev'` mode during development without touching each resource file:
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
import type { ElevasConfig } from '@elevasis/sdk';
|
|
345
|
+
|
|
346
|
+
const config: ElevasConfig = {
|
|
347
|
+
defaultStatus: 'dev',
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
export default config;
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Individual resources that set their own `config.status` override this default.
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Organizing Multiple Resources
|
|
358
|
+
|
|
359
|
+
As your project grows, organize resources by business domain. Each domain gets its own directory with an `index.ts` barrel that exports `workflows` and `agents` arrays:
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
// src/orders/fulfill-order.ts
|
|
363
|
+
export const fulfillOrder: WorkflowDefinition = { ... };
|
|
364
|
+
|
|
365
|
+
// src/billing/send-invoice.ts
|
|
366
|
+
export const sendInvoice: WorkflowDefinition = { ... };
|
|
367
|
+
|
|
368
|
+
// src/orders/index.ts
|
|
369
|
+
import { fulfillOrder } from './fulfill-order.js';
|
|
370
|
+
export const workflows = [fulfillOrder];
|
|
371
|
+
export const agents = [];
|
|
372
|
+
|
|
373
|
+
// src/billing/index.ts
|
|
374
|
+
import { sendInvoice } from './send-invoice.js';
|
|
375
|
+
export const workflows = [sendInvoice];
|
|
376
|
+
export const agents = [];
|
|
377
|
+
|
|
378
|
+
// src/index.ts
|
|
379
|
+
import type { DeploymentSpec } from '@elevasis/sdk';
|
|
380
|
+
import * as orders from './orders/index.js';
|
|
381
|
+
import * as billing from './billing/index.js';
|
|
382
|
+
|
|
383
|
+
const org: DeploymentSpec = {
|
|
384
|
+
workflows: [
|
|
385
|
+
...orders.workflows,
|
|
386
|
+
...billing.workflows,
|
|
387
|
+
],
|
|
388
|
+
agents: [
|
|
389
|
+
...orders.agents,
|
|
390
|
+
...billing.agents,
|
|
391
|
+
],
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
export default org;
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
The keys in `workflows` and `agents` are the resource identifiers used in CLI commands and the dashboard. Choose names that are descriptive and use kebab-case for consistency.
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Human-in-the-Loop UI Integration
|
|
404
|
+
|
|
405
|
+
The platform's HITL mechanism works in two parts: your workflow code creates an approval task, and a UI surfaces that task to a reviewer.
|
|
406
|
+
|
|
407
|
+
### Creating approval tasks
|
|
408
|
+
|
|
409
|
+
Call `approval.create()` from any workflow step to pause execution and emit a task to the Command Queue:
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
import { approval } from '@elevasis/sdk/worker'
|
|
413
|
+
|
|
414
|
+
const task = await approval.create({
|
|
415
|
+
actions: [
|
|
416
|
+
{ id: 'approve', label: 'Approve', type: 'primary' },
|
|
417
|
+
{ id: 'reject', label: 'Reject', type: 'danger' },
|
|
418
|
+
],
|
|
419
|
+
context: { dealId, proposalUrl },
|
|
420
|
+
description: 'Review proposal before sending',
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
if (task.actionId === 'approve') {
|
|
424
|
+
// continue with approved path
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
The workflow step suspends at `approval.create()` and resumes only after a reviewer submits an action. See [Platform Adapters](../platform-tools/adapters-platform.mdx) for the full `approval.create()` reference.
|
|
429
|
+
|
|
430
|
+
### Built-in Command Center handling
|
|
431
|
+
|
|
432
|
+
The Command Queue page in the Command Center surfaces all pending approval tasks automatically. Reviewers can filter by resource, approve or reject with an optional comment, and view decision history. No custom UI code is required -- deploy your workflow and the queue populates as tasks arrive.
|
|
433
|
+
|
|
434
|
+
### Custom UI handling
|
|
435
|
+
|
|
436
|
+
`@elevasis/ui` exposes the following hooks for approval task operations, exported from `@elevasis/ui/hooks`:
|
|
437
|
+
|
|
438
|
+
- `useCommandQueue` -- fetches the list of pending tasks for the organization
|
|
439
|
+
- `useSubmitAction` -- POSTs an action decision to `/command-queue/{taskId}/action`
|
|
440
|
+
- `usePatchTask` -- updates task metadata
|
|
441
|
+
- `useDeleteTask` -- removes a task
|
|
442
|
+
|
|
443
|
+
Use these hooks to build a custom approval queue surface in your template UI. `useSubmitAction` accepts `taskId`, `actionId`, and optional `notes`, and optimistically marks the task as `processing` while the request is in flight.
|
|
444
|
+
|
|
445
|
+
For triggering executions from custom pages (the other side of the workflow interaction), see [UI Execution](../deployment/ui-execution.mdx).
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
**Last Updated:** 2026-02-25
|