@pikku/inspector 0.12.34 → 0.12.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/add/add-functions.js +2 -2
  3. package/dist/add/add-http-route.js +4 -1
  4. package/dist/add/add-rpc-invocations.js +7 -0
  5. package/dist/add/add-workflow.d.ts +1 -1
  6. package/dist/add/add-workflow.js +39 -11
  7. package/dist/error-codes.d.ts +2 -1
  8. package/dist/error-codes.js +2 -1
  9. package/dist/inspector.js +1 -0
  10. package/dist/types.d.ts +1 -0
  11. package/dist/utils/filter-inspector-state.js +43 -0
  12. package/dist/utils/load-addon-functions-meta.js +20 -14
  13. package/dist/utils/post-process.js +38 -13
  14. package/dist/utils/serialize-inspector-state.d.ts +1 -0
  15. package/dist/utils/serialize-inspector-state.js +2 -0
  16. package/dist/utils/workflow/dsl/patterns.d.ts +1 -1
  17. package/dist/utils/workflow/dsl/patterns.js +1 -1
  18. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +3 -3
  19. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +5 -5
  20. package/package.json +2 -2
  21. package/run-tests.sh +4 -4
  22. package/src/add/add-functions.ts +11 -5
  23. package/src/add/add-http-route.ts +4 -1
  24. package/src/add/add-rpc-invocations.test.ts +113 -0
  25. package/src/add/add-rpc-invocations.ts +8 -0
  26. package/src/add/add-workflow.ts +53 -14
  27. package/src/add/expect-eventually-scenario-only.test.ts +108 -0
  28. package/src/error-codes.ts +2 -1
  29. package/src/inspector.ts +1 -0
  30. package/src/types.ts +1 -0
  31. package/src/utils/filter-inspector-state.test.ts +231 -0
  32. package/src/utils/filter-inspector-state.ts +56 -0
  33. package/src/utils/load-addon-functions-meta.ts +26 -16
  34. package/src/utils/post-process.test.ts +254 -0
  35. package/src/utils/post-process.ts +41 -14
  36. package/src/utils/serialize-inspector-state.ts +11 -0
  37. package/src/utils/workflow/dsl/patterns.ts +1 -1
  38. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +3 -3
  39. package/src/utils/workflow/graph/workflow-graph.types.ts +5 -5
  40. package/tsconfig.tsbuildinfo +1 -1
@@ -468,6 +468,11 @@ export function filterInspectorState(
468
468
  filteredState.serviceAggregation.usedFunctions.add(
469
469
  routeMeta.pikkuFuncId
470
470
  )
471
+ if (routeMeta.refTarget) {
472
+ filteredState.serviceAggregation.usedFunctions.add(
473
+ routeMeta.refTarget
474
+ )
475
+ }
471
476
  // For workflow/agent routes, also add the base name
472
477
  // so the workflow/agent definition survives pruning
473
478
  const colonIdx = routeMeta.pikkuFuncId.indexOf(':')
@@ -1105,6 +1110,57 @@ export function filterInspectorState(
1105
1110
  filteredState.serviceAggregation.requiredServices.add('queueService')
1106
1111
  }
1107
1112
 
1113
+ if ((filteredState.rpc.wireAddonDeclarations?.size ?? 0) > 0) {
1114
+ const referencedIds = new Set<string>(
1115
+ filteredState.serviceAggregation.usedFunctions
1116
+ )
1117
+ const keptFiles = new Set<string>()
1118
+ for (const funcId of filteredState.serviceAggregation.usedFunctions) {
1119
+ const file = filteredState.functions.files.get(funcId)
1120
+ if (file?.path) keptFiles.add(file.path)
1121
+ }
1122
+ for (const [file, invoked] of state.rpc.invokedFunctionsByFile ??
1123
+ new Map<string, Set<string>>()) {
1124
+ if (!keptFiles.has(file)) continue
1125
+ for (const id of invoked) {
1126
+ referencedIds.add(id)
1127
+ if (id.includes(':')) {
1128
+ filteredState.serviceAggregation.usedFunctions.add(id)
1129
+ }
1130
+ }
1131
+ }
1132
+ for (const toolMeta of Object.values(
1133
+ filteredState.mcpEndpoints?.toolsMeta ?? {}
1134
+ ) as Array<{ pikkuFuncId?: string }>) {
1135
+ if (toolMeta.pikkuFuncId) referencedIds.add(toolMeta.pikkuFuncId)
1136
+ }
1137
+ for (const agentMeta of Object.values(
1138
+ filteredState.agents?.agentsMeta ?? {}
1139
+ ) as Array<{ tools?: string[] }>) {
1140
+ for (const tool of agentMeta.tools ?? []) referencedIds.add(tool)
1141
+ }
1142
+ const keptNamespaces = new Set<string>()
1143
+ for (const namespace of filteredState.rpc.wireAddonDeclarations.keys()) {
1144
+ const prefix = `${namespace}:`
1145
+ for (const id of referencedIds) {
1146
+ if (id.startsWith(prefix)) {
1147
+ keptNamespaces.add(namespace)
1148
+ break
1149
+ }
1150
+ }
1151
+ }
1152
+ filteredState.rpc.wireAddonDeclarations = new Map(
1153
+ [...filteredState.rpc.wireAddonDeclarations].filter(([namespace]) =>
1154
+ keptNamespaces.has(namespace)
1155
+ )
1156
+ )
1157
+ filteredState.rpc.usedAddons = new Set(
1158
+ [...filteredState.rpc.usedAddons].filter((namespace) =>
1159
+ keptNamespaces.has(namespace)
1160
+ )
1161
+ )
1162
+ }
1163
+
1108
1164
  // Recalculate requiredServices based on filtered functions/middleware/permissions
1109
1165
  // Need to cast to InspectorState temporarily for aggregateRequiredServices
1110
1166
  const stateForAggregation = filteredState as InspectorState
@@ -200,6 +200,7 @@ export async function loadAddonFunctionsMeta(
200
200
  // No variables meta — that's fine
201
201
  }
202
202
  // Load addon serverlessIncompatible service names from pikku-addon-meta.gen.json
203
+ let loadedParentServices = false
203
204
  try {
204
205
  const addonMetaPath = require.resolve(
205
206
  `${decl.package}/.pikku/console/pikku-addon-meta.gen.json`
@@ -218,29 +219,38 @@ export async function loadAddonFunctionsMeta(
218
219
  `Addon '${namespace}' marks [${addonMeta.serverlessIncompatible.join(', ')}] as serverless-incompatible`
219
220
  )
220
221
  }
221
- } catch {
222
- // No addon meta or no serverlessIncompatible declared — that's fine
223
- }
224
-
225
- // Load addon required parent services from pikku-services.gen
226
- try {
227
- const servicesGenPath = require.resolve(
228
- `${decl.package}/.pikku/pikku-services.gen.js`
229
- )
230
- const servicesModule = await import(servicesGenPath)
231
222
  if (
232
- servicesModule.requiredParentServices &&
233
- Array.isArray(servicesModule.requiredParentServices)
223
+ Array.isArray(addonMeta.requiredParentServices) &&
224
+ addonMeta.requiredParentServices.length > 0
234
225
  ) {
235
- for (const service of servicesModule.requiredParentServices) {
226
+ for (const service of addonMeta.requiredParentServices) {
236
227
  state.addonRequiredParentServices.push(service)
237
228
  }
229
+ loadedParentServices = true
238
230
  logger.debug(
239
- `Loaded ${servicesModule.requiredParentServices.length} required parent services for '${namespace}' from ${decl.package}`
231
+ `Loaded ${addonMeta.requiredParentServices.length} required parent services for '${namespace}' from addon meta`
240
232
  )
241
233
  }
242
- } catch {
243
- // No services gen — addon may not have requiredParentServices
234
+ } catch {}
235
+
236
+ if (!loadedParentServices) {
237
+ try {
238
+ const servicesGenPath = require.resolve(
239
+ `${decl.package}/.pikku/pikku-services.gen.js`
240
+ )
241
+ const servicesModule = await import(servicesGenPath)
242
+ if (
243
+ servicesModule.requiredParentServices &&
244
+ Array.isArray(servicesModule.requiredParentServices)
245
+ ) {
246
+ for (const service of servicesModule.requiredParentServices) {
247
+ state.addonRequiredParentServices.push(service)
248
+ }
249
+ logger.debug(
250
+ `Loaded ${servicesModule.requiredParentServices.length} required parent services for '${namespace}' from ${decl.package}`
251
+ )
252
+ }
253
+ } catch {}
244
254
  }
245
255
 
246
256
  try {
@@ -0,0 +1,254 @@
1
+ import { strict as assert } from 'assert'
2
+ import { describe, test } from 'node:test'
3
+ import { aggregateRequiredServices } from './post-process.js'
4
+ import type { InspectorState } from '../types.js'
5
+
6
+ function makeState(
7
+ overrides: {
8
+ usedFunctions?: string[]
9
+ functionsMeta?: Record<string, any>
10
+ addonFunctions?: Record<string, Record<string, any>>
11
+ addonRequiredParentServices?: string[]
12
+ } = {}
13
+ ): Omit<InspectorState, 'typesLookup'> {
14
+ return {
15
+ serviceAggregation: {
16
+ requiredServices: new Set<string>(),
17
+ usedFunctions: new Set(overrides.usedFunctions ?? []),
18
+ usedMiddleware: new Set<string>(),
19
+ usedPermissions: new Set<string>(),
20
+ allSingletonServices: [],
21
+ allWireServices: [],
22
+ },
23
+ functions: { meta: overrides.functionsMeta ?? {} },
24
+ middleware: { definitions: {}, tagMiddleware: new Map() },
25
+ permissions: { definitions: {}, tagPermissions: new Map() },
26
+ http: {
27
+ meta: {
28
+ get: {},
29
+ post: {},
30
+ put: {},
31
+ patch: {},
32
+ delete: {},
33
+ head: {},
34
+ options: {},
35
+ },
36
+ routeMiddleware: new Map(),
37
+ routePermissions: new Map(),
38
+ },
39
+ channels: { meta: {} },
40
+ queueWorkers: { meta: {} },
41
+ scheduledTasks: { meta: {} },
42
+ mcpEndpoints: { toolsMeta: {}, promptsMeta: {}, resourcesMeta: {} },
43
+ agents: { agentsMeta: {} },
44
+ workflows: { meta: {}, graphMeta: {} },
45
+ wireServicesMeta: new Map(),
46
+ rpc: { internalMeta: {}, exposedMeta: {} },
47
+ addonFunctions: overrides.addonFunctions ?? {},
48
+ addonRequiredParentServices: overrides.addonRequiredParentServices ?? [],
49
+ } as unknown as Omit<InspectorState, 'typesLookup'>
50
+ }
51
+
52
+ const CONSOLE_PARENT_SERVICES = [
53
+ 'metaService',
54
+ 'aiAgentRunner',
55
+ 'deploymentService',
56
+ 'credentialService',
57
+ ]
58
+
59
+ describe('aggregateRequiredServices — per-function addon services', () => {
60
+ test('a used addon function adds only its own parent services', () => {
61
+ const state = makeState({
62
+ usedFunctions: ['console:getSchema'],
63
+ addonFunctions: {
64
+ console: {
65
+ getSchema: {
66
+ services: { optimized: true, services: ['metaService'] },
67
+ },
68
+ runAgent: {
69
+ services: {
70
+ optimized: true,
71
+ services: ['aiAgentRunner', 'deploymentService'],
72
+ },
73
+ },
74
+ },
75
+ },
76
+ addonRequiredParentServices: CONSOLE_PARENT_SERVICES,
77
+ })
78
+ aggregateRequiredServices(state)
79
+ const required = state.serviceAggregation.requiredServices
80
+ assert.ok(required.has('metaService'))
81
+ assert.ok(!required.has('aiAgentRunner'))
82
+ assert.ok(!required.has('deploymentService'))
83
+ assert.ok(!required.has('credentialService'))
84
+ })
85
+
86
+ test('an addon-created service falls back to the full parent blanket', () => {
87
+ const state = makeState({
88
+ usedFunctions: ['console:editCode'],
89
+ addonFunctions: {
90
+ console: {
91
+ editCode: {
92
+ services: {
93
+ optimized: true,
94
+ services: ['codeEditService', 'metaService'],
95
+ },
96
+ },
97
+ },
98
+ },
99
+ addonRequiredParentServices: CONSOLE_PARENT_SERVICES,
100
+ })
101
+ aggregateRequiredServices(state)
102
+ const required = state.serviceAggregation.requiredServices
103
+ for (const service of CONSOLE_PARENT_SERVICES) {
104
+ assert.ok(required.has(service), `expected blanket to add ${service}`)
105
+ }
106
+ })
107
+
108
+ test('missing services meta (old addon build) falls back to the blanket', () => {
109
+ const state = makeState({
110
+ usedFunctions: ['console:getSchema'],
111
+ addonFunctions: { console: { getSchema: {} } },
112
+ addonRequiredParentServices: CONSOLE_PARENT_SERVICES,
113
+ })
114
+ aggregateRequiredServices(state)
115
+ const required = state.serviceAggregation.requiredServices
116
+ for (const service of CONSOLE_PARENT_SERVICES) {
117
+ assert.ok(required.has(service), `expected blanket to add ${service}`)
118
+ }
119
+ })
120
+
121
+ test('a bare project function colliding with an addon function name adds nothing', () => {
122
+ const state = makeState({
123
+ usedFunctions: ['getAgentThreads'],
124
+ functionsMeta: {
125
+ getAgentThreads: {
126
+ services: { optimized: true, services: ['kysely'] },
127
+ },
128
+ },
129
+ addonFunctions: {
130
+ console: {
131
+ getAgentThreads: {
132
+ services: { optimized: true, services: ['metaService'] },
133
+ },
134
+ },
135
+ },
136
+ addonRequiredParentServices: CONSOLE_PARENT_SERVICES,
137
+ })
138
+ aggregateRequiredServices(state)
139
+ const required = state.serviceAggregation.requiredServices
140
+ assert.ok(required.has('kysely'))
141
+ assert.ok(!required.has('metaService'))
142
+ assert.ok(!required.has('aiAgentRunner'))
143
+ })
144
+
145
+ test('no used addon functions adds no parent services', () => {
146
+ const state = makeState({
147
+ usedFunctions: ['listTasks'],
148
+ functionsMeta: {
149
+ listTasks: { services: { optimized: true, services: ['kysely'] } },
150
+ },
151
+ addonFunctions: {
152
+ console: {
153
+ getSchema: {
154
+ services: { optimized: true, services: ['metaService'] },
155
+ },
156
+ },
157
+ },
158
+ addonRequiredParentServices: CONSOLE_PARENT_SERVICES,
159
+ })
160
+ aggregateRequiredServices(state)
161
+ const required = state.serviceAggregation.requiredServices
162
+ assert.ok(!required.has('metaService'))
163
+ assert.ok(!required.has('aiAgentRunner'))
164
+ })
165
+
166
+ test('internal services in addon function meta never force the blanket', () => {
167
+ const state = makeState({
168
+ usedFunctions: ['console:getSchema'],
169
+ addonFunctions: {
170
+ console: {
171
+ getSchema: {
172
+ services: {
173
+ optimized: true,
174
+ services: ['metaService', 'rpc', 'channel'],
175
+ },
176
+ },
177
+ },
178
+ },
179
+ addonRequiredParentServices: CONSOLE_PARENT_SERVICES,
180
+ })
181
+ aggregateRequiredServices(state)
182
+ const required = state.serviceAggregation.requiredServices
183
+ assert.ok(required.has('metaService'))
184
+ assert.ok(!required.has('aiAgentRunner'))
185
+ assert.ok(!required.has('rpc'))
186
+ })
187
+
188
+ test('default framework services in addon function meta never force the blanket', () => {
189
+ const state = makeState({
190
+ usedFunctions: ['ext:goodbye'],
191
+ addonFunctions: {
192
+ ext: {
193
+ goodbye: {
194
+ services: { optimized: true, services: ['logger', 'config'] },
195
+ },
196
+ },
197
+ },
198
+ addonRequiredParentServices: ['greetingStore', 'auditSink'],
199
+ })
200
+ aggregateRequiredServices(state)
201
+ const required = state.serviceAggregation.requiredServices
202
+ assert.ok(!required.has('greetingStore'))
203
+ assert.ok(!required.has('auditSink'))
204
+ })
205
+
206
+ test('a ref()-wired route (inline id + namespaced target) aggregates the target services', () => {
207
+ const state = makeState({
208
+ usedFunctions: [
209
+ 'http:get:/function-tests/stream',
210
+ 'console:streamFunctionTests',
211
+ ],
212
+ functionsMeta: {
213
+ 'http:get:/function-tests/stream': {
214
+ services: { optimized: false, services: [] },
215
+ },
216
+ },
217
+ addonFunctions: {
218
+ console: {
219
+ streamFunctionTests: {
220
+ services: { optimized: true, services: ['metaService'] },
221
+ },
222
+ },
223
+ },
224
+ addonRequiredParentServices: CONSOLE_PARENT_SERVICES,
225
+ })
226
+ aggregateRequiredServices(state)
227
+ const required = state.serviceAggregation.requiredServices
228
+ assert.ok(required.has('metaService'))
229
+ assert.ok(!required.has('aiAgentRunner'))
230
+ })
231
+
232
+ test('multiple used addon functions union their parent services', () => {
233
+ const state = makeState({
234
+ usedFunctions: ['console:getSchema', 'console:runAgent'],
235
+ addonFunctions: {
236
+ console: {
237
+ getSchema: {
238
+ services: { optimized: true, services: ['metaService'] },
239
+ },
240
+ runAgent: {
241
+ services: { optimized: true, services: ['aiAgentRunner'] },
242
+ },
243
+ },
244
+ },
245
+ addonRequiredParentServices: CONSOLE_PARENT_SERVICES,
246
+ })
247
+ aggregateRequiredServices(state)
248
+ const required = state.serviceAggregation.requiredServices
249
+ assert.ok(required.has('metaService'))
250
+ assert.ok(required.has('aiAgentRunner'))
251
+ assert.ok(!required.has('deploymentService'))
252
+ assert.ok(!required.has('credentialService'))
253
+ })
254
+ })
@@ -325,20 +325,47 @@ export function aggregateRequiredServices(
325
325
  }
326
326
 
327
327
  // 7. Services that consumed addons need from the parent project.
328
- // These are required ONLY by units that actually deploy an addon function;
329
- // a unit that merely calls the addon over RPC (or never touches it) must not
330
- // carry them, or every per-unit bundle would over-include the addon's
331
- // parent-service dependencies (e.g. aiAgentRunner, deploymentService) and
332
- // defeat per-unit tree-shaking.
333
- const addonFuncIds = new Set<string>()
334
- for (const fns of Object.values(state.addonFunctions ?? {})) {
335
- for (const id of Object.keys(fns)) addonFuncIds.add(id)
336
- }
337
- const unitDeploysAddonFn = [...usedFunctions].some((fn) =>
338
- addonFuncIds.has(fn)
339
- )
340
- if (unitDeploysAddonFn) {
341
- for (const service of state.addonRequiredParentServices ?? []) {
328
+ const addonFnServices = new Map<string, string[] | undefined>()
329
+ for (const [namespace, fns] of Object.entries(state.addonFunctions ?? {})) {
330
+ for (const [id, meta] of Object.entries(fns)) {
331
+ addonFnServices.set(
332
+ `${namespace}:${id}`,
333
+ (meta as { services?: FunctionServicesMeta })?.services?.services
334
+ )
335
+ }
336
+ }
337
+ const parentDeclared = state.addonRequiredParentServices ?? []
338
+ const parentDeclaredSet = new Set(parentDeclared)
339
+ const defaultServices = new Set([
340
+ 'config',
341
+ 'logger',
342
+ 'variables',
343
+ 'schema',
344
+ 'secrets',
345
+ ])
346
+ let usesAddonFn = false
347
+ let addonFactoryNeeded = false
348
+ for (const funcId of usedFunctions) {
349
+ if (!addonFnServices.has(funcId)) continue
350
+ usesAddonFn = true
351
+ const services = addonFnServices.get(funcId)
352
+ if (!services) {
353
+ addonFactoryNeeded = true
354
+ continue
355
+ }
356
+ for (const service of services) {
357
+ if (parentDeclaredSet.has(service)) {
358
+ requiredServices.add(service)
359
+ } else if (
360
+ !internalServices.has(service) &&
361
+ !defaultServices.has(service)
362
+ ) {
363
+ addonFactoryNeeded = true
364
+ }
365
+ }
366
+ }
367
+ if (usesAddonFn && addonFactoryNeeded) {
368
+ for (const service of parentDeclared) {
342
369
  requiredServices.add(service)
343
370
  }
344
371
  }
@@ -155,6 +155,7 @@ export interface SerializableInspectorState {
155
155
  exposedMeta: InspectorState['rpc']['exposedMeta']
156
156
  exposedFiles: Array<[string, { path: string; exportedName: string }]>
157
157
  invokedFunctions: string[]
158
+ invokedFunctionsByFile?: Array<[string, string[]]>
158
159
  usedAddons: string[]
159
160
  wireAddonDeclarations: Array<
160
161
  [
@@ -379,6 +380,11 @@ export function serializeInspectorState(
379
380
  exposedMeta: state.rpc.exposedMeta,
380
381
  exposedFiles: Array.from(state.rpc.exposedFiles.entries()),
381
382
  invokedFunctions: Array.from(state.rpc.invokedFunctions),
383
+ invokedFunctionsByFile: Array.from(
384
+ (
385
+ state.rpc.invokedFunctionsByFile ?? new Map<string, Set<string>>()
386
+ ).entries()
387
+ ).map(([file, fns]): [string, string[]] => [file, Array.from(fns)]),
382
388
  usedAddons: Array.from(state.rpc.usedAddons),
383
389
  wireAddonDeclarations: Array.from(
384
390
  state.rpc.wireAddonDeclarations.entries()
@@ -565,6 +571,11 @@ export function deserializeInspectorState(
565
571
  exposedMeta: data.rpc.exposedMeta,
566
572
  exposedFiles: new Map(data.rpc.exposedFiles),
567
573
  invokedFunctions: new Set(data.rpc.invokedFunctions),
574
+ invokedFunctionsByFile: new Map(
575
+ (data.rpc.invokedFunctionsByFile || []).map(
576
+ ([file, fns]): [string, Set<string>] => [file, new Set(fns)]
577
+ )
578
+ ),
568
579
  usedAddons: new Set(data.rpc.usedAddons || []),
569
580
  wireAddonDeclarations: new Map(data.rpc.wireAddonDeclarations || []),
570
581
  wireAddonFiles: new Set(data.rpc.wireAddonFiles || []),
@@ -6,7 +6,7 @@ import * as ts from 'typescript'
6
6
 
7
7
  /**
8
8
  * Check if a call expression is workflow.do() or workflow.expectEventually()
9
- * (both are RPC steps; expectEventually is the polling variant used by user flows)
9
+ * (both are RPC steps; expectEventually is the polling variant used by scenarios)
10
10
  */
11
11
  export function isWorkflowDoCall(
12
12
  node: ts.CallExpression,
@@ -396,11 +396,11 @@ export function convertDslToGraph(
396
396
  const entryNodeIds = nodes.length > 0 ? [nodes[0].nodeId] : []
397
397
 
398
398
  // Determine source type:
399
- // - userFlow: complex workflow whose steps run as actors (user flow)
399
+ // - scenario: complex workflow whose steps run as actors (scenario)
400
400
  // - dsl === true: pure DSL workflow, can be serialized
401
401
  // - dsl === false: complex workflow with inline steps, not serializable
402
- const source = meta.userFlow
403
- ? 'user-flow'
402
+ const source = meta.scenario
403
+ ? 'scenario'
404
404
  : meta.dsl === false
405
405
  ? 'complex'
406
406
  : 'dsl'
@@ -142,9 +142,9 @@ export interface FunctionNode extends BaseNode {
142
142
  outputVar?: string
143
143
  /** Hash of nodeId + RPC input/output schemas for version detection */
144
144
  stepHash?: string
145
- /** User-flow actor name this step runs as (workflow.do {actor: actors.x}) */
145
+ /** Scenario actor name this step runs as (workflow.do {actor: actors.x}) */
146
146
  actor?: string
147
- /** True for workflow.expectEventually polling steps (user flows) */
147
+ /** True for workflow.expectEventually polling steps (scenarios) */
148
148
  expectEventually?: boolean
149
149
  }
150
150
 
@@ -181,9 +181,9 @@ export const isFlowNode = (node: SerializedGraphNode): node is FlowNode =>
181
181
  * - 'dsl': Pure DSL workflow (pikkuWorkflowFunc) - can be round-tripped to code
182
182
  * - 'complex': Complex workflow (pikkuWorkflowComplexFunc) - contains inline steps, not serializable
183
183
  * - 'graph': Graph-based workflow (pikkuWorkflowGraph)
184
- * - 'user-flow': User flow (pikkuUserFlow) - complex workflow whose steps run as actors
184
+ * - 'scenario': Scenario (pikkuScenario) - complex workflow whose steps run as actors
185
185
  */
186
- export type WorkflowSourceType = 'dsl' | 'complex' | 'graph' | 'user-flow'
186
+ export type WorkflowSourceType = 'dsl' | 'complex' | 'graph' | 'scenario'
187
187
 
188
188
  /**
189
189
  * Serialized workflow graph - the canonical JSON format
@@ -201,7 +201,7 @@ export interface SerializedWorkflowGraph {
201
201
  description?: string
202
202
  /** Tags for organization */
203
203
  tags?: string[]
204
- /** Actor names a user flow's steps run as */
204
+ /** Actor names a scenario's steps run as */
205
205
  actors?: string[]
206
206
  /** If true, workflow always executes inline without queues */
207
207
  inline?: boolean