@pikku/core 0.12.51 → 0.12.52

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 (41) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/dist/services/http-scenario-actors.d.ts +67 -0
  3. package/dist/services/http-scenario-actors.js +193 -0
  4. package/dist/services/http-user-flow-actors.d.ts +20 -0
  5. package/dist/services/http-user-flow-actors.js +100 -0
  6. package/dist/services/index.d.ts +2 -2
  7. package/dist/services/index.js +1 -1
  8. package/dist/services/meta-service.d.ts +4 -4
  9. package/dist/services/meta-service.js +8 -8
  10. package/dist/services/scenario-actors-service.d.ts +39 -0
  11. package/dist/services/scenario-actors-service.js +1 -0
  12. package/dist/services/user-flow-actors-service.d.ts +11 -1
  13. package/dist/types/core.types.d.ts +4 -4
  14. package/dist/wirings/actor-flow/actor-flow.types.d.ts +68 -0
  15. package/dist/wirings/actor-flow/actor-flow.types.js +1 -0
  16. package/dist/wirings/actor-flow/index.d.ts +11 -0
  17. package/dist/wirings/actor-flow/index.js +1 -0
  18. package/dist/wirings/actor-flow/run-conversation.d.ts +36 -0
  19. package/dist/wirings/actor-flow/run-conversation.js +181 -0
  20. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +6 -6
  21. package/dist/wirings/workflow/pikku-workflow-service.d.ts +10 -2
  22. package/dist/wirings/workflow/pikku-workflow-service.js +42 -2
  23. package/dist/wirings/workflow/workflow.types.d.ts +6 -6
  24. package/package.json +2 -1
  25. package/src/services/http-scenario-actors-converse.test.ts +222 -0
  26. package/src/services/{http-user-flow-actors.test.ts → http-scenario-actors.test.ts} +17 -7
  27. package/src/services/http-scenario-actors.ts +269 -0
  28. package/src/services/index.ts +8 -8
  29. package/src/services/meta-service.ts +9 -9
  30. package/src/services/{user-flow-actors-service.ts → scenario-actors-service.ts} +18 -4
  31. package/src/types/core.types.ts +4 -4
  32. package/src/wirings/actor-flow/actor-flow.types.ts +73 -0
  33. package/src/wirings/actor-flow/index.ts +22 -0
  34. package/src/wirings/actor-flow/run-conversation.test.ts +176 -0
  35. package/src/wirings/actor-flow/run-conversation.ts +285 -0
  36. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +6 -6
  37. package/src/wirings/workflow/pikku-workflow-service.ts +50 -5
  38. package/src/wirings/workflow/{user-flow-step.test.ts → scenario-step.test.ts} +19 -14
  39. package/src/wirings/workflow/workflow.types.ts +6 -6
  40. package/tsconfig.tsbuildinfo +1 -1
  41. package/src/services/http-user-flow-actors.ts +0 -132
@@ -11,6 +11,7 @@ import {
11
11
  pikkuState,
12
12
  } from '../../pikku-state.js'
13
13
  import { getDurationInMilliseconds } from '../../time-utils.js'
14
+ import { createHttpScenarioActors } from '../../services/http-scenario-actors.js'
14
15
 
15
16
  const resolveWorkflowMeta = (
16
17
  name: string
@@ -67,7 +68,7 @@ import {
67
68
  runFromMeta,
68
69
  } from './graph/graph-runner.js'
69
70
  import type { WorkflowService } from '../../services/workflow-service.js'
70
- import type { UserFlowActors } from '../../services/user-flow-actors-service.js'
71
+ import type { ScenarioActors } from '../../services/scenario-actors-service.js'
71
72
  import {
72
73
  PikkuError,
73
74
  addError,
@@ -222,7 +223,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
222
223
  private inlineRuns = new Set<string>()
223
224
  // User-flow actors per run: live authenticated clients (cookie jars) are
224
225
  // process-local by nature, so they ride this map, never the persisted wire.
225
- private runActors = new Map<string, UserFlowActors>()
226
+ private runActors = new Map<string, ScenarioActors>()
226
227
 
227
228
  protected get logger() {
228
229
  return getSingletonServices()?.logger
@@ -1065,6 +1066,45 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1065
1066
  return true
1066
1067
  }
1067
1068
 
1069
+ /** Build HTTP scenario actors for a run started without them; undefined when SCENARIO_ACTOR_SECRET or the API URL is missing */
1070
+ private async resolveScenarioActors(): Promise<ScenarioActors | undefined> {
1071
+ const services = getSingletonServices()
1072
+ const variables = services?.variables
1073
+ const metaService = services?.metaService
1074
+ if (!variables || !metaService) {
1075
+ return undefined
1076
+ }
1077
+ const secret = await variables.get('SCENARIO_ACTOR_SECRET')
1078
+ const apiUrl = await variables.get('API_URL')
1079
+ if (!secret || !apiUrl) {
1080
+ services?.logger?.warn(
1081
+ 'A scenario was started without actors but SCENARIO_ACTOR_SECRET / API_URL is not configured — running without actors.'
1082
+ )
1083
+ return undefined
1084
+ }
1085
+ const actorsConfig = await metaService.getScenarioActorsMeta()
1086
+ if (!actorsConfig || Object.keys(actorsConfig).length === 0) {
1087
+ return undefined
1088
+ }
1089
+ const signInPath =
1090
+ (await variables.get('SCENARIO_SIGN_IN_PATH')) ??
1091
+ '/api/auth/sign-in/actor'
1092
+ const rpcPath = (await variables.get('SCENARIO_RPC_PATH')) ?? '/rpc'
1093
+ return createHttpScenarioActors({
1094
+ apiUrl,
1095
+ secret,
1096
+ actors: actorsConfig,
1097
+ signInPath,
1098
+ rpcPath,
1099
+ })
1100
+ }
1101
+
1102
+ /**
1103
+ * Start a new workflow run
1104
+ * Automatically detects workflow type (DSL or graph) from meta and executes accordingly
1105
+ * @param options.inline - If true, execute workflow directly without queue service
1106
+ * @param options.startNode - Starting node ID for graph workflows (from wire config)
1107
+ */
1068
1108
  /**
1069
1109
  * Start a new workflow run
1070
1110
  * Automatically detects workflow type (DSL or graph) from meta and executes accordingly
@@ -1076,7 +1116,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1076
1116
  input: I,
1077
1117
  wire: WorkflowRunWire,
1078
1118
  rpcService: any,
1079
- options?: { inline?: boolean; startNode?: string; actors?: UserFlowActors }
1119
+ options?: { inline?: boolean; startNode?: string; actors?: ScenarioActors }
1080
1120
  ): Promise<{ runId: string }> {
1081
1121
  // Resolve workflow from static meta (root or addon namespace), then dynamic DB
1082
1122
  const resolved = resolveWorkflowMeta(name)
@@ -1140,8 +1180,13 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1140
1180
  }
1141
1181
  )
1142
1182
 
1143
- if (options?.actors) {
1144
- this.runActors.set(runId, options.actors)
1183
+ const actors =
1184
+ options?.actors ??
1185
+ (workflowMeta.source === 'scenario'
1186
+ ? await this.resolveScenarioActors()
1187
+ : undefined)
1188
+ if (actors) {
1189
+ this.runActors.set(runId, actors)
1145
1190
  }
1146
1191
 
1147
1192
  if (shouldInline) {
@@ -3,14 +3,14 @@ import assert from 'node:assert/strict'
3
3
 
4
4
  import { InMemoryWorkflowService } from '../../services/in-memory-workflow-service.js'
5
5
  import { pikkuState, resetPikkuState } from '../../pikku-state.js'
6
- import type { UserFlowActor } from '../../services/user-flow-actors-service.js'
6
+ import type { ScenarioActor } from '../../services/scenario-actors-service.js'
7
7
 
8
8
  const noopLogger = { error() {}, info() {}, warn() {}, debug() {} }
9
9
 
10
10
  const fakeActor = (
11
11
  name: string,
12
12
  handler: (rpcName: string, data: unknown) => Promise<unknown>
13
- ): UserFlowActor & { calls: Array<{ rpcName: string; data: unknown }> } => {
13
+ ): ScenarioActor & { calls: Array<{ rpcName: string; data: unknown }> } => {
14
14
  const calls: Array<{ rpcName: string; data: unknown }> = []
15
15
  return {
16
16
  name,
@@ -31,14 +31,14 @@ const setup = async (
31
31
  logger: noopLogger,
32
32
  ...services,
33
33
  } as any)
34
- const runId = await ws.createRun('userFlowTest', {}, true, 'hash', {
34
+ const runId = await ws.createRun('scenarioTest', {}, true, 'hash', {
35
35
  type: 'test',
36
36
  } as any)
37
37
  ws.registerInlineRun(runId)
38
38
  return runId
39
39
  }
40
40
 
41
- describe('user-flow actor steps (workflow.do with `actor`)', () => {
41
+ describe('scenario actor steps (workflow.do with `actor`)', () => {
42
42
  beforeEach(() => resetPikkuState())
43
43
 
44
44
  test('routes through the actor over the real transport, never internal rpc', async () => {
@@ -54,7 +54,7 @@ describe('user-flow actor steps (workflow.do with `actor`)', () => {
54
54
  },
55
55
  }
56
56
 
57
- const wire = ws.createWorkflowWire('userFlowTest', runId, rpc)
57
+ const wire = ws.createWorkflowWire('scenarioTest', runId, rpc)
58
58
  const result = await wire.do(
59
59
  'customer creates todo',
60
60
  'createTodo',
@@ -74,7 +74,7 @@ describe('user-flow actor steps (workflow.do with `actor`)', () => {
74
74
  let invocations = 0
75
75
  const yasser = fakeActor('yasser', async () => ({ n: ++invocations }))
76
76
  const runId = await setup(ws)
77
- const wire = ws.createWorkflowWire('userFlowTest', runId, {})
77
+ const wire = ws.createWorkflowWire('scenarioTest', runId, {})
78
78
 
79
79
  const first = await wire.do('step', 'someRpc', {}, { actor: yasser })
80
80
  assert.deepEqual(first, { n: 1 })
@@ -82,8 +82,13 @@ describe('user-flow actor steps (workflow.do with `actor`)', () => {
82
82
  // Simulate replay: reset per-run ordinals so the same logical step name
83
83
  // resolves to the same durable step key.
84
84
  ;(ws as any).resetStepOrdinals(runId)
85
- const replayWire = ws.createWorkflowWire('userFlowTest', runId, {})
86
- const replayed = await replayWire.do('step', 'someRpc', {}, { actor: yasser })
85
+ const replayWire = ws.createWorkflowWire('scenarioTest', runId, {})
86
+ const replayed = await replayWire.do(
87
+ 'step',
88
+ 'someRpc',
89
+ {},
90
+ { actor: yasser }
91
+ )
87
92
 
88
93
  assert.deepEqual(replayed, { n: 1 }, 'replay must return the cached result')
89
94
  assert.equal(invocations, 1, 'the actor must not be re-invoked on replay')
@@ -106,7 +111,7 @@ describe('user-flow actor steps (workflow.do with `actor`)', () => {
106
111
  workflowQueued: true,
107
112
  } as any
108
113
 
109
- const wire = ws.createWorkflowWire('userFlowTest', runId, {})
114
+ const wire = ws.createWorkflowWire('scenarioTest', runId, {})
110
115
  await wire.do('step', 'queuedRpc', {}, { actor: customer })
111
116
 
112
117
  assert.equal(queued, 0, 'actor steps are outbound HTTP — never queued')
@@ -116,10 +121,10 @@ describe('user-flow actor steps (workflow.do with `actor`)', () => {
116
121
  test('actor step failure surfaces the actor error and fails after retries', async () => {
117
122
  const ws = new InMemoryWorkflowService()
118
123
  const broken = fakeActor('broken', async () => {
119
- throw new Error("[user-flow] 'createTodo' as 'broken' returned 403: nope")
124
+ throw new Error("[scenario] 'createTodo' as 'broken' returned 403: nope")
120
125
  })
121
126
  const runId = await setup(ws)
122
- const wire = ws.createWorkflowWire('userFlowTest', runId, {})
127
+ const wire = ws.createWorkflowWire('scenarioTest', runId, {})
123
128
 
124
129
  await assert.rejects(
125
130
  wire.do('step', 'createTodo', {}, { actor: broken, retries: 2 }),
@@ -139,7 +144,7 @@ describe('workflow.expectEventually', () => {
139
144
  notifications: ++polls >= 3 ? ['ping'] : [],
140
145
  }))
141
146
  const runId = await setup(ws)
142
- const wire = ws.createWorkflowWire('userFlowTest', runId, {})
147
+ const wire = ws.createWorkflowWire('scenarioTest', runId, {})
143
148
 
144
149
  const result = await wire.expectEventually(
145
150
  'sarah sees the notification',
@@ -157,7 +162,7 @@ describe('workflow.expectEventually', () => {
157
162
  const ws = new InMemoryWorkflowService()
158
163
  const sarah = fakeActor('sarah', async () => ({ notifications: [] }))
159
164
  const runId = await setup(ws)
160
- const wire = ws.createWorkflowWire('userFlowTest', runId, {})
165
+ const wire = ws.createWorkflowWire('scenarioTest', runId, {})
161
166
 
162
167
  await assert.rejects(
163
168
  wire.expectEventually(
@@ -178,7 +183,7 @@ describe('workflow.expectEventually', () => {
178
183
  const rpc = {
179
184
  rpcWithWire: async () => ({ ready: ++polls >= 2 }),
180
185
  }
181
- const wire = ws.createWorkflowWire('userFlowTest', runId, rpc)
186
+ const wire = ws.createWorkflowWire('scenarioTest', runId, rpc)
182
187
 
183
188
  const result = await wire.expectEventually(
184
189
  'job finishes',
@@ -325,9 +325,9 @@ export type WorkflowsMeta = Record<
325
325
  context?: WorkflowContext
326
326
  dsl?: boolean
327
327
  expose?: boolean
328
- /** True for pikkuUserFlow workflows (complex + actor steps). */
329
- userFlow?: boolean
330
- /** Actor names a user flow declares (personas it runs steps as). */
328
+ /** True for pikkuScenario workflows (complex + actor steps). */
329
+ scenario?: boolean
330
+ /** Actor names a scenario declares (personas it runs steps as). */
331
331
  actors?: string[]
332
332
  }
333
333
  >
@@ -342,13 +342,13 @@ export interface WorkflowRuntimeMeta {
342
342
  name: string
343
343
  /** Pikku function name (for execution) */
344
344
  pikkuFuncId: string
345
- /** Source type: 'dsl' (serializable), 'complex' (has inline steps), 'graph', 'user-flow' (complex + actor steps) */
346
- source: 'dsl' | 'complex' | 'graph' | 'dynamic-workflow' | 'user-flow'
345
+ /** Source type: 'dsl' (serializable), 'complex' (has inline steps), 'graph', 'scenario' (complex + actor steps) */
346
+ source: 'dsl' | 'complex' | 'graph' | 'dynamic-workflow' | 'scenario'
347
347
  /** Optional description */
348
348
  description?: string
349
349
  /** Tags for organization */
350
350
  tags?: string[]
351
- /** Actor names a user flow declares (personas it runs steps as). */
351
+ /** Actor names a scenario declares (personas it runs steps as). */
352
352
  actors?: string[]
353
353
  /** Serialized nodes */
354
354
  nodes?: Record<string, any>