@pikku/core 0.12.7 → 0.12.9

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 (52) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/env.d.ts +1 -0
  3. package/dist/env.js +1 -0
  4. package/dist/errors/errors.d.ts +14 -0
  5. package/dist/errors/errors.js +27 -0
  6. package/dist/handle-error.js +2 -1
  7. package/dist/middleware/auth-bearer.js +10 -1
  8. package/dist/middleware/auth-cookie.js +34 -25
  9. package/dist/middleware/timeout.js +11 -5
  10. package/dist/pikku-state.js +1 -1
  11. package/dist/services/local-content.d.ts +7 -4
  12. package/dist/services/local-content.js +42 -12
  13. package/dist/services/local-secrets.js +2 -2
  14. package/dist/testing/service-tests.js +1 -1
  15. package/dist/wirings/ai-agent/ai-agent-prepare.js +2 -1
  16. package/dist/wirings/ai-agent/ai-agent-runner.js +2 -1
  17. package/dist/wirings/ai-agent/ai-agent-stream.js +2 -1
  18. package/dist/wirings/channel/local/local-channel-runner.js +11 -6
  19. package/dist/wirings/http/http-runner.js +4 -2
  20. package/dist/wirings/http/pikku-fetch-http-request.js +11 -1
  21. package/dist/wirings/http/web-request.js +3 -1
  22. package/dist/wirings/workflow/graph/graph-runner.d.ts +7 -0
  23. package/dist/wirings/workflow/graph/graph-runner.js +72 -7
  24. package/dist/wirings/workflow/pikku-workflow-service.d.ts +3 -0
  25. package/dist/wirings/workflow/pikku-workflow-service.js +59 -16
  26. package/dist/wirings/workflow/workflow.types.d.ts +4 -0
  27. package/package.json +1 -1
  28. package/src/env.ts +1 -0
  29. package/src/errors/errors.ts +35 -0
  30. package/src/handle-error.ts +2 -1
  31. package/src/middleware/auth-bearer.ts +10 -1
  32. package/src/middleware/auth-cookie.ts +11 -4
  33. package/src/middleware/timeout.ts +14 -7
  34. package/src/pikku-state.ts +1 -1
  35. package/src/services/local-content.ts +61 -13
  36. package/src/services/local-secrets.test.ts +2 -2
  37. package/src/services/local-secrets.ts +2 -2
  38. package/src/testing/service-tests.ts +1 -1
  39. package/src/wirings/ai-agent/ai-agent-prepare.ts +2 -1
  40. package/src/wirings/ai-agent/ai-agent-runner.test.ts +34 -0
  41. package/src/wirings/ai-agent/ai-agent-runner.ts +2 -1
  42. package/src/wirings/ai-agent/ai-agent-stream.ts +2 -1
  43. package/src/wirings/channel/local/local-channel-runner.ts +11 -6
  44. package/src/wirings/http/http-runner.ts +4 -2
  45. package/src/wirings/http/pikku-fetch-http-request.ts +11 -1
  46. package/src/wirings/http/web-request.ts +3 -1
  47. package/src/wirings/workflow/graph/graph-runner.test.ts +42 -0
  48. package/src/wirings/workflow/graph/graph-runner.ts +84 -7
  49. package/src/wirings/workflow/pikku-workflow-service.test.ts +109 -0
  50. package/src/wirings/workflow/pikku-workflow-service.ts +95 -25
  51. package/src/wirings/workflow/workflow.types.ts +4 -0
  52. package/tsconfig.tsbuildinfo +1 -1
@@ -26,6 +26,7 @@ import {
26
26
  import type { WorkflowService } from '../../services/workflow-service.js'
27
27
  import { PikkuError, addError } from '../../errors/error-handler.js'
28
28
  import { RPCNotFoundError } from '../rpc/rpc-runner.js'
29
+ import { ChildWorkflowStartedException } from './graph/graph-runner.js'
29
30
 
30
31
  /**
31
32
  * Exception thrown when workflow needs to pause for async step
@@ -111,6 +112,10 @@ const WORKFLOW_END_STATES: ReadonlySet<string> = new Set([
111
112
  export abstract class PikkuWorkflowService implements WorkflowService {
112
113
  private inlineRuns = new Set<string>()
113
114
 
115
+ protected get logger() {
116
+ return getSingletonServices()?.logger
117
+ }
118
+
114
119
  constructor() {}
115
120
 
116
121
  /**
@@ -386,12 +391,17 @@ export abstract class PikkuWorkflowService implements WorkflowService {
386
391
  data: any
387
392
  ): Promise<void> {
388
393
  const queueService = this.verifyQueueService()
389
- await queueService.add(this.getConfig().stepWorkerQueueName, {
390
- runId,
391
- stepName,
392
- rpcName,
393
- data,
394
- })
394
+ await queueService.add(
395
+ this.getConfig().stepWorkerQueueName,
396
+ JSON.parse(
397
+ JSON.stringify({
398
+ runId,
399
+ stepName,
400
+ rpcName,
401
+ data,
402
+ })
403
+ )
404
+ )
395
405
  }
396
406
 
397
407
  /**
@@ -447,7 +457,9 @@ export abstract class PikkuWorkflowService implements WorkflowService {
447
457
 
448
458
  if (workflowMeta.source === 'graph' || workflowMeta.source === 'ai-agent') {
449
459
  const shouldInline =
450
- options?.inline || !getSingletonServices()?.queueService
460
+ options?.inline ||
461
+ workflowMeta.inline ||
462
+ !getSingletonServices()?.queueService
451
463
  return runWorkflowGraph(
452
464
  this,
453
465
  name,
@@ -472,7 +484,9 @@ export abstract class PikkuWorkflowService implements WorkflowService {
472
484
  }
473
485
 
474
486
  const shouldInline =
475
- options?.inline || !getSingletonServices()?.queueService
487
+ options?.inline ||
488
+ workflowMeta.inline ||
489
+ !getSingletonServices()?.queueService
476
490
 
477
491
  const runId = await this.createRun(
478
492
  name,
@@ -484,16 +498,22 @@ export abstract class PikkuWorkflowService implements WorkflowService {
484
498
 
485
499
  if (shouldInline) {
486
500
  this.inlineRuns.add(runId)
487
- this.runWorkflowJob(runId, rpcService)
488
- .catch((err) => {
501
+ try {
502
+ await this.runWorkflowJob(runId, rpcService)
503
+ } catch (error: any) {
504
+ if (
505
+ error.name !== 'WorkflowAsyncException' &&
506
+ error.name !== 'WorkflowCancelledException' &&
507
+ error.name !== 'WorkflowSuspendedException'
508
+ ) {
489
509
  getSingletonServices()!.logger.error(
490
510
  `Workflow ${name} (run ${runId}) failed:`,
491
- err
511
+ error
492
512
  )
493
- })
494
- .finally(() => {
495
- this.inlineRuns.delete(runId)
496
- })
513
+ }
514
+ } finally {
515
+ this.inlineRuns.delete(runId)
516
+ }
497
517
  } else {
498
518
  await this.resumeWorkflow(runId)
499
519
  }
@@ -553,6 +573,18 @@ export abstract class PikkuWorkflowService implements WorkflowService {
553
573
 
554
574
  if (workflowMeta?.source === 'graph') {
555
575
  await continueGraph(this, runId, run.workflow)
576
+ const updatedRun = await this.getRun(runId)
577
+ if (updatedRun?.status === 'completed') {
578
+ await this.onChildWorkflowCompleted(updatedRun, updatedRun.output)
579
+ } else if (
580
+ updatedRun?.status === 'failed' ||
581
+ updatedRun?.status === 'cancelled'
582
+ ) {
583
+ await this.onChildWorkflowFailed(
584
+ updatedRun,
585
+ new Error(updatedRun.error?.message || 'Child workflow failed')
586
+ )
587
+ }
556
588
  return
557
589
  }
558
590
 
@@ -587,6 +619,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
587
619
  )
588
620
 
589
621
  await this.updateRunStatus(runId, 'completed', result)
622
+ await this.onChildWorkflowCompleted(run, result)
590
623
  } catch (error: any) {
591
624
  if (error instanceof WorkflowAsyncException) {
592
625
  throw error
@@ -598,6 +631,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
598
631
  stack: '',
599
632
  code: 'WORKFLOW_CANCELLED',
600
633
  })
634
+ await this.onChildWorkflowFailed(run, error)
601
635
  throw error
602
636
  }
603
637
 
@@ -615,12 +649,41 @@ export abstract class PikkuWorkflowService implements WorkflowService {
615
649
  stack: error.stack,
616
650
  code: error.code,
617
651
  })
652
+ await this.onChildWorkflowFailed(run, error)
618
653
 
619
654
  throw error
620
655
  }
621
656
  })
622
657
  }
623
658
 
659
+ private async onChildWorkflowCompleted(
660
+ childRun: WorkflowRun,
661
+ result: any
662
+ ): Promise<void> {
663
+ const { parentRunId, parentStepId } = childRun.wire ?? {}
664
+ if (!parentRunId || !parentStepId) return
665
+
666
+ this.logger?.debug(
667
+ `Child workflow ${childRun.id} completed, updating parent step ${parentStepId}`
668
+ )
669
+ await this.setStepResult(parentStepId, result)
670
+ await this.resumeWorkflow(parentRunId)
671
+ }
672
+
673
+ private async onChildWorkflowFailed(
674
+ childRun: WorkflowRun,
675
+ error: Error
676
+ ): Promise<void> {
677
+ const { parentRunId, parentStepId } = childRun.wire ?? {}
678
+ if (!parentRunId || !parentStepId) return
679
+
680
+ this.logger?.debug(
681
+ `Child workflow ${childRun.id} failed, updating parent step ${parentStepId}`
682
+ )
683
+ await this.setStepError(parentStepId, error)
684
+ await this.resumeWorkflow(parentRunId)
685
+ }
686
+
624
687
  private async runVersionMismatchFallback(
625
688
  run: WorkflowRun,
626
689
  currentMeta: { source: string },
@@ -666,9 +729,8 @@ export abstract class PikkuWorkflowService implements WorkflowService {
666
729
  // Get step state
667
730
  let stepState = await this.getStepState(runId, stepName)
668
731
 
669
- // Idempotency - if already succeeded, resume orchestrator and return
732
+ // Idempotency - if already succeeded, nothing to do
670
733
  if (stepState.status === 'succeeded') {
671
- await this.resumeWorkflow(runId)
672
734
  return
673
735
  }
674
736
 
@@ -682,8 +744,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
682
744
  stepState = await this.createRetryAttempt(stepState.stepId, 'running')
683
745
  }
684
746
 
685
- if (stepState.status === 'pending') {
686
- // Mark pending step as running before execution
747
+ if (stepState.status === 'pending' || stepState.status === 'scheduled') {
687
748
  await this.setStepRunning(stepState.stepId)
688
749
  }
689
750
 
@@ -725,6 +786,13 @@ export abstract class PikkuWorkflowService implements WorkflowService {
725
786
  // Resume orchestrator to continue workflow
726
787
  await this.resumeWorkflow(runId)
727
788
  } catch (error: any) {
789
+ if (error instanceof ChildWorkflowStartedException) {
790
+ this.logger?.debug(
791
+ `Workflow step '${stepName}': child workflow ${error.childRunId} started, waiting for completion`
792
+ )
793
+ return
794
+ }
795
+
728
796
  if (error instanceof RPCNotFoundError) {
729
797
  await this.setStepError(stepState.stepId, error)
730
798
  await this.updateRunStatus(runId, 'suspended', undefined, {
@@ -848,12 +916,14 @@ export abstract class PikkuWorkflowService implements WorkflowService {
848
916
 
849
917
  await getSingletonServices()!.queueService!.add(
850
918
  this.getConfig().stepWorkerQueueName,
851
- {
852
- runId,
853
- stepName,
854
- rpcName,
855
- data,
856
- },
919
+ JSON.parse(
920
+ JSON.stringify({
921
+ runId,
922
+ stepName,
923
+ rpcName,
924
+ data,
925
+ })
926
+ ),
857
927
  {
858
928
  // attempts includes initial attempt, retries doesn't
859
929
  attempts: retries + 1,
@@ -40,6 +40,7 @@ export interface WorkflowRunWire {
40
40
  type: string
41
41
  id?: string
42
42
  parentRunId?: string
43
+ parentStepId?: string
43
44
  }
44
45
 
45
46
  export interface WorkflowServiceConfig {
@@ -224,6 +225,7 @@ export type WorkflowsMeta = Record<
224
225
  steps: WorkflowStepMeta[]
225
226
  context?: WorkflowContext
226
227
  dsl?: boolean
228
+ inline?: boolean
227
229
  }
228
230
  >
229
231
 
@@ -243,6 +245,8 @@ export interface WorkflowRuntimeMeta {
243
245
  description?: string
244
246
  /** Tags for organization */
245
247
  tags?: string[]
248
+ /** If true, workflow always executes inline without queues */
249
+ inline?: boolean
246
250
  /** Serialized nodes */
247
251
  nodes?: Record<string, any>
248
252
  /** Entry node IDs for graph workflows (computed at build time) */