@elevasis/core 0.10.0 → 0.11.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/index.d.ts +69 -159
- package/dist/index.js +324 -613
- package/dist/organization-model/index.d.ts +69 -159
- package/dist/organization-model/index.js +324 -613
- package/dist/test-utils/index.d.ts +192 -45
- package/dist/test-utils/index.js +260 -600
- package/package.json +1 -1
- package/src/__tests__/template-core-compatibility.test.ts +73 -91
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +94 -182
- package/src/auth/multi-tenancy/index.ts +20 -17
- package/src/auth/multi-tenancy/memberships/api-schemas.ts +142 -126
- package/src/auth/multi-tenancy/memberships/index.ts +26 -22
- package/src/auth/multi-tenancy/permissions.test.ts +42 -0
- package/src/auth/multi-tenancy/permissions.ts +104 -0
- package/src/organization-model/README.md +102 -97
- package/src/organization-model/__tests__/defaults.test.ts +19 -6
- package/src/organization-model/__tests__/domains/resource-mappings.test.ts +24 -93
- package/src/organization-model/__tests__/graph.test.ts +82 -894
- package/src/organization-model/__tests__/resolve.test.ts +59 -690
- package/src/organization-model/__tests__/schema.test.ts +83 -407
- package/src/organization-model/contracts.ts +4 -3
- package/src/organization-model/defaults.ts +277 -141
- package/src/organization-model/domains/features.ts +31 -22
- package/src/organization-model/domains/navigation.ts +27 -20
- package/src/organization-model/foundation.ts +42 -54
- package/src/organization-model/graph/build.ts +42 -217
- package/src/organization-model/graph/index.ts +4 -4
- package/src/organization-model/graph/link.ts +10 -0
- package/src/organization-model/graph/schema.ts +21 -16
- package/src/organization-model/graph/types.ts +10 -10
- package/src/organization-model/helpers.ts +74 -0
- package/src/organization-model/index.ts +7 -7
- package/src/organization-model/organization-graph.mdx +89 -272
- package/src/organization-model/organization-model.mdx +152 -320
- package/src/organization-model/published.ts +20 -19
- package/src/organization-model/resolve.ts +8 -33
- package/src/organization-model/schema.ts +63 -205
- package/src/organization-model/types.ts +12 -11
- package/src/platform/constants/versions.ts +3 -3
- package/src/platform/registry/__tests__/command-view.test.ts +6 -5
- package/src/platform/registry/__tests__/resource-link.test.ts +30 -0
- package/src/platform/registry/__tests__/resource-registry.integration.test.ts +15 -15
- package/src/platform/registry/command-view.ts +10 -12
- package/src/platform/registry/index.ts +93 -93
- package/src/platform/registry/resource-link.ts +32 -0
- package/src/platform/registry/resource-registry.ts +917 -876
- package/src/platform/registry/serialization.ts +56 -73
- package/src/platform/registry/serialized-types.ts +17 -12
- package/src/platform/registry/types.ts +14 -43
- package/src/reference/_generated/contracts.md +94 -182
- package/src/reference/glossary.md +71 -105
- package/src/scaffold-registry/__tests__/index.test.ts +125 -1
- package/src/scaffold-registry/__tests__/schema.test.ts +48 -20
- package/src/scaffold-registry/index.ts +236 -188
- package/src/scaffold-registry/schema.ts +47 -22
- package/src/supabase/database.types.ts +2880 -2719
- package/src/test-utils/fixtures/memberships.ts +82 -80
- package/src/platform/registry/domains.ts +0 -165
|
@@ -1,876 +1,917 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ResourceRegistry - Resource discovery and lookup
|
|
3
|
-
* Handles resource definitions from OrganizationRegistry
|
|
4
|
-
*
|
|
5
|
-
* Features:
|
|
6
|
-
* - Resource discovery by organization
|
|
7
|
-
* - Startup validation (duplicate IDs, model configs, relationships, interface-schema alignment)
|
|
8
|
-
* - Pre-serialization cache for instant API responses
|
|
9
|
-
* - Command View data generation
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type { WorkflowDefinition } from '../../execution/engine/workflow/types'
|
|
13
|
-
import type { AgentDefinition } from '../../execution/engine/agent/core/types'
|
|
14
|
-
import type {
|
|
15
|
-
ResourceStatus,
|
|
16
|
-
ResourceDefinition,
|
|
17
|
-
ResourceList,
|
|
18
|
-
TriggerDefinition,
|
|
19
|
-
IntegrationDefinition,
|
|
20
|
-
ResourceRelationships,
|
|
21
|
-
ExternalResourceDefinition,
|
|
22
|
-
HumanCheckpointDefinition
|
|
23
|
-
} from './types'
|
|
24
|
-
import type {
|
|
25
|
-
SerializedOrganizationData,
|
|
26
|
-
SerializedAgentDefinition,
|
|
27
|
-
SerializedWorkflowDefinition,
|
|
28
|
-
SerializedExecutionInterface,
|
|
29
|
-
CommandViewData
|
|
30
|
-
} from './serialized-types'
|
|
31
|
-
import { validateDeploymentSpec, validateRelationships } from './validation'
|
|
32
|
-
import { serializeAllOrganizations, serializeOrganization } from './serialization'
|
|
33
|
-
import { isReservedResourceId } from './reserved'
|
|
34
|
-
|
|
35
|
-
/** Filter out archived resources from an organization's resource collection */
|
|
36
|
-
function filterArchived(org: DeploymentSpec): DeploymentSpec {
|
|
37
|
-
return {
|
|
38
|
-
...org,
|
|
39
|
-
workflows: org.workflows?.filter((w) => !w.config.archived),
|
|
40
|
-
agents: org.agents?.filter((a) => !a.config.archived),
|
|
41
|
-
triggers: org.triggers?.filter((t) => !t.archived),
|
|
42
|
-
integrations: org.integrations?.filter((i) => !i.archived),
|
|
43
|
-
externalResources: org.externalResources?.filter((e) => !e.archived),
|
|
44
|
-
humanCheckpoints: org.humanCheckpoints?.filter((h) => !h.archived)
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Configuration for a remotely-deployed organization
|
|
50
|
-
*
|
|
51
|
-
* Stored alongside runtime-registered organizations to support
|
|
52
|
-
* worker thread execution branching and credential management.
|
|
53
|
-
*/
|
|
54
|
-
export interface RemoteOrgConfig {
|
|
55
|
-
/** Supabase Storage path: "{orgId}/{deploymentId}/bundle.js" */
|
|
56
|
-
storagePath: string
|
|
57
|
-
/** Deployment record ID */
|
|
58
|
-
deploymentId: string
|
|
59
|
-
/** OS temp path to bundle -- set after first download, used by worker threads */
|
|
60
|
-
cachedTempPath?: string
|
|
61
|
-
/** Platform tool name -> credential name mapping */
|
|
62
|
-
toolCredentials?: Record<string, string>
|
|
63
|
-
/** SDK version used to deploy this bundle */
|
|
64
|
-
sdkVersion?: string
|
|
65
|
-
/** Deployment version (semver) of the deployed bundle */
|
|
66
|
-
deploymentVersion?: string
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
*/
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
environment
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
//
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
//
|
|
412
|
-
|
|
413
|
-
this.
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
//
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
* @
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
*
|
|
630
|
-
*
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
*
|
|
659
|
-
* @
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
*
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
*
|
|
700
|
-
* @
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
*
|
|
709
|
-
* @param
|
|
710
|
-
* @returns
|
|
711
|
-
*/
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
*
|
|
719
|
-
* @param
|
|
720
|
-
* @returns
|
|
721
|
-
*/
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
*
|
|
729
|
-
* @param
|
|
730
|
-
* @
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
*
|
|
761
|
-
*
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
return
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
*
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
*
|
|
803
|
-
*
|
|
804
|
-
*
|
|
805
|
-
* @
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
1
|
+
/**
|
|
2
|
+
* ResourceRegistry - Resource discovery and lookup
|
|
3
|
+
* Handles resource definitions from OrganizationRegistry
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Resource discovery by organization
|
|
7
|
+
* - Startup validation (duplicate IDs, model configs, relationships, interface-schema alignment)
|
|
8
|
+
* - Pre-serialization cache for instant API responses
|
|
9
|
+
* - Command View data generation
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { WorkflowDefinition } from '../../execution/engine/workflow/types'
|
|
13
|
+
import type { AgentDefinition } from '../../execution/engine/agent/core/types'
|
|
14
|
+
import type {
|
|
15
|
+
ResourceStatus,
|
|
16
|
+
ResourceDefinition,
|
|
17
|
+
ResourceList,
|
|
18
|
+
TriggerDefinition,
|
|
19
|
+
IntegrationDefinition,
|
|
20
|
+
ResourceRelationships,
|
|
21
|
+
ExternalResourceDefinition,
|
|
22
|
+
HumanCheckpointDefinition
|
|
23
|
+
} from './types'
|
|
24
|
+
import type {
|
|
25
|
+
SerializedOrganizationData,
|
|
26
|
+
SerializedAgentDefinition,
|
|
27
|
+
SerializedWorkflowDefinition,
|
|
28
|
+
SerializedExecutionInterface,
|
|
29
|
+
CommandViewData
|
|
30
|
+
} from './serialized-types'
|
|
31
|
+
import { validateDeploymentSpec, validateRelationships } from './validation'
|
|
32
|
+
import { serializeAllOrganizations, serializeOrganization } from './serialization'
|
|
33
|
+
import { isReservedResourceId } from './reserved'
|
|
34
|
+
|
|
35
|
+
/** Filter out archived resources from an organization's resource collection */
|
|
36
|
+
function filterArchived(org: DeploymentSpec): DeploymentSpec {
|
|
37
|
+
return {
|
|
38
|
+
...org,
|
|
39
|
+
workflows: org.workflows?.filter((w) => !w.config.archived),
|
|
40
|
+
agents: org.agents?.filter((a) => !a.config.archived),
|
|
41
|
+
triggers: org.triggers?.filter((t) => !t.archived),
|
|
42
|
+
integrations: org.integrations?.filter((i) => !i.archived),
|
|
43
|
+
externalResources: org.externalResources?.filter((e) => !e.archived),
|
|
44
|
+
humanCheckpoints: org.humanCheckpoints?.filter((h) => !h.archived)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Configuration for a remotely-deployed organization
|
|
50
|
+
*
|
|
51
|
+
* Stored alongside runtime-registered organizations to support
|
|
52
|
+
* worker thread execution branching and credential management.
|
|
53
|
+
*/
|
|
54
|
+
export interface RemoteOrgConfig {
|
|
55
|
+
/** Supabase Storage path: "{orgId}/{deploymentId}/bundle.js" */
|
|
56
|
+
storagePath: string
|
|
57
|
+
/** Deployment record ID */
|
|
58
|
+
deploymentId: string
|
|
59
|
+
/** OS temp path to bundle -- set after first download, used by worker threads */
|
|
60
|
+
cachedTempPath?: string
|
|
61
|
+
/** Platform tool name -> credential name mapping */
|
|
62
|
+
toolCredentials?: Record<string, string>
|
|
63
|
+
/** SDK version used to deploy this bundle */
|
|
64
|
+
sdkVersion?: string
|
|
65
|
+
/** Deployment version (semver) of the deployed bundle */
|
|
66
|
+
deploymentVersion?: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Configuration for a first-class System resource.
|
|
71
|
+
*
|
|
72
|
+
* System resources are owned by the platform, registered under the 'system' org,
|
|
73
|
+
* and execute via the static-bundle loader mode in executeInWorker(). The moduleId
|
|
74
|
+
* maps to an entry in the API's STATIC_MODULE_MAP.
|
|
75
|
+
*/
|
|
76
|
+
export interface SystemConfig {
|
|
77
|
+
kind: 'static'
|
|
78
|
+
moduleId: string
|
|
79
|
+
/** Always undefined for system resources; present for API compatibility with RemoteOrgConfig consumers */
|
|
80
|
+
sdkVersion?: never
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Organization-specific resource collection
|
|
85
|
+
*
|
|
86
|
+
* Complete manifest of all automation resources for an organization.
|
|
87
|
+
* Used by ResourceRegistry for discovery and Command View for visualization.
|
|
88
|
+
*/
|
|
89
|
+
export interface DeploymentSpec {
|
|
90
|
+
/** Deployment version (semver) */
|
|
91
|
+
version: string
|
|
92
|
+
/** Workflow definitions */
|
|
93
|
+
workflows?: WorkflowDefinition[]
|
|
94
|
+
/** Agent definitions */
|
|
95
|
+
agents?: AgentDefinition[]
|
|
96
|
+
|
|
97
|
+
// Resource Manifest fields (optional for backwards compatibility)
|
|
98
|
+
/** Trigger definitions - entry points that initiate executions */
|
|
99
|
+
triggers?: TriggerDefinition[]
|
|
100
|
+
/** Integration definitions - external service connections */
|
|
101
|
+
integrations?: IntegrationDefinition[]
|
|
102
|
+
/** Explicit relationship declarations between resources */
|
|
103
|
+
relationships?: ResourceRelationships
|
|
104
|
+
/** External automation resources (n8n, Make, Zapier, etc.) */
|
|
105
|
+
externalResources?: ExternalResourceDefinition[]
|
|
106
|
+
/** Human checkpoint definitions - human decision points in automation */
|
|
107
|
+
humanCheckpoints?: HumanCheckpointDefinition[]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Organization Registry type
|
|
112
|
+
*/
|
|
113
|
+
export type OrganizationRegistry = Record<string, DeploymentSpec>
|
|
114
|
+
|
|
115
|
+
export class ResourceRegistry {
|
|
116
|
+
/**
|
|
117
|
+
* Pre-serialized organization data cache
|
|
118
|
+
* Computed once at construction for static orgs, updated incrementally for runtime orgs
|
|
119
|
+
*/
|
|
120
|
+
private serializedCache: Map<string, SerializedOrganizationData>
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Per-resource remote configuration (external deployments)
|
|
124
|
+
* Key: "orgName/resourceId", Value: RemoteOrgConfig for that resource.
|
|
125
|
+
* Tracks which individual resources were added at runtime via deploy pipeline.
|
|
126
|
+
* Static and remote resources coexist in the same org.
|
|
127
|
+
*/
|
|
128
|
+
private remoteResources = new Map<string, RemoteOrgConfig>()
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* System configs for first-class platform resources.
|
|
132
|
+
* Key: "orgName/resourceId", Value: SystemConfig.
|
|
133
|
+
* Registered at startup alongside registerStaticResources().
|
|
134
|
+
*/
|
|
135
|
+
private systemConfigs = new Map<string, SystemConfig>()
|
|
136
|
+
|
|
137
|
+
constructor(private registry: OrganizationRegistry) {
|
|
138
|
+
this.validateRegistry()
|
|
139
|
+
this.validateRelationships()
|
|
140
|
+
this.serializedCache = serializeAllOrganizations(registry)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Validates registry on construction
|
|
145
|
+
* - Checks for duplicate resourceIds within organizations
|
|
146
|
+
* - Validates model configurations against constraints
|
|
147
|
+
* - Validates ExecutionInterface matches inputSchema
|
|
148
|
+
* @throws Error if validation fails
|
|
149
|
+
*/
|
|
150
|
+
private validateRegistry(): void {
|
|
151
|
+
for (const [orgName, resources] of Object.entries(this.registry)) {
|
|
152
|
+
validateDeploymentSpec(orgName, resources)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Validates relationship declarations reference valid resources
|
|
158
|
+
* Runs at API server startup - fails fast in development
|
|
159
|
+
* @throws Error if validation fails
|
|
160
|
+
*/
|
|
161
|
+
private validateRelationships(): void {
|
|
162
|
+
for (const [orgName, resources] of Object.entries(this.registry)) {
|
|
163
|
+
validateRelationships(orgName, resources)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get the remote resource IDs currently registered for an organization.
|
|
169
|
+
* Used to validate redeployments against the post-swap state before any
|
|
170
|
+
* live registry mutation occurs.
|
|
171
|
+
*/
|
|
172
|
+
private getRemoteResourceIds(orgName: string): Set<string> {
|
|
173
|
+
const prefix = `${orgName}/`
|
|
174
|
+
const remoteIds = new Set<string>()
|
|
175
|
+
for (const key of this.remoteResources.keys()) {
|
|
176
|
+
if (key.startsWith(prefix)) {
|
|
177
|
+
remoteIds.add(key.slice(prefix.length))
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return remoteIds
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Build the "static + surviving" baseline for registration validation.
|
|
185
|
+
* On redeploy, this strips the currently remote-owned resources and
|
|
186
|
+
* deployment-owned metadata so validation reflects the state after swap.
|
|
187
|
+
*/
|
|
188
|
+
private buildRegistrationBase(orgName: string): DeploymentSpec | undefined {
|
|
189
|
+
const existingOrg = this.registry[orgName]
|
|
190
|
+
if (!existingOrg) return undefined
|
|
191
|
+
|
|
192
|
+
const remoteIds = this.getRemoteResourceIds(orgName)
|
|
193
|
+
if (remoteIds.size === 0) return existingOrg
|
|
194
|
+
|
|
195
|
+
const relationships = existingOrg.relationships
|
|
196
|
+
? Object.fromEntries(
|
|
197
|
+
Object.entries(existingOrg.relationships).filter(([resourceId]) => !remoteIds.has(resourceId))
|
|
198
|
+
)
|
|
199
|
+
: undefined
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
...existingOrg,
|
|
203
|
+
version: existingOrg.version ?? '0.0.0',
|
|
204
|
+
workflows: (existingOrg.workflows ?? []).filter((w) => !remoteIds.has(w.config.resourceId)),
|
|
205
|
+
agents: (existingOrg.agents ?? []).filter((a) => !remoteIds.has(a.config.resourceId)),
|
|
206
|
+
triggers: undefined,
|
|
207
|
+
integrations: undefined,
|
|
208
|
+
humanCheckpoints: undefined,
|
|
209
|
+
externalResources: undefined,
|
|
210
|
+
relationships: relationships && Object.keys(relationships).length > 0 ? relationships : undefined
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Validate the registry state that would exist after registration succeeds.
|
|
216
|
+
* This runs before any live mutation so invalid redeploys preserve the
|
|
217
|
+
* currently active remote resources.
|
|
218
|
+
*/
|
|
219
|
+
private validateRegistrationCandidate(orgName: string, incoming: DeploymentSpec): void {
|
|
220
|
+
const base = this.buildRegistrationBase(orgName)
|
|
221
|
+
|
|
222
|
+
const candidate: DeploymentSpec = base
|
|
223
|
+
? {
|
|
224
|
+
...base,
|
|
225
|
+
version: incoming.version ?? base.version ?? '0.0.0',
|
|
226
|
+
workflows: [...(base.workflows ?? []), ...(incoming.workflows ?? [])],
|
|
227
|
+
agents: [...(base.agents ?? []), ...(incoming.agents ?? [])],
|
|
228
|
+
triggers: incoming.triggers,
|
|
229
|
+
integrations: incoming.integrations,
|
|
230
|
+
humanCheckpoints: incoming.humanCheckpoints,
|
|
231
|
+
externalResources: incoming.externalResources,
|
|
232
|
+
relationships: incoming.relationships
|
|
233
|
+
? {
|
|
234
|
+
...(base.relationships ?? {}),
|
|
235
|
+
...incoming.relationships
|
|
236
|
+
}
|
|
237
|
+
: base.relationships
|
|
238
|
+
}
|
|
239
|
+
: {
|
|
240
|
+
...incoming,
|
|
241
|
+
version: incoming.version ?? '0.0.0'
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
validateDeploymentSpec(orgName, candidate)
|
|
245
|
+
validateRelationships(orgName, candidate)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get a resource definition by ID
|
|
250
|
+
* Returns full definition (WorkflowDefinition or AgentDefinition)
|
|
251
|
+
* Check definition.config.type to determine if it's a workflow or agent
|
|
252
|
+
*/
|
|
253
|
+
getResourceDefinition(organizationName: string, resourceId: string): WorkflowDefinition | AgentDefinition | null {
|
|
254
|
+
const orgResources = this.registry[organizationName]
|
|
255
|
+
if (!orgResources) return null
|
|
256
|
+
|
|
257
|
+
// Check workflows first
|
|
258
|
+
const workflow = orgResources.workflows?.find((w) => w.config.resourceId === resourceId)
|
|
259
|
+
if (workflow) return workflow
|
|
260
|
+
|
|
261
|
+
// Check agents
|
|
262
|
+
const agent = orgResources.agents?.find((a) => a.config.resourceId === resourceId)
|
|
263
|
+
if (agent) return agent
|
|
264
|
+
|
|
265
|
+
return null
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* List all resources for an organization
|
|
270
|
+
* Returns ResourceDefinition metadata (not full definitions)
|
|
271
|
+
*
|
|
272
|
+
* All resources are returned regardless of server environment.
|
|
273
|
+
* Pass an explicit `environment` filter to get only 'dev' or 'prod' resources.
|
|
274
|
+
*/
|
|
275
|
+
listResourcesForOrganization(organizationName: string, environment?: ResourceStatus): ResourceList {
|
|
276
|
+
const orgResources = this.registry[organizationName]
|
|
277
|
+
if (!orgResources) {
|
|
278
|
+
return {
|
|
279
|
+
workflows: [],
|
|
280
|
+
agents: [],
|
|
281
|
+
total: 0,
|
|
282
|
+
organizationName,
|
|
283
|
+
environment
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Map workflows to ResourceDefinition metadata and filter by environment
|
|
288
|
+
const workflows: ResourceDefinition[] = (orgResources.workflows || [])
|
|
289
|
+
.map((def) => ({
|
|
290
|
+
resourceId: def.config.resourceId,
|
|
291
|
+
name: def.config.name,
|
|
292
|
+
description: def.config.description,
|
|
293
|
+
version: def.config.version,
|
|
294
|
+
type: def.config.type,
|
|
295
|
+
status: def.config.status,
|
|
296
|
+
links: def.config.links,
|
|
297
|
+
category: def.config.category,
|
|
298
|
+
origin: this.remoteResources.has(`${organizationName}/${def.config.resourceId}`)
|
|
299
|
+
? ('remote' as const)
|
|
300
|
+
: ('local' as const)
|
|
301
|
+
}))
|
|
302
|
+
.filter((resource) => !environment || resource.status === environment)
|
|
303
|
+
|
|
304
|
+
// Map agents to ResourceDefinition metadata and filter by environment
|
|
305
|
+
const agents: ResourceDefinition[] = (orgResources.agents || [])
|
|
306
|
+
.map((def) => ({
|
|
307
|
+
resourceId: def.config.resourceId,
|
|
308
|
+
name: def.config.name,
|
|
309
|
+
description: def.config.description,
|
|
310
|
+
version: def.config.version,
|
|
311
|
+
type: def.config.type,
|
|
312
|
+
status: def.config.status,
|
|
313
|
+
links: def.config.links,
|
|
314
|
+
category: def.config.category,
|
|
315
|
+
sessionCapable: def.config.sessionCapable ?? false,
|
|
316
|
+
origin: this.remoteResources.has(`${organizationName}/${def.config.resourceId}`)
|
|
317
|
+
? ('remote' as const)
|
|
318
|
+
: ('local' as const)
|
|
319
|
+
}))
|
|
320
|
+
.filter((resource) => !environment || resource.status === environment)
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
workflows,
|
|
324
|
+
agents,
|
|
325
|
+
total: workflows.length + agents.length,
|
|
326
|
+
organizationName,
|
|
327
|
+
environment
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* List all resources from all organizations
|
|
333
|
+
* NOTE: For debugging only - returns raw registry data
|
|
334
|
+
*/
|
|
335
|
+
listAllResources(): OrganizationRegistry {
|
|
336
|
+
return this.registry
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ============================================================================
|
|
340
|
+
// Runtime Organization Registration (External Deployments)
|
|
341
|
+
// ============================================================================
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Register external resources at runtime
|
|
345
|
+
*
|
|
346
|
+
* Called during deploy pipeline when an external developer deploys their bundle.
|
|
347
|
+
* Merges the incoming stub definitions into the org's registry and stores
|
|
348
|
+
* per-resource remote config for worker thread execution branching.
|
|
349
|
+
*
|
|
350
|
+
* Static and remote resources coexist in the same org. If the org already
|
|
351
|
+
* has static resources, the incoming remote resources are merged alongside them.
|
|
352
|
+
* If redeploying (some resources already registered as remote for this org),
|
|
353
|
+
* the previous remote resources are unregistered first.
|
|
354
|
+
*
|
|
355
|
+
* @param orgName - Organization name (used as registry key)
|
|
356
|
+
* @param org - Stub resource definitions (workflows/agents with placeholder handlers)
|
|
357
|
+
* @param remote - Remote configuration (bundle path, deployment ID, env vars)
|
|
358
|
+
* @throws Error if incoming resourceId conflicts with a static resource
|
|
359
|
+
* @throws Error if incoming deployment contains duplicate resourceIds
|
|
360
|
+
*/
|
|
361
|
+
registerOrganization(orgName: string, org: DeploymentSpec, remote: RemoteOrgConfig): void {
|
|
362
|
+
// Filter out archived resources before any processing
|
|
363
|
+
org = filterArchived(org)
|
|
364
|
+
|
|
365
|
+
// Collect all incoming resource IDs for conflict checking
|
|
366
|
+
const incomingWorkflowIds = (org.workflows ?? []).map((w) => w.config.resourceId)
|
|
367
|
+
const incomingAgentIds = (org.agents ?? []).map((a) => a.config.resourceId)
|
|
368
|
+
const incomingIds = [...incomingWorkflowIds, ...incomingAgentIds]
|
|
369
|
+
|
|
370
|
+
// Check for intra-deployment duplicates
|
|
371
|
+
const seen = new Set<string>()
|
|
372
|
+
for (const id of incomingIds) {
|
|
373
|
+
if (seen.has(id)) {
|
|
374
|
+
throw new Error(`Duplicate resource ID '${id}' in deployment. Each resource must have a unique ID.`)
|
|
375
|
+
}
|
|
376
|
+
seen.add(id)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Check for reserved resource IDs (cannot be claimed by external deployments)
|
|
380
|
+
for (const id of incomingIds) {
|
|
381
|
+
if (isReservedResourceId(id)) {
|
|
382
|
+
throw new Error(
|
|
383
|
+
`Resource ID '${id}' is reserved for platform use. External deployments cannot use reserved resource IDs.`
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Check for conflicts against the static/surviving org state.
|
|
389
|
+
// On redeploy, current remote resources are excluded so a deployment can
|
|
390
|
+
// legitimately replace its own resource IDs without colliding with itself.
|
|
391
|
+
const validationBase = this.buildRegistrationBase(orgName)
|
|
392
|
+
if (validationBase) {
|
|
393
|
+
const staticWorkflowIds = new Set((validationBase.workflows ?? []).map((w) => w.config.resourceId))
|
|
394
|
+
const staticAgentIds = new Set((validationBase.agents ?? []).map((a) => a.config.resourceId))
|
|
395
|
+
|
|
396
|
+
for (const id of incomingIds) {
|
|
397
|
+
if (staticWorkflowIds.has(id) || staticAgentIds.has(id)) {
|
|
398
|
+
throw new Error(
|
|
399
|
+
`Resource '${id}' already exists in '${orgName}' as an internal resource. External deployments cannot override internal resources.`
|
|
400
|
+
)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Validate the merged deployment shape before mutating the live registry.
|
|
406
|
+
// This keeps the current deployment intact if a redeploy introduces
|
|
407
|
+
// invalid relationships or other registry-level validation failures.
|
|
408
|
+
this.validateRegistrationCandidate(orgName, org)
|
|
409
|
+
|
|
410
|
+
// If redeploying (some resources already registered as remote for this org), clean up only
|
|
411
|
+
// after validation succeeds so a failed redeploy preserves the current remote resources.
|
|
412
|
+
if (this.isRemote(orgName)) {
|
|
413
|
+
this.unregisterOrganization(orgName)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Merge incoming resources into the org (or create new org entry)
|
|
417
|
+
const existingOrg = this.registry[orgName]
|
|
418
|
+
if (existingOrg) {
|
|
419
|
+
existingOrg.workflows = [...(existingOrg.workflows ?? []), ...(org.workflows ?? [])]
|
|
420
|
+
existingOrg.agents = [...(existingOrg.agents ?? []), ...(org.agents ?? [])]
|
|
421
|
+
// Deployment-owned metadata: replace entirely (unregister cleared these)
|
|
422
|
+
existingOrg.triggers = org.triggers
|
|
423
|
+
existingOrg.integrations = org.integrations
|
|
424
|
+
existingOrg.humanCheckpoints = org.humanCheckpoints
|
|
425
|
+
existingOrg.externalResources = org.externalResources
|
|
426
|
+
if (org.relationships) {
|
|
427
|
+
existingOrg.relationships = {
|
|
428
|
+
...(existingOrg.relationships ?? {}),
|
|
429
|
+
...org.relationships
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
} else {
|
|
433
|
+
this.registry[orgName] = org
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Populate per-resource remote config entries
|
|
437
|
+
for (const id of incomingIds) {
|
|
438
|
+
this.remoteResources.set(`${orgName}/${id}`, remote)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Rebuild serialized cache for the full merged org
|
|
442
|
+
this.serializedCache.set(orgName, serializeOrganization(this.registry[orgName]))
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Patch serialized cache with pre-serialized schemas from an external manifest.
|
|
447
|
+
*
|
|
448
|
+
* External deployments use stub definitions with z.any() schemas (never called).
|
|
449
|
+
* The manifest carries the real schemas as pre-serialized JSON Schema from the worker.
|
|
450
|
+
* This method patches those into the serialized cache so describe/CLI display them.
|
|
451
|
+
*
|
|
452
|
+
* @param orgName - Organization name
|
|
453
|
+
* @param manifestSchemas - Map of resourceId -> { contract, steps } with JSON Schema
|
|
454
|
+
*/
|
|
455
|
+
patchManifestSchemas(
|
|
456
|
+
orgName: string,
|
|
457
|
+
manifestSchemas: Array<{
|
|
458
|
+
resourceId: string
|
|
459
|
+
type: 'workflow' | 'agent'
|
|
460
|
+
contract?: { inputSchema?: object; outputSchema?: object }
|
|
461
|
+
steps?: Array<{ id: string; inputSchema?: object; outputSchema?: object }>
|
|
462
|
+
}>
|
|
463
|
+
): void {
|
|
464
|
+
const cache = this.serializedCache.get(orgName)
|
|
465
|
+
if (!cache) return
|
|
466
|
+
|
|
467
|
+
for (const entry of manifestSchemas) {
|
|
468
|
+
if (entry.type === 'workflow') {
|
|
469
|
+
const def = cache.definitions.workflows.get(entry.resourceId)
|
|
470
|
+
if (!def) continue
|
|
471
|
+
|
|
472
|
+
// Patch contract schemas
|
|
473
|
+
if (entry.contract?.inputSchema) def.contract.inputSchema = entry.contract.inputSchema
|
|
474
|
+
if (entry.contract?.outputSchema) def.contract.outputSchema = entry.contract.outputSchema
|
|
475
|
+
|
|
476
|
+
// Patch step schemas
|
|
477
|
+
if (entry.steps && def.steps) {
|
|
478
|
+
for (const stepPatch of entry.steps) {
|
|
479
|
+
const step = def.steps.find((s) => s.id === stepPatch.id)
|
|
480
|
+
if (!step) continue
|
|
481
|
+
if (stepPatch.inputSchema) step.inputSchema = stepPatch.inputSchema
|
|
482
|
+
if (stepPatch.outputSchema) step.outputSchema = stepPatch.outputSchema
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
} else if (entry.type === 'agent') {
|
|
486
|
+
const def = cache.definitions.agents.get(entry.resourceId)
|
|
487
|
+
if (!def) continue
|
|
488
|
+
if (entry.contract?.inputSchema) def.contract.inputSchema = entry.contract.inputSchema
|
|
489
|
+
if (entry.contract?.outputSchema) def.contract.outputSchema = entry.contract.outputSchema
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Register built-in platform resources (static, local execution)
|
|
496
|
+
*
|
|
497
|
+
* Unlike registerOrganization(), these resources:
|
|
498
|
+
* - Do NOT have remote config (execute in-process, not in worker threads)
|
|
499
|
+
* - Are NOT removed by unregisterOrganization() (persist across redeployments)
|
|
500
|
+
* - Use reserved resource IDs that external deployments cannot claim
|
|
501
|
+
*
|
|
502
|
+
* @param orgName - Organization name
|
|
503
|
+
* @param org - Resource definitions with real handlers (not stubs)
|
|
504
|
+
*/
|
|
505
|
+
registerStaticResources(orgName: string, org: DeploymentSpec): void {
|
|
506
|
+
// Filter out archived resources before any processing
|
|
507
|
+
org = filterArchived(org)
|
|
508
|
+
|
|
509
|
+
const incomingWorkflowIds = (org.workflows ?? []).map((w) => w.config.resourceId)
|
|
510
|
+
const incomingAgentIds = (org.agents ?? []).map((a) => a.config.resourceId)
|
|
511
|
+
const incomingIds = [...incomingWorkflowIds, ...incomingAgentIds]
|
|
512
|
+
|
|
513
|
+
// Check for duplicates within incoming resources
|
|
514
|
+
const seen = new Set<string>()
|
|
515
|
+
for (const id of incomingIds) {
|
|
516
|
+
if (seen.has(id)) {
|
|
517
|
+
throw new Error(`Duplicate resource ID '${id}' in static resources.`)
|
|
518
|
+
}
|
|
519
|
+
seen.add(id)
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Check for conflicts with existing resources in this org
|
|
523
|
+
const existingOrg = this.registry[orgName]
|
|
524
|
+
if (existingOrg) {
|
|
525
|
+
const existingWorkflowIds = new Set((existingOrg.workflows ?? []).map((w) => w.config.resourceId))
|
|
526
|
+
const existingAgentIds = new Set((existingOrg.agents ?? []).map((a) => a.config.resourceId))
|
|
527
|
+
|
|
528
|
+
for (const id of incomingIds) {
|
|
529
|
+
if (existingWorkflowIds.has(id) || existingAgentIds.has(id)) {
|
|
530
|
+
throw new Error(`Static resource '${id}' conflicts with existing resource in '${orgName}'.`)
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Merge into registry (no remote config = local execution path)
|
|
536
|
+
if (existingOrg) {
|
|
537
|
+
existingOrg.workflows = [...(existingOrg.workflows ?? []), ...(org.workflows ?? [])]
|
|
538
|
+
existingOrg.agents = [...(existingOrg.agents ?? []), ...(org.agents ?? [])]
|
|
539
|
+
} else {
|
|
540
|
+
this.registry[orgName] = org
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Rebuild serialized cache
|
|
544
|
+
this.serializedCache.set(orgName, serializeOrganization(this.registry[orgName]))
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Unregister runtime-registered resources for an organization
|
|
549
|
+
*
|
|
550
|
+
* Removes only resources that were registered at runtime (via registerOrganization).
|
|
551
|
+
* Static resources loaded at startup are preserved. If the org still has static
|
|
552
|
+
* resources after removal, the serialization cache is rebuilt. If no resources
|
|
553
|
+
* remain, the org is fully removed from the registry.
|
|
554
|
+
* No-op if the org has no remote resources.
|
|
555
|
+
*
|
|
556
|
+
* @param orgName - Organization name to unregister remote resources from
|
|
557
|
+
*/
|
|
558
|
+
unregisterOrganization(orgName: string): void {
|
|
559
|
+
// Find all remote resource keys for this org
|
|
560
|
+
const prefix = `${orgName}/`
|
|
561
|
+
const remoteIds = new Set<string>()
|
|
562
|
+
for (const key of this.remoteResources.keys()) {
|
|
563
|
+
if (key.startsWith(prefix)) {
|
|
564
|
+
remoteIds.add(key.slice(prefix.length))
|
|
565
|
+
this.remoteResources.delete(key)
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// No-op if org had no remote resources
|
|
570
|
+
if (remoteIds.size === 0) return
|
|
571
|
+
|
|
572
|
+
const orgResources = this.registry[orgName]
|
|
573
|
+
if (!orgResources) return
|
|
574
|
+
|
|
575
|
+
// Remove remote resources from the org's arrays
|
|
576
|
+
orgResources.workflows = (orgResources.workflows ?? []).filter((w) => !remoteIds.has(w.config.resourceId))
|
|
577
|
+
orgResources.agents = (orgResources.agents ?? []).filter((a) => !remoteIds.has(a.config.resourceId))
|
|
578
|
+
|
|
579
|
+
// Remove deployment-owned metadata (triggers, integrations, checkpoints, external resources)
|
|
580
|
+
// These are always fully owned by the deployment — replaced on each deploy
|
|
581
|
+
orgResources.triggers = undefined
|
|
582
|
+
orgResources.integrations = undefined
|
|
583
|
+
orgResources.humanCheckpoints = undefined
|
|
584
|
+
orgResources.externalResources = undefined
|
|
585
|
+
|
|
586
|
+
// Remove relationship entries for remote resource IDs
|
|
587
|
+
if (orgResources.relationships) {
|
|
588
|
+
for (const id of remoteIds) {
|
|
589
|
+
delete orgResources.relationships[id]
|
|
590
|
+
}
|
|
591
|
+
if (Object.keys(orgResources.relationships).length === 0) {
|
|
592
|
+
delete orgResources.relationships
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// If the org still has static resources, rebuild cache; otherwise fully remove
|
|
597
|
+
// (triggers, integrations, checkpoints, externalResources were cleared above)
|
|
598
|
+
const remaining = (orgResources.workflows?.length ?? 0) + (orgResources.agents?.length ?? 0)
|
|
599
|
+
|
|
600
|
+
if (remaining > 0) {
|
|
601
|
+
this.serializedCache.set(orgName, serializeOrganization(orgResources))
|
|
602
|
+
} else {
|
|
603
|
+
delete this.registry[orgName]
|
|
604
|
+
this.serializedCache.delete(orgName)
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Get remote configuration for a specific resource.
|
|
610
|
+
*
|
|
611
|
+
* Returns RemoteOrgConfig for externally-deployed resources, SystemConfig for
|
|
612
|
+
* first-class platform resources, or null for static in-process resources.
|
|
613
|
+
* Used by the execution coordinator to determine the execution path.
|
|
614
|
+
*
|
|
615
|
+
* @param orgName - Organization name
|
|
616
|
+
* @param resourceId - Resource ID
|
|
617
|
+
* @returns Remote or System config, or null
|
|
618
|
+
*/
|
|
619
|
+
getRemoteConfig(orgName: string, resourceId: string): RemoteOrgConfig | SystemConfig | null {
|
|
620
|
+
const key = `${orgName}/${resourceId}`
|
|
621
|
+
return this.remoteResources.get(key) ?? this.systemConfigs.get(key) ?? null
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Register a System config for a first-class platform resource.
|
|
626
|
+
*
|
|
627
|
+
* Called at startup alongside registerStaticResources() so that
|
|
628
|
+
* getRemoteConfig('system', resourceId) returns truthy and the execution
|
|
629
|
+
* coordinator routes the resource through the worker-thread path.
|
|
630
|
+
*
|
|
631
|
+
* @param orgName - Organization name (typically 'system')
|
|
632
|
+
* @param resourceId - Resource ID
|
|
633
|
+
* @param config - SystemConfig with kind:'static' and moduleId
|
|
634
|
+
*/
|
|
635
|
+
registerSystemConfig(orgName: string, resourceId: string, config: SystemConfig): void {
|
|
636
|
+
this.systemConfigs.set(`${orgName}/${resourceId}`, config)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Check if an organization has any remote (externally deployed) resources
|
|
641
|
+
*
|
|
642
|
+
* @param orgName - Organization name
|
|
643
|
+
* @returns true if the org has at least one runtime-registered resource
|
|
644
|
+
*/
|
|
645
|
+
isRemote(orgName: string): boolean {
|
|
646
|
+
const prefix = `${orgName}/`
|
|
647
|
+
for (const key of this.remoteResources.keys()) {
|
|
648
|
+
if (key.startsWith(prefix)) return true
|
|
649
|
+
}
|
|
650
|
+
return false
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Get the remote config for any resource in an organization.
|
|
655
|
+
* Used when the specific resource ID is unknown (e.g., to clean up a
|
|
656
|
+
* temp file before unregistering an org -- all resources share one config).
|
|
657
|
+
*
|
|
658
|
+
* @param orgName - Organization name
|
|
659
|
+
* @returns Remote config or null if org has no remote resources
|
|
660
|
+
*/
|
|
661
|
+
getAnyRemoteConfig(orgName: string): RemoteOrgConfig | null {
|
|
662
|
+
const prefix = `${orgName}/`
|
|
663
|
+
for (const [key, config] of this.remoteResources) {
|
|
664
|
+
if (key.startsWith(prefix)) return config
|
|
665
|
+
}
|
|
666
|
+
return null
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Get statistics about remotely-deployed resources
|
|
671
|
+
* Used by the health endpoint for platform-wide deployment visibility.
|
|
672
|
+
*/
|
|
673
|
+
getRemoteStats(): { activeOrgs: number; totalResources: number } {
|
|
674
|
+
const orgs = new Set<string>()
|
|
675
|
+
for (const key of this.remoteResources.keys()) {
|
|
676
|
+
const orgName = key.split('/')[0]
|
|
677
|
+
orgs.add(orgName)
|
|
678
|
+
}
|
|
679
|
+
return {
|
|
680
|
+
activeOrgs: orgs.size,
|
|
681
|
+
totalResources: this.remoteResources.size
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// ============================================================================
|
|
686
|
+
// Resource Manifest Accessors
|
|
687
|
+
// ============================================================================
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Get triggers for an organization
|
|
691
|
+
* @param organizationName - Organization name
|
|
692
|
+
* @returns Array of trigger definitions (empty if none defined)
|
|
693
|
+
*/
|
|
694
|
+
getTriggers(organizationName: string): TriggerDefinition[] {
|
|
695
|
+
return this.registry[organizationName]?.triggers ?? []
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Get integrations for an organization
|
|
700
|
+
* @param organizationName - Organization name
|
|
701
|
+
* @returns Array of integration definitions (empty if none defined)
|
|
702
|
+
*/
|
|
703
|
+
getIntegrations(organizationName: string): IntegrationDefinition[] {
|
|
704
|
+
return this.registry[organizationName]?.integrations ?? []
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/**
|
|
708
|
+
* Get resource relationships for an organization
|
|
709
|
+
* @param organizationName - Organization name
|
|
710
|
+
* @returns Resource relationships map (undefined if none defined)
|
|
711
|
+
*/
|
|
712
|
+
getRelationships(organizationName: string): ResourceRelationships | undefined {
|
|
713
|
+
return this.registry[organizationName]?.relationships
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Get a specific trigger by ID
|
|
718
|
+
* @param organizationName - Organization name
|
|
719
|
+
* @param triggerId - Trigger ID
|
|
720
|
+
* @returns Trigger definition or null if not found
|
|
721
|
+
*/
|
|
722
|
+
getTrigger(organizationName: string, triggerId: string): TriggerDefinition | null {
|
|
723
|
+
const triggers = this.getTriggers(organizationName)
|
|
724
|
+
return triggers.find((t) => t.resourceId === triggerId) ?? null
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Get a specific integration by ID
|
|
729
|
+
* @param organizationName - Organization name
|
|
730
|
+
* @param integrationId - Integration ID
|
|
731
|
+
* @returns Integration definition or null if not found
|
|
732
|
+
*/
|
|
733
|
+
getIntegration(organizationName: string, integrationId: string): IntegrationDefinition | null {
|
|
734
|
+
const integrations = this.getIntegrations(organizationName)
|
|
735
|
+
return integrations.find((i) => i.resourceId === integrationId) ?? null
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Get external resources for an organization
|
|
740
|
+
* @param organizationName - Organization name
|
|
741
|
+
* @returns Array of external resource definitions (empty if none defined)
|
|
742
|
+
*/
|
|
743
|
+
getExternalResources(organizationName: string): ExternalResourceDefinition[] {
|
|
744
|
+
return this.registry[organizationName]?.externalResources ?? []
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Get a specific external resource by ID
|
|
749
|
+
* @param organizationName - Organization name
|
|
750
|
+
* @param externalId - External resource ID
|
|
751
|
+
* @returns External resource definition or null if not found
|
|
752
|
+
*/
|
|
753
|
+
getExternalResource(organizationName: string, externalId: string): ExternalResourceDefinition | null {
|
|
754
|
+
const externalResources = this.getExternalResources(organizationName)
|
|
755
|
+
return externalResources.find((e) => e.resourceId === externalId) ?? null
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Get human checkpoints for an organization
|
|
760
|
+
* @param organizationName - Organization name
|
|
761
|
+
* @returns Array of human checkpoint definitions (empty if none defined)
|
|
762
|
+
*/
|
|
763
|
+
getHumanCheckpoints(organizationName: string): HumanCheckpointDefinition[] {
|
|
764
|
+
return this.registry[organizationName]?.humanCheckpoints ?? []
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
/**
|
|
768
|
+
* Get a specific human checkpoint by ID
|
|
769
|
+
* @param organizationName - Organization name
|
|
770
|
+
* @param humanCheckpointId - Human checkpoint ID
|
|
771
|
+
* @returns Human checkpoint definition or null if not found
|
|
772
|
+
*/
|
|
773
|
+
getHumanCheckpoint(organizationName: string, humanCheckpointId: string): HumanCheckpointDefinition | null {
|
|
774
|
+
const humanCheckpoints = this.getHumanCheckpoints(organizationName)
|
|
775
|
+
return humanCheckpoints.find((hc) => hc.resourceId === humanCheckpointId) ?? null
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// ============================================================================
|
|
779
|
+
// Serialized Data Access (Pre-computed at Startup)
|
|
780
|
+
// ============================================================================
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Get serialized resource definition (instant lookup)
|
|
784
|
+
* Use for API responses - returns pre-computed JSON-safe structure
|
|
785
|
+
*
|
|
786
|
+
* @param organizationName - Organization name
|
|
787
|
+
* @param resourceId - Resource ID
|
|
788
|
+
* @returns Serialized definition or null if not found
|
|
789
|
+
*/
|
|
790
|
+
getSerializedDefinition(
|
|
791
|
+
organizationName: string,
|
|
792
|
+
resourceId: string
|
|
793
|
+
): SerializedAgentDefinition | SerializedWorkflowDefinition | null {
|
|
794
|
+
const cache = this.serializedCache.get(organizationName)
|
|
795
|
+
if (!cache) return null
|
|
796
|
+
|
|
797
|
+
return cache.definitions.agents.get(resourceId) ?? cache.definitions.workflows.get(resourceId) ?? null
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* Get resource list for organization (instant lookup)
|
|
802
|
+
* Use for /resources endpoint - returns pre-computed ResourceDefinition array
|
|
803
|
+
*
|
|
804
|
+
* @param organizationName - Organization name
|
|
805
|
+
* @returns Resource list with workflows, agents, and total count
|
|
806
|
+
*/
|
|
807
|
+
getResourceList(organizationName: string): {
|
|
808
|
+
workflows: ResourceDefinition[]
|
|
809
|
+
agents: ResourceDefinition[]
|
|
810
|
+
total: number
|
|
811
|
+
} {
|
|
812
|
+
const cache = this.serializedCache.get(organizationName)
|
|
813
|
+
if (!cache) {
|
|
814
|
+
return { workflows: [], agents: [], total: 0 }
|
|
815
|
+
}
|
|
816
|
+
return cache.resources
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Get Command View data for organization (instant lookup)
|
|
821
|
+
* Use for /command-view endpoint - returns complete graph data
|
|
822
|
+
*
|
|
823
|
+
* @param organizationName - Organization name
|
|
824
|
+
* @returns Command View data with nodes and edges
|
|
825
|
+
*/
|
|
826
|
+
getCommandViewData(organizationName: string): CommandViewData {
|
|
827
|
+
const cache = this.serializedCache.get(organizationName)
|
|
828
|
+
if (!cache) {
|
|
829
|
+
return {
|
|
830
|
+
workflows: [],
|
|
831
|
+
agents: [],
|
|
832
|
+
triggers: [],
|
|
833
|
+
integrations: [],
|
|
834
|
+
externalResources: [],
|
|
835
|
+
humanCheckpoints: [],
|
|
836
|
+
edges: []
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return cache.commandView
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* List resources that have UI interfaces configured
|
|
844
|
+
* Used by Execution Runner Catalog UI
|
|
845
|
+
*
|
|
846
|
+
* @param organizationName - Organization name
|
|
847
|
+
* @param environment - Optional environment filter ('dev' or 'prod')
|
|
848
|
+
* @returns Array of resources with interfaces
|
|
849
|
+
*/
|
|
850
|
+
listExecutable(
|
|
851
|
+
organizationName: string,
|
|
852
|
+
environment?: 'dev' | 'prod'
|
|
853
|
+
): Array<{
|
|
854
|
+
resourceId: string
|
|
855
|
+
resourceName: string
|
|
856
|
+
resourceType: 'workflow' | 'agent'
|
|
857
|
+
description?: string
|
|
858
|
+
status: 'dev' | 'prod'
|
|
859
|
+
version: string
|
|
860
|
+
interface: SerializedExecutionInterface
|
|
861
|
+
}> {
|
|
862
|
+
const cache = this.serializedCache.get(organizationName)
|
|
863
|
+
if (!cache) {
|
|
864
|
+
return []
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
const results: Array<{
|
|
868
|
+
resourceId: string
|
|
869
|
+
resourceName: string
|
|
870
|
+
resourceType: 'workflow' | 'agent'
|
|
871
|
+
description?: string
|
|
872
|
+
status: 'dev' | 'prod'
|
|
873
|
+
version: string
|
|
874
|
+
interface: SerializedExecutionInterface
|
|
875
|
+
}> = []
|
|
876
|
+
|
|
877
|
+
// Check workflows with interfaces
|
|
878
|
+
cache.definitions.workflows.forEach((serializedWorkflow, resourceId) => {
|
|
879
|
+
// Skip if no interface defined
|
|
880
|
+
if (!serializedWorkflow.interface) return
|
|
881
|
+
|
|
882
|
+
// Apply environment filter if specified
|
|
883
|
+
if (environment && serializedWorkflow.config.status !== environment) return
|
|
884
|
+
|
|
885
|
+
results.push({
|
|
886
|
+
resourceId,
|
|
887
|
+
resourceName: serializedWorkflow.config.name,
|
|
888
|
+
resourceType: 'workflow',
|
|
889
|
+
description: serializedWorkflow.config.description,
|
|
890
|
+
status: serializedWorkflow.config.status as 'dev' | 'prod',
|
|
891
|
+
version: serializedWorkflow.config.version,
|
|
892
|
+
interface: serializedWorkflow.interface
|
|
893
|
+
})
|
|
894
|
+
})
|
|
895
|
+
|
|
896
|
+
// Check agents with interfaces
|
|
897
|
+
cache.definitions.agents.forEach((serializedAgent, resourceId) => {
|
|
898
|
+
// Skip if no interface defined
|
|
899
|
+
if (!serializedAgent.interface) return
|
|
900
|
+
|
|
901
|
+
// Apply environment filter if specified
|
|
902
|
+
if (environment && serializedAgent.config.status !== environment) return
|
|
903
|
+
|
|
904
|
+
results.push({
|
|
905
|
+
resourceId,
|
|
906
|
+
resourceName: serializedAgent.config.name,
|
|
907
|
+
resourceType: 'agent',
|
|
908
|
+
description: serializedAgent.config.description,
|
|
909
|
+
status: serializedAgent.config.status as 'dev' | 'prod',
|
|
910
|
+
version: serializedAgent.config.version,
|
|
911
|
+
interface: serializedAgent.interface
|
|
912
|
+
})
|
|
913
|
+
})
|
|
914
|
+
|
|
915
|
+
return results
|
|
916
|
+
}
|
|
917
|
+
}
|