@pikku/core 0.12.14 → 0.12.15

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 (123) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/errors/errors.d.ts +4 -0
  3. package/dist/errors/errors.js +12 -0
  4. package/dist/function/function-runner.js +2 -1
  5. package/dist/function/functions.types.js +1 -0
  6. package/dist/function/index.d.ts +2 -1
  7. package/dist/function/index.js +1 -0
  8. package/dist/handle-error.d.ts +2 -2
  9. package/dist/handle-error.js +8 -8
  10. package/dist/index.d.ts +1 -2
  11. package/dist/index.js +1 -2
  12. package/dist/middleware/index.d.ts +2 -0
  13. package/dist/middleware/index.js +2 -0
  14. package/dist/middleware/remote-auth.js +5 -2
  15. package/dist/permissions.d.ts +13 -1
  16. package/dist/permissions.js +51 -0
  17. package/dist/pikku-state.d.ts +0 -1
  18. package/dist/pikku-state.js +1 -4
  19. package/dist/remote.d.ts +10 -0
  20. package/dist/remote.js +32 -0
  21. package/dist/schema.js +2 -0
  22. package/dist/services/deployment-service.d.ts +12 -1
  23. package/dist/services/in-memory-queue-service.d.ts +7 -0
  24. package/dist/services/in-memory-queue-service.js +28 -0
  25. package/dist/services/index.d.ts +4 -1
  26. package/dist/services/index.js +2 -1
  27. package/dist/services/logger-console.d.ts +26 -40
  28. package/dist/services/logger-console.js +102 -46
  29. package/dist/services/logger.d.ts +5 -0
  30. package/dist/services/meta-service.d.ts +175 -0
  31. package/dist/services/meta-service.js +310 -0
  32. package/dist/services/workflow-service.d.ts +6 -1
  33. package/dist/testing/service-tests.js +0 -8
  34. package/dist/types/core.types.d.ts +8 -0
  35. package/dist/types/state.types.d.ts +2 -2
  36. package/dist/wirings/ai-agent/ai-agent-prepare.js +42 -21
  37. package/dist/wirings/ai-agent/ai-agent-registry.js +2 -2
  38. package/dist/wirings/ai-agent/ai-agent-runner.js +18 -1
  39. package/dist/wirings/ai-agent/ai-agent-stream.js +1 -0
  40. package/dist/wirings/ai-agent/ai-agent.types.d.ts +5 -3
  41. package/dist/wirings/channel/channel-runner.js +3 -3
  42. package/dist/wirings/cli/cli-runner.js +3 -2
  43. package/dist/wirings/cli/command-parser.js +7 -3
  44. package/dist/wirings/http/http-runner.d.ts +1 -1
  45. package/dist/wirings/http/http-runner.js +23 -11
  46. package/dist/wirings/http/http.types.d.ts +2 -0
  47. package/dist/wirings/mcp/mcp-runner.js +5 -3
  48. package/dist/wirings/queue/queue-runner.d.ts +3 -1
  49. package/dist/wirings/queue/queue-runner.js +7 -3
  50. package/dist/wirings/rpc/rpc-runner.js +56 -90
  51. package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -1
  52. package/dist/wirings/scheduler/scheduler-runner.js +9 -5
  53. package/dist/wirings/trigger/trigger-runner.js +4 -2
  54. package/dist/wirings/workflow/dsl/workflow-runner.d.ts +1 -1
  55. package/dist/wirings/workflow/dsl/workflow-runner.js +6 -9
  56. package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -1
  57. package/dist/wirings/workflow/graph/graph-runner.js +18 -4
  58. package/dist/wirings/workflow/index.d.ts +4 -2
  59. package/dist/wirings/workflow/index.js +4 -2
  60. package/dist/wirings/workflow/pikku-workflow-service.d.ts +16 -4
  61. package/dist/wirings/workflow/pikku-workflow-service.js +216 -24
  62. package/dist/wirings/workflow/workflow-queue-workers.d.ts +40 -0
  63. package/dist/wirings/workflow/workflow-queue-workers.js +41 -0
  64. package/dist/wirings/workflow/workflow.types.d.ts +17 -1
  65. package/package.json +4 -1
  66. package/src/errors/errors.ts +12 -0
  67. package/src/function/function-runner.ts +5 -4
  68. package/src/function/functions.types.ts +1 -0
  69. package/src/function/index.ts +10 -0
  70. package/src/handle-error.test.ts +2 -2
  71. package/src/handle-error.ts +8 -8
  72. package/src/index.ts +1 -2
  73. package/src/middleware/index.ts +2 -0
  74. package/src/middleware/remote-auth.test.ts +4 -4
  75. package/src/middleware/remote-auth.ts +7 -2
  76. package/src/permissions.ts +70 -0
  77. package/src/pikku-state.test.ts +0 -26
  78. package/src/pikku-state.ts +1 -5
  79. package/src/remote.ts +46 -0
  80. package/src/schema.ts +1 -0
  81. package/src/services/deployment-service.ts +17 -1
  82. package/src/services/in-memory-queue-service.ts +46 -0
  83. package/src/services/index.ts +21 -1
  84. package/src/services/logger-console.ts +127 -47
  85. package/src/services/logger.ts +6 -0
  86. package/src/services/meta-service.ts +523 -0
  87. package/src/services/workflow-service.ts +8 -0
  88. package/src/testing/service-tests.ts +0 -10
  89. package/src/types/core.types.ts +8 -0
  90. package/src/types/state.types.ts +2 -2
  91. package/src/wirings/ai-agent/ai-agent-prepare.ts +51 -40
  92. package/src/wirings/ai-agent/ai-agent-registry.ts +3 -3
  93. package/src/wirings/ai-agent/ai-agent-runner.ts +16 -1
  94. package/src/wirings/ai-agent/ai-agent-stream.ts +8 -0
  95. package/src/wirings/ai-agent/ai-agent.types.ts +5 -3
  96. package/src/wirings/channel/channel-runner.ts +4 -5
  97. package/src/wirings/cli/cli-runner.test.ts +8 -7
  98. package/src/wirings/cli/cli-runner.ts +4 -3
  99. package/src/wirings/cli/command-parser.ts +8 -3
  100. package/src/wirings/http/http-runner.ts +27 -11
  101. package/src/wirings/http/http.types.ts +2 -0
  102. package/src/wirings/mcp/mcp-runner.ts +7 -9
  103. package/src/wirings/queue/queue-runner.test.ts +5 -11
  104. package/src/wirings/queue/queue-runner.ts +11 -3
  105. package/src/wirings/rpc/rpc-runner.ts +82 -115
  106. package/src/wirings/scheduler/scheduler-runner.test.ts +5 -11
  107. package/src/wirings/scheduler/scheduler-runner.ts +13 -5
  108. package/src/wirings/trigger/trigger-runner.test.ts +10 -11
  109. package/src/wirings/trigger/trigger-runner.ts +6 -4
  110. package/src/wirings/workflow/dsl/workflow-runner.ts +11 -10
  111. package/src/wirings/workflow/graph/graph-runner.ts +19 -4
  112. package/src/wirings/workflow/index.ts +17 -6
  113. package/src/wirings/workflow/pikku-workflow-service.ts +284 -24
  114. package/src/wirings/workflow/workflow-queue-workers.ts +85 -0
  115. package/src/wirings/workflow/workflow.types.ts +16 -1
  116. package/tsconfig.tsbuildinfo +1 -1
  117. package/dist/wirings/ai-agent/agent-dynamic-workflow.d.ts +0 -6
  118. package/dist/wirings/ai-agent/agent-dynamic-workflow.js +0 -468
  119. package/dist/wirings/workflow/workflow-helpers.d.ts +0 -36
  120. package/dist/wirings/workflow/workflow-helpers.js +0 -38
  121. package/src/wirings/ai-agent/agent-dynamic-workflow.ts +0 -610
  122. package/src/wirings/workflow/workflow-helpers.test.ts +0 -129
  123. package/src/wirings/workflow/workflow-helpers.ts +0 -79
@@ -1,16 +1,56 @@
1
- import { runPikkuFunc } from '../../function/function-runner.js'
1
+ import { runPikkuFunc, addFunction } from '../../function/function-runner.js'
2
+ import {
3
+ pikkuWorkflowWorkerFunc,
4
+ pikkuWorkflowOrchestratorFunc,
5
+ pikkuWorkflowSleeperFunc,
6
+ } from './workflow-queue-workers.js'
7
+ import { wireQueueWorker } from '../queue/queue-runner.js'
2
8
  import {
3
9
  getSingletonServices,
4
10
  getCreateWireServices,
5
11
  pikkuState,
6
12
  } from '../../pikku-state.js'
7
13
  import { getDurationInMilliseconds } from '../../time-utils.js'
14
+
15
+ const resolveWorkflowMeta = (
16
+ name: string
17
+ ): { meta: any; packageName: string | null; resolvedName: string } | null => {
18
+ const rootMeta = pikkuState(null, 'workflows', 'meta')
19
+ if (rootMeta[name]) {
20
+ return { meta: rootMeta[name], packageName: null, resolvedName: name }
21
+ }
22
+
23
+ const colonIndex = name.indexOf(':')
24
+ if (colonIndex !== -1) {
25
+ const namespace = name.substring(0, colonIndex)
26
+ const localName = name.substring(colonIndex + 1)
27
+ const addons = pikkuState(null, 'addons', 'packages')
28
+ const pkgConfig = addons?.get(namespace)
29
+ if (pkgConfig) {
30
+ const addonMeta = pikkuState(pkgConfig.package, 'workflows', 'meta')
31
+ if (addonMeta?.[localName]) {
32
+ return {
33
+ meta: addonMeta[localName],
34
+ packageName: pkgConfig.package,
35
+ resolvedName: localName,
36
+ }
37
+ }
38
+ }
39
+ }
40
+
41
+ return null
42
+ }
43
+
44
+ const toKebab = (s: string) =>
45
+ s.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
8
46
  import type { PikkuWire, SerializedError } from '../../types/core.types.js'
9
47
  import type { QueueService } from '../queue/queue.types.js'
10
48
  import type {
11
49
  PikkuWorkflowWire,
12
50
  StepState,
51
+ StepStatus,
13
52
  WorkflowRun,
53
+ WorkflowRunStatus,
14
54
  WorkflowRunWire,
15
55
  WorkflowStatus,
16
56
  WorkflowVersionStatus,
@@ -116,7 +156,87 @@ export abstract class PikkuWorkflowService implements WorkflowService {
116
156
  return getSingletonServices()?.logger
117
157
  }
118
158
 
119
- constructor() {}
159
+ constructor() {
160
+ const functions = pikkuState(null, 'function', 'functions')
161
+ const functionsMeta = pikkuState(null, 'function', 'meta')
162
+
163
+ // Minimal meta for internal workflow functions (satisfies FunctionMeta)
164
+ const mkMeta = (funcId: string) => ({
165
+ pikkuFuncId: funcId,
166
+ sessionless: true,
167
+ functionType: 'helper' as const,
168
+ inputSchemaName: null,
169
+ outputSchemaName: null,
170
+ })
171
+
172
+ const queueMeta = pikkuState(null, 'queue', 'meta')
173
+
174
+ const registerWorkflowFunc = (
175
+ funcId: string,
176
+ func: { func: unknown },
177
+ queueName: string
178
+ ) => {
179
+ if (functions.has(funcId)) return
180
+ addFunction(funcId, func as never)
181
+ if (!queueMeta[queueName]) {
182
+ queueMeta[queueName] = { pikkuFuncId: funcId, name: queueName }
183
+ }
184
+ wireQueueWorker({ name: queueName, func } as never)
185
+ if (!functionsMeta[funcId]) {
186
+ functionsMeta[funcId] = mkMeta(funcId)
187
+ }
188
+ }
189
+
190
+ // Register shared queue workers for monolith deployments
191
+ registerWorkflowFunc(
192
+ 'pikkuWorkflowOrchestrator',
193
+ { func: pikkuWorkflowOrchestratorFunc },
194
+ 'pikku-workflow-orchestrator'
195
+ )
196
+ registerWorkflowFunc(
197
+ 'pikkuWorkflowStepWorker',
198
+ { func: pikkuWorkflowWorkerFunc },
199
+ 'pikku-workflow-step-worker'
200
+ )
201
+
202
+ // Register per-workflow queue workers (root + addon packages)
203
+ const registerQueueWorkers = (queueMeta: Record<string, any>) => {
204
+ for (const [queueName, meta] of Object.entries(queueMeta)) {
205
+ if (functions.has(meta.pikkuFuncId)) continue
206
+ if (queueName.startsWith('wf-orchestrator-')) {
207
+ registerWorkflowFunc(
208
+ meta.pikkuFuncId,
209
+ { func: pikkuWorkflowOrchestratorFunc },
210
+ queueName
211
+ )
212
+ } else if (queueName.startsWith('wf-step-')) {
213
+ registerWorkflowFunc(
214
+ meta.pikkuFuncId,
215
+ { func: pikkuWorkflowWorkerFunc },
216
+ queueName
217
+ )
218
+ }
219
+ }
220
+ }
221
+
222
+ registerQueueWorkers(pikkuState(null, 'queue', 'meta'))
223
+
224
+ const addons = pikkuState(null, 'addons', 'packages')
225
+ if (addons) {
226
+ for (const [, addon] of addons) {
227
+ const addonQueueMeta = pikkuState(addon.package, 'queue', 'meta')
228
+ if (addonQueueMeta) {
229
+ registerQueueWorkers(addonQueueMeta)
230
+ }
231
+ }
232
+ }
233
+
234
+ if (!functions.has('pikkuWorkflowSleeper')) {
235
+ addFunction('pikkuWorkflowSleeper', {
236
+ func: pikkuWorkflowSleeperFunc,
237
+ })
238
+ }
239
+ }
120
240
 
121
241
  /**
122
242
  * Check if a run is executing inline (without queues)
@@ -162,6 +282,55 @@ export abstract class PikkuWorkflowService implements WorkflowService {
162
282
  */
163
283
  abstract getRun(id: string): Promise<WorkflowRun | null>
164
284
 
285
+ /**
286
+ * Get minimal workflow run status with step summaries.
287
+ * Used by the public API — the console addon provides the full verbose view.
288
+ */
289
+ async getRunStatus(id: string): Promise<WorkflowRunStatus | null> {
290
+ const run = await this.getRun(id)
291
+ if (!run) return null
292
+
293
+ const history = await this.getRunHistory(id)
294
+ const terminalStatuses = new Set(['completed', 'failed', 'cancelled'])
295
+
296
+ // Build step summaries from history (latest attempt per step)
297
+ const stepMap = new Map<
298
+ string,
299
+ { status: StepStatus; startedAt?: Date; completedAt?: Date }
300
+ >()
301
+ for (const step of history) {
302
+ const existing = stepMap.get(step.stepName)
303
+ if (!existing || step.updatedAt > existing.completedAt!) {
304
+ stepMap.set(step.stepName, {
305
+ status: step.status,
306
+ startedAt: step.runningAt ?? step.createdAt,
307
+ completedAt: step.succeededAt ?? step.failedAt,
308
+ })
309
+ }
310
+ }
311
+
312
+ const steps = [...stepMap.entries()].map(([name, s]) => ({
313
+ name,
314
+ status: s.status,
315
+ duration:
316
+ s.startedAt && s.completedAt
317
+ ? s.completedAt.getTime() - s.startedAt.getTime()
318
+ : undefined,
319
+ }))
320
+
321
+ return {
322
+ id: run.id,
323
+ status: run.status,
324
+ startedAt: run.createdAt,
325
+ completedAt: terminalStatuses.has(run.status) ? run.updatedAt : undefined,
326
+ steps,
327
+ output: run.status === 'completed' ? run.output : undefined,
328
+ error: run.error
329
+ ? { message: run.error.message ?? 'Unknown error' }
330
+ : undefined,
331
+ }
332
+ }
333
+
165
334
  /**
166
335
  * Get workflow run history (all step attempts in chronological order)
167
336
  * @param runId - Run ID
@@ -379,9 +548,18 @@ export abstract class PikkuWorkflowService implements WorkflowService {
379
548
  * Resume a paused workflow by triggering the orchestrator
380
549
  * @param runId - Run ID
381
550
  */
382
- public async resumeWorkflow(runId: string): Promise<void> {
551
+ public async resumeWorkflow(
552
+ runId: string,
553
+ workflowName?: string
554
+ ): Promise<void> {
383
555
  const queueService = this.verifyQueueService()
384
- await queueService.add(this.getConfig().orchestratorQueueName, { runId })
556
+ if (!workflowName) {
557
+ const run = await this.getRun(runId)
558
+ workflowName = run?.workflow
559
+ }
560
+ await queueService.add(this.getOrchestratorQueueName(workflowName), {
561
+ runId,
562
+ })
385
563
  }
386
564
 
387
565
  public async queueStepWorker(
@@ -392,7 +570,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
392
570
  ): Promise<void> {
393
571
  const queueService = this.verifyQueueService()
394
572
  await queueService.add(
395
- this.getConfig().stepWorkerQueueName,
573
+ this.getStepWorkerQueueName(rpcName),
396
574
  JSON.parse(
397
575
  JSON.stringify({
398
576
  runId,
@@ -424,11 +602,16 @@ export abstract class PikkuWorkflowService implements WorkflowService {
424
602
  */
425
603
  protected async scheduleOrchestratorRetry(
426
604
  runId: string,
427
- retryDelay?: number | string
605
+ retryDelay?: number | string,
606
+ workflowName?: string
428
607
  ): Promise<void> {
429
608
  const queueService = this.verifyQueueService()
609
+ if (!workflowName) {
610
+ const run = await this.getRun(runId)
611
+ workflowName = run?.workflow
612
+ }
430
613
  await queueService.add(
431
- this.getConfig().orchestratorQueueName,
614
+ this.getOrchestratorQueueName(workflowName),
432
615
  { runId },
433
616
  retryDelay ? { delay: getDurationInMilliseconds(retryDelay) } : undefined
434
617
  )
@@ -447,15 +630,27 @@ export abstract class PikkuWorkflowService implements WorkflowService {
447
630
  rpcService: any,
448
631
  options?: { inline?: boolean; startNode?: string }
449
632
  ): Promise<{ runId: string }> {
450
- // Check meta to determine workflow type
451
- const meta = pikkuState(null, 'workflows', 'meta')
452
- const workflowMeta = meta[name]
633
+ // Resolve workflow from static meta (root or addon namespace), then dynamic DB
634
+ const resolved = resolveWorkflowMeta(name)
635
+ let workflowMeta = resolved?.meta
636
+ const packageName = resolved?.packageName ?? null
637
+
638
+ if (!workflowMeta) {
639
+ const dynamicWorkflows = await this.getAIGeneratedWorkflows()
640
+ const match = dynamicWorkflows.find((w) => w.workflowName === name)
641
+ if (match?.graph) {
642
+ workflowMeta = match.graph
643
+ }
644
+ }
453
645
 
454
646
  if (!workflowMeta) {
455
647
  throw new WorkflowNotFoundError(name)
456
648
  }
457
649
 
458
- if (workflowMeta.source === 'graph' || workflowMeta.source === 'ai-agent') {
650
+ if (
651
+ workflowMeta.source === 'graph' ||
652
+ workflowMeta.source === 'dynamic-workflow'
653
+ ) {
459
654
  const shouldInline =
460
655
  options?.inline ||
461
656
  workflowMeta.inline ||
@@ -467,13 +662,14 @@ export abstract class PikkuWorkflowService implements WorkflowService {
467
662
  rpcService,
468
663
  shouldInline,
469
664
  options?.startNode,
470
- wire
665
+ wire,
666
+ workflowMeta
471
667
  )
472
668
  }
473
669
 
474
670
  // DSL workflow - check registration exists
475
- const registrations = pikkuState(null, 'workflows', 'registrations')
476
- const workflow = registrations.get(name)
671
+ const registrations = pikkuState(packageName, 'workflows', 'registrations')
672
+ const workflow = registrations.get(resolved?.resolvedName ?? name)
477
673
 
478
674
  if (!workflow) {
479
675
  throw new WorkflowNotFoundError(name)
@@ -559,8 +755,9 @@ export abstract class PikkuWorkflowService implements WorkflowService {
559
755
  throw new WorkflowRunNotFoundError(runId)
560
756
  }
561
757
 
562
- const meta = pikkuState(null, 'workflows', 'meta')
563
- const workflowMeta = meta[run.workflow]
758
+ const resolved = resolveWorkflowMeta(run.workflow)
759
+ const workflowMeta = resolved?.meta
760
+ const pkgName = resolved?.packageName ?? null
564
761
 
565
762
  if (
566
763
  run.graphHash &&
@@ -571,7 +768,10 @@ export abstract class PikkuWorkflowService implements WorkflowService {
571
768
  return
572
769
  }
573
770
 
574
- if (workflowMeta?.source === 'graph') {
771
+ if (
772
+ workflowMeta?.source === 'graph' ||
773
+ workflowMeta?.source === 'dynamic-workflow'
774
+ ) {
575
775
  await continueGraph(this, runId, run.workflow)
576
776
  const updatedRun = await this.getRun(runId)
577
777
  if (updatedRun?.status === 'completed') {
@@ -588,17 +788,44 @@ export abstract class PikkuWorkflowService implements WorkflowService {
588
788
  return
589
789
  }
590
790
 
591
- const registrations = pikkuState(null, 'workflows', 'registrations')
592
- const workflow = registrations.get(run.workflow)
791
+ if (!workflowMeta) {
792
+ const dynamicWorkflows = await this.getAIGeneratedWorkflows()
793
+ const match = dynamicWorkflows.find(
794
+ (w) => w.workflowName === run.workflow
795
+ )
796
+ if (match?.graph) {
797
+ await continueGraph(this, runId, run.workflow, match.graph)
798
+ const updatedRun = await this.getRun(runId)
799
+ if (updatedRun?.status === 'completed') {
800
+ await this.onChildWorkflowCompleted(updatedRun, updatedRun.output)
801
+ } else if (
802
+ updatedRun?.status === 'failed' ||
803
+ updatedRun?.status === 'cancelled'
804
+ ) {
805
+ await this.onChildWorkflowFailed(
806
+ updatedRun,
807
+ new Error(updatedRun.error?.message || 'Child workflow failed')
808
+ )
809
+ }
810
+ return
811
+ }
812
+ }
813
+
814
+ const registrations = pikkuState(pkgName, 'workflows', 'registrations')
815
+ const workflow = registrations.get(resolved?.resolvedName ?? run.workflow)
593
816
  if (!workflow) {
594
817
  throw new WorkflowNotFoundError(run.workflow)
595
818
  }
596
819
 
597
820
  await this.withRunLock(runId, async () => {
821
+ const addonNs = run.workflow.includes(':')
822
+ ? run.workflow.substring(0, run.workflow.indexOf(':'))
823
+ : null
598
824
  const workflowWire = this.createWorkflowWire(
599
825
  run.workflow,
600
826
  runId,
601
- rpcService
827
+ rpcService,
828
+ addonNs
602
829
  )
603
830
  workflowWire.pikkuUserId = run.wire?.pikkuUserId
604
831
  const wire: PikkuWire = {
@@ -617,6 +844,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
617
844
  wire,
618
845
  createWireServices: getCreateWireServices(),
619
846
  data: () => run.input,
847
+ packageName: pkgName ?? undefined,
620
848
  }
621
849
  )
622
850
 
@@ -763,7 +991,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
763
991
 
764
992
  const isGraphWorkflow =
765
993
  workflowMeta?.source === 'graph' ||
766
- workflowMeta?.source === 'ai-agent'
994
+ workflowMeta?.source === 'dynamic-workflow'
767
995
  if (
768
996
  isGraphWorkflow &&
769
997
  workflowMeta?.nodes &&
@@ -963,7 +1191,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
963
1191
  const retryDelay = stepOptions?.retryDelay
964
1192
 
965
1193
  await getSingletonServices()!.queueService!.add(
966
- this.getConfig().stepWorkerQueueName,
1194
+ this.getStepWorkerQueueName(rpcName),
967
1195
  JSON.parse(
968
1196
  JSON.stringify({
969
1197
  runId,
@@ -1274,7 +1502,8 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1274
1502
  public createWorkflowWire(
1275
1503
  name: string,
1276
1504
  runId: string,
1277
- rpcService: any
1505
+ rpcService: any,
1506
+ addonNamespace?: string | null
1278
1507
  ): PikkuWorkflowWire {
1279
1508
  const workflowWire: PikkuWorkflowWire = {
1280
1509
  name,
@@ -1290,10 +1519,14 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1290
1519
  ) => {
1291
1520
  this.verifyStepName(stepName)
1292
1521
  if (typeof rpcNameOrFn === 'string') {
1522
+ const resolvedRpcName =
1523
+ addonNamespace && !rpcNameOrFn.includes(':')
1524
+ ? `${addonNamespace}:${rpcNameOrFn}`
1525
+ : rpcNameOrFn
1293
1526
  return await this.rpcStep(
1294
1527
  runId,
1295
1528
  stepName,
1296
- rpcNameOrFn,
1529
+ resolvedRpcName,
1297
1530
  dataOrOptions,
1298
1531
  rpcService,
1299
1532
  options
@@ -1344,4 +1577,31 @@ export abstract class PikkuWorkflowService implements WorkflowService {
1344
1577
  sleeperRPCName: workflow?.sleeperRPCName ?? 'pikkuWorkflowSleeper',
1345
1578
  }
1346
1579
  }
1580
+
1581
+ /**
1582
+ * Get the orchestrator queue name for a specific workflow.
1583
+ * Checks queue meta for a per-workflow queue first (e.g. wf-orchestrator-{name}),
1584
+ * falls back to the shared orchestrator queue.
1585
+ */
1586
+ protected getOrchestratorQueueName(workflowName?: string): string {
1587
+ if (workflowName) {
1588
+ const perWorkflow = `wf-orchestrator-${toKebab(workflowName)}`
1589
+ const registrations = pikkuState(null, 'queue', 'registrations')
1590
+ if (registrations.has(perWorkflow)) {
1591
+ return perWorkflow
1592
+ }
1593
+ }
1594
+ return this.getConfig().orchestratorQueueName
1595
+ }
1596
+
1597
+ protected getStepWorkerQueueName(rpcName?: string): string {
1598
+ if (rpcName) {
1599
+ const perStep = `wf-step-${toKebab(rpcName)}`
1600
+ const registrations = pikkuState(null, 'queue', 'registrations')
1601
+ if (registrations.has(perStep)) {
1602
+ return perStep
1603
+ }
1604
+ }
1605
+ return this.getConfig().stepWorkerQueueName
1606
+ }
1347
1607
  }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Queue worker functions for workflow orchestration.
3
+ *
4
+ * These are registered as queue consumers by the codegen pipeline
5
+ * (injected into queue meta when workflows exist). They provide
6
+ * the runtime implementations that process queued workflow jobs.
7
+ */
8
+
9
+ import { getSingletonServices } from '../../pikku-state.js'
10
+ import type { PikkuRPC } from '../rpc/rpc-types.js'
11
+
12
+ export interface WorkflowStepInput {
13
+ runId: string
14
+ stepName: string
15
+ rpcName: string
16
+ data: unknown
17
+ }
18
+
19
+ export interface PikkuWorkflowOrchestratorInput {
20
+ runId: string
21
+ }
22
+
23
+ export interface PikkuWorkflowSleeperInput {
24
+ runId: string
25
+ stepId: string
26
+ }
27
+
28
+ /**
29
+ * Step worker — executes individual workflow steps dispatched via queue.
30
+ * The orchestrator queues steps here when they're async (not inline).
31
+ */
32
+ export async function pikkuWorkflowWorkerFunc(
33
+ _services: Record<string, unknown>,
34
+ { runId, stepName, rpcName, data }: WorkflowStepInput,
35
+ { rpc }: { rpc: PikkuRPC }
36
+ ): Promise<void> {
37
+ const services = getSingletonServices()
38
+ if (!services?.workflowService) {
39
+ throw new Error(
40
+ `Workflow service not initialized: cannot execute workflow step for runId ${runId}, stepName ${stepName}`
41
+ )
42
+ }
43
+ await services.workflowService.executeWorkflowStep(
44
+ runId,
45
+ stepName,
46
+ rpcName,
47
+ data,
48
+ rpc
49
+ )
50
+ }
51
+
52
+ /**
53
+ * Orchestrator — resumes workflow execution after an async step completes.
54
+ * Called when a step worker finishes and the workflow needs to continue.
55
+ */
56
+ export async function pikkuWorkflowOrchestratorFunc(
57
+ _services: Record<string, unknown>,
58
+ { runId }: PikkuWorkflowOrchestratorInput,
59
+ { rpc }: { rpc: PikkuRPC }
60
+ ): Promise<void> {
61
+ const services = getSingletonServices()
62
+ if (!services?.workflowService) {
63
+ throw new Error(
64
+ `Workflow service not initialized: cannot orchestrate workflow for runId ${runId}`
65
+ )
66
+ }
67
+ await services.workflowService.orchestrateWorkflow(runId, rpc)
68
+ }
69
+
70
+ /**
71
+ * Sleeper — wakes a workflow after a workflow.sleep() duration expires.
72
+ * Triggered by a delayed queue message or scheduler callback.
73
+ */
74
+ export async function pikkuWorkflowSleeperFunc(
75
+ _services: Record<string, unknown>,
76
+ { runId, stepId }: PikkuWorkflowSleeperInput
77
+ ): Promise<void> {
78
+ const services = getSingletonServices()
79
+ if (!services?.workflowService) {
80
+ throw new Error(
81
+ `Workflow service not initialized: cannot execute workflow sleep completed for runId ${runId}, stepId ${stepId}`
82
+ )
83
+ }
84
+ await services.workflowService.executeWorkflowSleepCompleted(runId, stepId)
85
+ }
@@ -141,6 +141,20 @@ export interface StepState {
141
141
  failedAt?: Date
142
142
  }
143
143
 
144
+ export interface WorkflowRunStatus {
145
+ id: string
146
+ status: WorkflowStatus
147
+ startedAt: Date
148
+ completedAt?: Date
149
+ steps: Array<{
150
+ name: string
151
+ status: StepStatus
152
+ duration?: number
153
+ }>
154
+ output?: unknown
155
+ error?: { message: string }
156
+ }
157
+
144
158
  export interface WorkflowRunService {
145
159
  listRuns(options?: {
146
160
  workflowName?: string
@@ -228,6 +242,7 @@ export type WorkflowsMeta = Record<
228
242
  context?: WorkflowContext
229
243
  dsl?: boolean
230
244
  inline?: boolean
245
+ expose?: boolean
231
246
  }
232
247
  >
233
248
 
@@ -242,7 +257,7 @@ export interface WorkflowRuntimeMeta {
242
257
  /** Pikku function name (for execution) */
243
258
  pikkuFuncId: string
244
259
  /** Source type: 'dsl' (serializable), 'complex' (has inline steps), 'graph' */
245
- source: 'dsl' | 'complex' | 'graph' | 'ai-agent'
260
+ source: 'dsl' | 'complex' | 'graph' | 'dynamic-workflow'
246
261
  /** Optional description */
247
262
  description?: string
248
263
  /** Tags for organization */